first true commit

This commit is contained in:
Noel Berry 2020-08-26 00:38:01 -07:00
parent c20900d690
commit 8d7e5e4224
88 changed files with 26844 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
build/
out/
temp/
.vs/
.vscode/
CMakeSettings.json

128
CMakeLists.txt Normal file
View File

@ -0,0 +1,128 @@
cmake_minimum_required(VERSION 3.6)
project(blah)
# C++ version
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(blah
public/blah.h
public/blah/app.cpp
public/blah/app.h
public/blah/filesystem.cpp
public/blah/filesystem.h
public/blah/log.cpp
public/blah/log.h
public/blah/time.cpp
public/blah/time.h
public/blah/graphics/graphics.cpp
public/blah/graphics/graphics.h
public/blah/graphics/texture.h
public/blah/graphics/framebuffer.h
public/blah/graphics/shader.h
public/blah/graphics/mesh.h
public/blah/graphics/material.h
public/blah/graphics/material.cpp
public/blah/input/input.cpp
public/blah/input/input.h
public/blah/input/virtual_stick.cpp
public/blah/input/virtual_stick.h
public/blah/input/virtual_button.cpp
public/blah/input/virtual_button.h
public/blah/input/virtual_axis.cpp
public/blah/input/virtual_axis.h
public/blah/containers/list.h
public/blah/containers/stacklist.h
public/blah/containers/str.cpp
public/blah/containers/str.h
public/blah/drawing/batch.cpp
public/blah/drawing/batch.h
public/blah/drawing/spritefont.cpp
public/blah/drawing/spritefont.h
public/blah/drawing/subtexture.cpp
public/blah/drawing/subtexture.h
public/blah/images/aseprite.cpp
public/blah/images/aseprite.h
public/blah/images/font.cpp
public/blah/images/font.h
public/blah/images/image.cpp
public/blah/images/image.h
public/blah/images/packer.cpp
public/blah/images/packer.h
public/blah/math/calc.cpp
public/blah/math/calc.h
public/blah/math/circle.cpp
public/blah/math/circle.h
public/blah/math/color.cpp
public/blah/math/color.h
public/blah/math/ease.h
public/blah/math/line.cpp
public/blah/math/line.h
public/blah/math/mat3x2.cpp
public/blah/math/mat3x2.h
public/blah/math/mat4x4.cpp
public/blah/math/mat4x4.h
public/blah/math/point.cpp
public/blah/math/point.h
public/blah/math/quad.h
public/blah/math/quad.cpp
public/blah/math/rect.cpp
public/blah/math/rect.h
public/blah/math/rectI.cpp
public/blah/math/rectI.h
public/blah/math/stopwatch.cpp
public/blah/math/stopwatch.h
public/blah/math/vec2.cpp
public/blah/math/vec2.h
public/blah/math/vec4.h
public/blah/streams/bufferstream.cpp
public/blah/streams/bufferstream.h
public/blah/streams/filestream.cpp
public/blah/streams/filestream.h
public/blah/streams/memorystream.cpp
public/blah/streams/memorystream.h
public/blah/streams/stream.cpp
public/blah/streams/stream.h
private/blah/third_party/stb_image.h
private/blah/third_party/stb_image_write.h
private/blah/third_party/stb_truetype.h
private/blah/internal/graphics.h
private/blah/internal/graphics_opengl.cpp
private/blah/internal/input.h
private/blah/internal/platform.h
private/blah/internal/platform_sdl2.cpp
)
target_include_directories(blah
PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/public>
PRIVATE
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/private>
)
# Platform Variables
set(SDL2_ENABLED true CACHE BOOL "Use SDL2 as the System implementation")
set(SDL2_INCLUDE_DIRS "" CACHE FILEPATH "SDL2 Headers")
set(SDL2_LIBRARIES "" CACHE FILEPATH "SDL2 Headers")
set(OPENGL_ENABLED true CACHE BOOL "Include OpenGL graphics implementation")
# add OpenGL definition if we're using OpenGL
if (OPENGL_ENABLED)
add_compile_definitions(BLAH_USE_OPENGL)
endif()
# Link and create SDL2 Definition if we're using SDL2
if (SDL2_ENABLED)
add_compile_definitions(BLAH_USE_SDL2)
target_include_directories(blah PUBLIC "$<BUILD_INTERFACE:${SDL2_INCLUDE_DIRS}>")
target_link_libraries(blah PUBLIC ${SDL2_LIBRARIES})
endif()

0
README.md Normal file
View File

View File

@ -0,0 +1,67 @@
#pragma once
#include <blah/graphics/graphics.h>
namespace Blah
{
namespace Internal
{
class GraphicsDevice;
// graphics device metadata used to instantiate and destroy
// devices of specific apis
struct GraphicsDeviceInfo
{
GfxAPI api;
bool (*supported)();
GraphicsDevice* (*create)();
void (*destroy)(GraphicsDevice*);
};
// graphics implementations
extern GraphicsDeviceInfo OpenGL_DeviceInfo;
// graphics device
// each graphics implementation needs to implement this
// ex. one for opengl, one for vulkan, etc
class GraphicsDevice
{
public:
bool valid = false;
GraphicsInfo info;
virtual void startup() = 0;
virtual void update() = 0;
virtual void shutdown() = 0;
virtual void before_render() = 0;
virtual void after_render() = 0;
virtual TextureRef create_texture(int width, int height, TextureFilter filter, TextureWrap wrap_x, TextureWrap wrap_y, TextureFormat format) = 0;
virtual FrameBufferRef create_framebuffer(int width, int height, const TextureFormat* attachments, int attachmentCount) = 0;
virtual ShaderRef create_shader(const ShaderData* data) = 0;
virtual MeshRef create_mesh() = 0;
virtual void render(RenderCall* call) = 0;
virtual void clear(const FrameBufferRef& target, uint32_t rgba) = 0;
};
namespace Graphics
{
// Picks a graphics API based on the available APIs
GfxAPI pick_api();
// Initializes the Graphics with the given API
bool init(GfxAPI api);
// Is called internally by the Application every update
void frame();
// Is called internally by the Application before every render
void before_render();
// Is called internally by the Application after every render
void after_render();
// Called when the Application is shutting down
void shutdown();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,57 @@
#pragma once
#include <blah/input/input.h>
namespace Blah
{
namespace Internal
{
namespace Input
{
// This is called internally by the app, and initializes the input state
void init();
// This is called internally by the app, and updates the input state
void frame();
// Call this when the Mouse moves relative to the window
void on_mouse_move(float x, float y);
// Call this when the Mouse moves relative to the screen
void on_mouse_screen_move(float x, float y);
// Call this when a Mouse Button is pressed
void on_mouse_down(MouseButton button);
// Call this when a Mouse Button is released
void on_mouse_up(MouseButton button);
// Call this when the Mouse Wheel moves
void on_mouse_wheel(Point wheel);
// Call this when a keyboard key is pressed
void on_key_down(Key key);
// Call this when a keyboard key is released
void on_key_up(Key key);
// Call this on Text Input
void on_text_utf8(const char* text);
// Call this when a Controller is connected. Note that the Name parameter must be kept valid
// until on_controller_disconnect is called with the same index.
void on_controller_connect(int index, const char* name, int isGamepad, int buttonCount, int axisCount);
// Call this when a controller is disconnected
void on_controller_disconnect(int index);
// Call this when a controller button is pressed
void on_button_down(int index, int button);
// Call this when a controller button is released
void on_button_up(int index, int button);
/// Call this when a controller axis is moved
void on_axis_move(int index, int axis, float value);
}
}
}

View File

@ -0,0 +1,120 @@
#pragma once
#include <inttypes.h>
#include <blah/filesystem.h>
#include <blah/containers/list.h>
namespace Blah
{
struct Config;
enum class FileMode;
namespace Internal
{
namespace Platform
{
typedef void* FileHandle;
// Initialize the System
bool init(const Config* config);
// Called after the on_startup callback, but before the update loop begins
void ready();
// Called during shutdown
void shutdown();
// The time, in milliseconds, since the Application was started
uint64_t time();
// Called every frame
void frame();
// Sleeps the current thread
void sleep(int milliseconds);
// Called to present the window contents
void present();
// Gets the Application Window Title in UTF-8
const char* get_title();
// Sets the Application Window Title in UTF-8
void set_title(const char* title);
// Gets the Application Window Position, in Screen Coordinates
void get_position(int* x, int* y);
// Sets the Application Window Position, in Screen Coordinates
void set_position(int x, int y);
// Sets the Window Fullscreen if enabled is not 0
void set_fullscreen(bool enabled);
// Gets the Application Window Size, in Screen Coordinates
void get_size(int* width, int* height);
// Sets the Application Window Size, in Screen Coordinates
void set_size(int width, int height);
// Gets the Application Window Drawing Size, in Pixels. This may differ from the Window Size on hi-dpi displays.
void get_draw_size(int* width, int* height);
// Gets the Desktop Content Scale. Gui should be scaled by this value
float get_content_scale();
// Returns the absoluate path to the directory that the application was started from
const char* app_path();
// Returns the absolute path to the user directory where save data and settings should be stored
const char* user_path();
// Returns true if a file with the given path exists
bool file_exists(const char* path);
// Returns true if a file with the given path was deleted
bool file_delete(const char* path);
// Returns true if a directory with the given path was successfully created
bool dir_create(const char* path);
// Returns true if a directory with the given path exists
bool dir_exists(const char* path);
// Returns true if a directory with the given path was deleted
bool dir_delete(const char* path);
// enumerates a directory and appends each file to the given list
void dir_enumerate(List<FilePath>& list, const char* path, bool recursive);
// opens a directory in the OS file explorer / finder
void dir_explore(const char* path);
// Opens a file and sets the handle. returns true if the file was successfully opened
bool file_open(const char* path, FileHandle* handle, FileMode mode);
// Returns the length of the file
int64_t file_length(FileHandle file);
// Returns the Position of the file
int64_t file_position(FileHandle file);
// Seeks the Position of the file and returns the new position from the start of the file
int64_t file_seek(FileHandle file, int64_t seekTo);
// Reads a specific number of elements of a given size from the file into ptr
int64_t file_read(FileHandle file, void* ptr, int64_t size);
// Writes a specific number of elements of the given size from ptr to the file
int64_t file_write(FileHandle file, const void* ptr, int64_t size);
// Closes a file
void file_close(FileHandle file);
// OpenGL Methods
void* gl_get_func(const char* name);
void* gl_context_create();
void gl_context_make_current(void* context);
void gl_context_destroy(void* context);
}
}
}

View File

@ -0,0 +1,652 @@
#ifdef BLAH_USE_SDL2
#include <blah/internal/platform.h>
#include <blah/internal/input.h>
#include <blah/input/input.h>
#include <blah/app.h>
#include <blah/filesystem.h>
#include <blah/log.h>
#include <SDL.h>
#include <SDL_vulkan.h>
#if _WIN32
// on Windows we're using the C++ <filesystem> API for now
#include <windows.h>
#include <winuser.h> // for SetProcessDPIAware
#include <filesystem> // for File Reading/Writing
#include <shellapi.h> // for file explore
namespace fs = std::filesystem;
#else
// on non-Windows we use POSIX standard file system stuff
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#endif
using namespace Blah;
using namespace Internal;
namespace
{
SDL_Window* window = nullptr;
SDL_Joystick* joysticks[BLAH_MAX_CONTROLLERS];
SDL_GameController* gamepads[BLAH_MAX_CONTROLLERS];
char* basePath = nullptr;
char* userPath = nullptr;
bool displayed = false;
void sdl_log(void* userdata, int category, SDL_LogPriority priority, const char* message)
{
if (priority <= SDL_LOG_PRIORITY_INFO)
Log::print(message);
else if (priority <= SDL_LOG_PRIORITY_WARN)
Log::warn(message);
else
Log::error(message);
}
}
bool Platform::init(const Config* config)
{
// Required to call this for Windows
// I'm not sure why SDL2 doesn't do this on Windows automatically?
#if _WIN32
SetProcessDPIAware();
#endif
// TODO:
// control this via some kind of config flag
SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
SDL_LogSetOutputFunction(sdl_log, nullptr);
// Get SDL version
SDL_version version;
SDL_GetVersion(&version);
Log::print("SDL v%i.%i.%i", version.major, version.minor, version.patch);
// initialize SDL
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_EVENTS | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
{
Log::error("Failed to initialize SDL2");
return false;
}
// GL Attributes
if (config->graphics == GfxAPI::OpenGL)
{
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
// TODO:
// This should be controlled via the gfx api somehow?
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4);
}
// set up window flags
int flags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
if (config->graphics == GfxAPI::OpenGL)
flags |= SDL_WINDOW_OPENGL;
// create the window
window = SDL_CreateWindow(config->name, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, config->width, config->height, flags);
if (window == nullptr)
{
Log::error("Failed to create a Window");
return false;
}
// Scale Window to monitor for High DPI displays
// Other platforms do this automatically ... Windows we need to explitely do so
#if _WIN32
{
// find the display index
int display = SDL_GetWindowDisplayIndex(window);
float ddpi, hdpi, vdpi;
if (SDL_GetDisplayDPI(display, &ddpi, &hdpi, &vdpi) == 0)
{
// scale the window up basesd on the display DPI
float hidpiRes = 96;
float dpi = (ddpi / hidpiRes);
if (dpi != 1)
{
SDL_DisplayMode mode;
SDL_GetDesktopDisplayMode(display, &mode);
SDL_SetWindowPosition(window, (int)(mode.w - config->width * dpi) / 2, (int)(mode.h - config->height * dpi) / 2);
SDL_SetWindowSize(window, (int)(config->width * dpi), (int)(config->height * dpi));
}
}
}
#endif
// set window properties
SDL_SetWindowResizable(window, SDL_TRUE);
SDL_SetWindowMinimumSize(window, 256, 256);
return true;
}
void Platform::ready()
{
// enable V-Sync
if (App::config()->graphics == GfxAPI::OpenGL)
SDL_GL_SetSwapInterval(1);
}
void Platform::shutdown()
{
if (window != nullptr)
SDL_DestroyWindow(window);
window = nullptr;
displayed = false;
if (basePath != nullptr)
SDL_free(basePath);
if (userPath != nullptr)
SDL_free(userPath);
SDL_Quit();
}
uint64_t Platform::time()
{
return (uint64_t)SDL_GetTicks();
}
void Platform::frame()
{
// update the mouse every frame
{
int winX, winY, x, y;
SDL_GetWindowPosition(window, &winX, &winY);
SDL_GetGlobalMouseState(&x, &y);
Internal::Input::on_mouse_move((float)(x - winX), (float)(y - winY));
Internal::Input::on_mouse_screen_move((float)x, (float)y);
}
// poll normal events
SDL_Event event;
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
{
auto config = App::config();
if (config->on_exit_request != nullptr)
config->on_exit_request();
}
// Mouse
else if (event.type == SDL_MOUSEBUTTONDOWN)
{
MouseButton btn = MouseButton::None;
if (event.button.button == SDL_BUTTON_LEFT)
btn = MouseButton::Left;
else if (event.button.button == SDL_BUTTON_RIGHT)
btn = MouseButton::Right;
else if (event.button.button == SDL_BUTTON_MIDDLE)
btn = MouseButton::Middle;
Internal::Input::on_mouse_down(btn);
}
else if (event.type == SDL_MOUSEBUTTONUP)
{
MouseButton btn = MouseButton::None;
if (event.button.button == SDL_BUTTON_LEFT)
btn = MouseButton::Left;
else if (event.button.button == SDL_BUTTON_RIGHT)
btn = MouseButton::Right;
else if (event.button.button == SDL_BUTTON_MIDDLE)
btn = MouseButton::Middle;
Internal::Input::on_mouse_up(btn);
}
else if (event.type == SDL_MOUSEWHEEL)
{
Internal::Input::on_mouse_wheel(Point(event.wheel.x, event.wheel.y));
}
// Keyboard
else if (event.type == SDL_KEYDOWN)
{
if (event.key.repeat == 0)
Internal::Input::on_key_down((Key)event.key.keysym.scancode);
}
else if (event.type == SDL_KEYUP)
{
if (event.key.repeat == 0)
Internal::Input::on_key_up((Key)event.key.keysym.scancode);
}
else if (event.type == SDL_TEXTINPUT)
{
Internal::Input::on_text_utf8(event.text.text);
}
// Joystick Controller
else if (event.type == SDL_JOYDEVICEADDED)
{
Sint32 index = event.jdevice.which;
if (SDL_IsGameController(index) == SDL_FALSE)
{
SDL_Joystick* ptr = joysticks[index] = SDL_JoystickOpen(index);
const char* name = SDL_JoystickName(ptr);
int button_count = SDL_JoystickNumButtons(ptr);
int axis_count = SDL_JoystickNumAxes(ptr);
Internal::Input::on_controller_connect(index, name, 0, button_count, axis_count);
}
}
else if (event.type == SDL_JOYDEVICEREMOVED)
{
Sint32 index = event.jdevice.which;
if (SDL_IsGameController(index) == SDL_FALSE)
{
Internal::Input::on_controller_disconnect(index);
SDL_JoystickClose(joysticks[index]);
}
}
else if (event.type == SDL_JOYBUTTONDOWN)
{
Sint32 index = event.jdevice.which;
if (SDL_IsGameController(index) == SDL_FALSE)
Internal::Input::on_button_down(index, event.jbutton.button);
}
else if (event.type == SDL_JOYBUTTONUP)
{
Sint32 index = event.jdevice.which;
if (SDL_IsGameController(index) == SDL_FALSE)
Internal::Input::on_button_up(index, event.jbutton.button);
}
else if (event.type == SDL_JOYAXISMOTION)
{
Sint32 index = event.jaxis.which;
if (SDL_IsGameController(index) == SDL_FALSE)
{
float value;
if (event.jaxis.value >= 0)
value = event.jaxis.value / 32767.0f;
else
value = event.jaxis.value / 32768.0f;
Internal::Input::on_axis_move(index, event.jaxis.axis, value);
}
}
// Gamepad Controller
else if (event.type == SDL_CONTROLLERDEVICEADDED)
{
Sint32 index = event.cdevice.which;
SDL_GameController* ptr = gamepads[index] = SDL_GameControllerOpen(index);
const char* name = SDL_GameControllerName(ptr);
Internal::Input::on_controller_connect(index, name, 1, 15, 6);
}
else if (event.type == SDL_CONTROLLERDEVICEREMOVED)
{
Sint32 index = event.cdevice.which;
Internal::Input::on_controller_disconnect(index);
SDL_GameControllerClose(gamepads[index]);
}
else if (event.type == SDL_CONTROLLERBUTTONDOWN)
{
Sint32 index = event.cbutton.which;
int button = (int)Button::None;
if (event.cbutton.button >= 0 && event.cbutton.button < 15)
button = event.cbutton.button; // NOTE: These map directly to Engine Buttons enum!
Internal::Input::on_button_down(index, button);
}
else if (event.type == SDL_CONTROLLERBUTTONUP)
{
Sint32 index = event.cbutton.which;
int button = (int)Button::None;
if (event.cbutton.button >= 0 && event.cbutton.button < 15)
button = event.cbutton.button; // NOTE: These map directly to Engine Buttons enum!
Internal::Input::on_button_up(index, button);
}
else if (event.type == SDL_CONTROLLERAXISMOTION)
{
Sint32 index = event.caxis.which;
int axis = (int)Axis::None;
if (event.caxis.axis >= 0 && event.caxis.axis < 6)
axis = event.caxis.axis; // NOTE: These map directly to Engine Axis enum!
float value;
if (event.caxis.value >= 0)
value = event.caxis.value / 32767.0f;
else
value = event.caxis.value / 32768.0f;
Internal::Input::on_axis_move(index, axis, value);
}
}
}
void Platform::sleep(int milliseconds)
{
if (milliseconds >= 0)
SDL_Delay((uint32_t)milliseconds);
}
void Platform::present()
{
if (App::config()->graphics == GfxAPI::OpenGL)
{
SDL_GL_SwapWindow(window);
}
// display the window
// this avoids a short black screen on macoS
if (!displayed)
{
SDL_ShowWindow(window);
displayed = true;
}
}
const char* Platform::get_title()
{
return nullptr;
}
void Platform::set_title(const char* title)
{
SDL_SetWindowTitle(window, title);
}
void Platform::get_position(int* x, int* y)
{
SDL_GetWindowPosition(window, x, y);
}
void Platform::set_position(int x, int y)
{
SDL_SetWindowPosition(window, x, y);
}
void Platform::set_fullscreen(bool enabled)
{
if (enabled)
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
else
SDL_SetWindowFullscreen(window, 0);
}
void Platform::get_size(int* width, int* height)
{
SDL_GetWindowSize(window, width, height);
}
void Platform::set_size(int width, int height)
{
SDL_SetWindowSize(window, width, height);
}
void Platform::get_draw_size(int* width, int* height)
{
auto config = App::config();
if (config->graphics == GfxAPI::OpenGL)
{
SDL_GL_GetDrawableSize(window, width, height);
}
/*
else if (config->graphics == GfxAPI::Vulkan)
{
SDL_Vulkan_GetDrawableSize(window, width, height);
}
*/
else
{
SDL_GetWindowSize(window, width, height);
}
}
float Platform::get_content_scale()
{
// TODO:
// This is incorrect! but for some reason the scale
// is HUGE if I use the Display DPI on macOS :/
#if __APPLE__
return 2.0f;
#endif
#if _WIN32
float hidpiRes = 96;
#else
float hidpiRes = 72;
#endif
int index = SDL_GetWindowDisplayIndex(window);
if (index < 0)
Log::error(SDL_GetError());
float ddpi, x, y;
if (SDL_GetDisplayDPI(index, &ddpi, &x, &y) != 0)
Log::error(SDL_GetError());
return (ddpi / hidpiRes);
}
// FILE IO
const char* Platform::app_path()
{
if (basePath == nullptr)
basePath = SDL_GetBasePath();
return basePath;
}
const char* Platform::user_path()
{
if (userPath == nullptr)
{
const Config* config = App::config();
userPath = SDL_GetPrefPath(nullptr, config->name);
}
return userPath;
}
// Windows File System methods
#if _WIN32
bool Platform::file_exists(const char* path)
{
return fs::is_regular_file(path);
}
bool Platform::file_delete(const char* path)
{
return fs::remove(path);
}
bool Platform::dir_create(const char* path)
{
std::error_code error;
return fs::create_directories(path, error);
}
bool Platform::dir_exists(const char* path)
{
return fs::is_directory(path);
}
bool Platform::dir_delete(const char* path)
{
BLAH_ERROR("not implemented");
return false;
}
void Platform::dir_enumerate(List<FilePath>& list, const char* path, bool recursive)
{
if (fs::is_directory(path))
{
if (recursive)
{
for (auto& p : fs::recursive_directory_iterator(path))
{
FilePath str(p.path().string().c_str());
list.add(str);
}
}
else
{
for (auto& p : fs::directory_iterator(path))
{
FilePath str(p.path().string().c_str());
list.add(str);
}
}
}
}
void Platform::dir_explore(const char* path)
{
ShellExecute(NULL, "open", path, NULL, NULL, SW_SHOWDEFAULT);
}
// Non-Windows File System Methods
#else
bool Platform::file_exists(const char* path)
{
struct stat buffer;
return (stat(path, &buffer) == 0) && S_ISREG(buffer.st_mode);
}
bool Platform::file_delete(const char* path)
{
BLAH_ERROR("not implemented");
return false;
}
bool Platform::dir_create(const char* path)
{
char tmp[265];
char* p = NULL;
size_t len;
snprintf(tmp, sizeof(tmp), "%s", path);
len = strlen(tmp);
if (tmp[len - 1] == '/')
tmp[len - 1] = 0;
for (p = tmp + 1; *p; p++)
if (*p == '/') {
*p = 0;
mkdir(tmp, S_IRWXU);
*p = '/';
}
return mkdir(tmp, S_IRWXU) == 0;
}
bool Platform::dir_exists(const char* path)
{
struct stat buffer;
return (stat(path, &buffer) == 0) && S_ISDIR(buffer.st_mode);
}
bool Platform::dir_delete(const char* path)
{
BLAH_ERROR("not implemented");
return false;
}
void Platform::dir_enumerate(List<FilePath>& list, const char* path, bool recursive)
{
DIR* dirp = opendir(path);
if (dirp != NULL)
{
struct dirent* dp;
while ((dp = readdir(dirp)) != NULL)
{
if (dp->d_name[0] == '.')
continue;
FilePath subpath = FilePath(path).Append(dp->d_name);
list.Add(subpath);
if (recursive && dp->d_type == DT_DIR)
dir_enumerate(list, subpath + "/", true);
}
closedir(dirp);
}
}
void Platform::dir_explore(const char* path)
{
BLAH_ERROR("not implemented");
}
#endif
bool Platform::file_open(const char* path, Platform::FileHandle* handle, FileMode mode)
{
const char* sdlMode = "rb";
if (mode == FileMode::Write)
sdlMode = "wb";
auto ptr = SDL_RWFromFile(path, sdlMode);
*handle = (Platform::FileHandle)ptr;
return ptr != nullptr;
}
int64_t Platform::file_length(Platform::FileHandle stream)
{
return SDL_RWsize((SDL_RWops*)stream);
}
int64_t Platform::file_position(Platform::FileHandle stream)
{
return SDL_RWtell((SDL_RWops*)stream);
}
int64_t Platform::file_seek(Platform::FileHandle stream, int64_t seekTo)
{
return SDL_RWseek((SDL_RWops*)stream, seekTo, RW_SEEK_SET);
}
int64_t Platform::file_read(Platform::FileHandle stream, void* ptr, int64_t length)
{
return SDL_RWread((SDL_RWops*)stream, ptr, sizeof(char), length);
}
int64_t Platform::file_write(Platform::FileHandle stream, const void* ptr, int64_t length)
{
return SDL_RWwrite((SDL_RWops*)stream, ptr, sizeof(char), length);
}
void Platform::file_close(Platform::FileHandle stream)
{
if (stream != nullptr)
SDL_RWclose((SDL_RWops*)stream);
}
void* Platform::gl_get_func(const char* name)
{
return SDL_GL_GetProcAddress(name);
}
void* Platform::gl_context_create()
{
void* pointer = SDL_GL_CreateContext(window);
if (pointer == nullptr)
Log::error("SDL_GL_CreateContext failed: %s", SDL_GetError());
return pointer;
}
void Platform::gl_context_make_current(void* context)
{
SDL_GL_MakeCurrent(window, context);
}
void Platform::gl_context_destroy(void* context)
{
SDL_GL_DeleteContext(context);
}
#endif // BLAH_USE_SDL2

7656
private/blah/third_party/stb_image.h vendored Normal file

File diff suppressed because it is too large Load Diff

1666
private/blah/third_party/stb_image_write.h vendored Normal file

File diff suppressed because it is too large Load Diff

5011
private/blah/third_party/stb_truetype.h vendored Normal file

File diff suppressed because it is too large Load Diff

52
public/blah.h Normal file
View File

@ -0,0 +1,52 @@
#pragma once
#include <blah.h>
#include <blah/app.h>
#include <blah/filesystem.h>
#include <blah/log.h>
#include <blah/time.h>
#include <blah/containers/list.h>
#include <blah/containers/stacklist.h>
#include <blah/containers/str.h>
#include <blah/drawing/batch.h>
#include <blah/drawing/spritefont.h>
#include <blah/drawing/subtexture.h>
#include <blah/graphics/framebuffer.h>
#include <blah/graphics/graphics.h>
#include <blah/graphics/material.h>
#include <blah/graphics/mesh.h>
#include <blah/graphics/shader.h>
#include <blah/graphics/texture.h>
#include <blah/images/aseprite.h>
#include <blah/images/font.h>
#include <blah/images/image.h>
#include <blah/images/packer.h>
#include <blah/input/input.h>
#include <blah/input/virtual_stick.h>
#include <blah/input/virtual_button.h>
#include <blah/input/virtual_axis.h>
#include <blah/math/calc.h>
#include <blah/math/circle.h>
#include <blah/math/color.h>
#include <blah/math/ease.h>
#include <blah/math/line.h>
#include <blah/math/mat3x2.h>
#include <blah/math/mat4x4.h>
#include <blah/math/point.h>
#include <blah/math/quad.h>
#include <blah/math/rect.h>
#include <blah/math/rectI.h>
#include <blah/math/stopwatch.h>
#include <blah/math/vec2.h>
#include <blah/math/vec4.h>
#include <blah/streams/bufferstream.h>
#include <blah/streams/filestream.h>
#include <blah/streams/memorystream.h>
#include <blah/streams/stream.h>

238
public/blah/app.cpp Normal file
View File

@ -0,0 +1,238 @@
#include <blah/app.h>
#include <blah/log.h>
#include <blah/time.h>
#include <blah/math/point.h>
#include <blah/internal/platform.h>
#include <blah/internal/graphics.h>
#include <blah/internal/input.h>
using namespace Blah;
namespace
{
static Config app_config;
static bool app_is_running = false;
static bool app_is_exiting = false;
}
Config::Config()
{
name = nullptr;
width = 0;
height = 0;
target_framerate = 60;
max_updates = 5;
graphics = GfxAPI::Any;
on_startup = nullptr;
on_shutdown = nullptr;
on_update = nullptr;
on_render = nullptr;
on_exit_request = App::exit;
on_info = nullptr;
on_warn = nullptr;
on_error = nullptr;
}
bool App::run(const Config* c)
{
BLAH_ASSERT(!app_is_running, "The Application is already running");
BLAH_ASSERT(c != nullptr, "The Application requires a valid Config");
BLAH_ASSERT(c->name != nullptr, "The Application Name cannot be null");
BLAH_ASSERT(c->width > 0 && c->height > 0, "The Width and Height must be larget than 0");
BLAH_ASSERT(c->max_updates > 0, "Max Updates must be >= 1");
BLAH_ASSERT(c->target_framerate > 0, "Target Framerate must be >= 1");
app_config = *c;
app_is_running = true;
app_is_exiting = false;
Log::print("Starting Up ...");
// figure out the graphics api
if (app_config.graphics == GfxAPI::Any)
{
app_config.graphics = Internal::Graphics::pick_api();
if (app_config.graphics == GfxAPI::Any)
{
Log::error("Failed to find a supported graphics api");
return false;
}
}
// initialize the system
if (!Internal::Platform::init(&app_config))
{
Log::error("Failed to initialize system module");
return false;
}
// initialize graphics
if (!Internal::Graphics::init(app_config.graphics))
{
Log::error("Failed to initialize graphics module");
return false;
}
// input
Internal::Input::init();
// startup
if (app_config.on_startup != nullptr)
app_config.on_startup();
uint64_t time_last = Internal::Platform::time();
uint64_t time_accumulator = 0;
// display window
Internal::Platform::ready();
while (!app_is_exiting)
{
// poll system events
Internal::Platform::frame();
// update at a fixed timerate
{
uint64_t time_target = (uint64_t)((1.0f / app_config.target_framerate) * 1000);
uint64_t time_curr = Internal::Platform::time();
uint64_t time_diff = time_curr - time_last;
time_last = time_curr;
time_accumulator += time_diff;
// do not let us run too fast
while (time_accumulator < time_target)
{
Internal::Platform::sleep((int)(time_target - time_accumulator));
time_curr = Internal::Platform::time();
time_diff = time_curr - time_last;
time_last = time_curr;
time_accumulator += time_diff;
}
// Do not allow us to fall behind too many updates
// (otherwise we'll get spiral of death)
uint64_t time_maximum = app_config.max_updates * time_target;
if (time_accumulator > time_maximum)
time_accumulator = time_maximum;
// do as many updates as we can
while (time_accumulator >= time_target)
{
time_accumulator -= time_target;
Time::milliseconds += time_target;
Time::delta = 1.0f / app_config.target_framerate;
Time::previous_elapsed = Time::elapsed;
Time::elapsed += Time::delta;
Internal::Input::frame();
Internal::Graphics::frame();
Time::pause_timer -= Time::delta;
if (Time::pause_timer <= 0)
{
if (app_config.on_update != nullptr)
app_config.on_update();
}
}
}
// render
{
Internal::Graphics::before_render();
if (app_config.on_render != nullptr)
app_config.on_render();
Internal::Graphics::after_render();
Internal::Platform::present();
}
}
Log::print("Shutting down ...");
// shutdown
if (app_config.on_shutdown != nullptr)
app_config.on_shutdown();
Internal::Graphics::shutdown();
Internal::Platform::shutdown();
// clear static state
Log::print("Exited");
app_is_running = false;
app_is_exiting = false;
Time::milliseconds = 0;
Time::elapsed = 0;
Time::delta = 0;
return true;
}
bool App::is_running()
{
return app_is_running;
}
void App::exit()
{
if (!app_is_exiting && app_is_running)
app_is_exiting = true;
}
const Config* App::config()
{
return &app_config;
}
const char* App::path()
{
return Internal::Platform::app_path();
}
const char* App::user_path()
{
return Internal::Platform::user_path();
}
int App::width()
{
int w, h;
Internal::Platform::get_size(&w, &h);
return w;
}
int App::height()
{
int w, h;
Internal::Platform::get_size(&w, &h);
return h;
}
int App::draw_width()
{
int w, h;
Internal::Platform::get_draw_size(&w, &h);
return w;
}
int App::draw_height()
{
int w, h;
Internal::Platform::get_draw_size(&w, &h);
return h;
}
float App::content_scale()
{
return Internal::Platform::get_content_scale();
}
void App::fullscreen(bool enabled)
{
Internal::Platform::set_fullscreen(enabled);
}

73
public/blah/app.h Normal file
View File

@ -0,0 +1,73 @@
#pragma once
namespace Blah
{
enum class GfxAPI
{
Any = -1,
OpenGL,
Count
};
struct Config
{
const char* name;
int width;
int height;
int max_updates;
int target_framerate;
GfxAPI graphics;
void (*on_startup)();
void (*on_shutdown)();
void (*on_update)();
void (*on_render)();
void (*on_exit_request)();
void (*on_info)(const char* text);
void (*on_warn)(const char* text);
void (*on_error)(const char* text);
Config();
};
namespace App
{
// Runs the application
bool run(const Config* config);
// Returns whether the application is running
bool is_running();
// Exits the application
void exit();
// Gets the config data used to run the application
const Config* config();
// Gets the working path
const char* path();
// Gets the user path
const char* user_path();
// Gets the width of the window
int width();
// Gets the height of the window
int height();
// Gets the drawable width of the window
int draw_width();
// Gets the drawable height of the window
int draw_height();
// Gets the content scale based on the OS
float content_scale();
// Toggles fullscreen
void fullscreen(bool enabled);
}
}

View File

@ -0,0 +1,314 @@
#pragma once
#include <blah/log.h>
#include <type_traits>
#include <new>
#include <string.h>
namespace Blah
{
template<class T>
class List
{
private:
// internal list buffer
T* m_buffer;
// total elements
int m_count;
// total capacity
int m_capacity;
public:
List();
List(int capacity);
List(const List& src);
List(List&& src) noexcept;
~List();
List& operator=(const List& src);
List& operator=(List&& src) noexcept;
// reserves the given amount of capacity
void reserve(int new_capacity);
// adds an element to the list
void add(const T& item);
template<class ...Args>
void emplace(Args&&...args);
// moves an element into the list
void add(T&& item);
// expands the list by the given amount, and returns a pointer to the first element
T* expand(int amount = 1);
// returns a reference to the element at the given index
T& operator[](int index);
// returns a reference to the element at the given index
const T& operator[](int index) const;
// returns a pointer to the first element
T* begin() { return m_buffer; }
// returns a pointer to the first element
const T* begin() const { return m_buffer; }
// returns a pointer to the last element
T* end() { return m_buffer + m_count; }
// returns a pointer to the last element
const T* end() const { return m_buffer + m_count; }
// clears all elements
void clear();
// removes the element at the index
void remove_at(int index);
// checks whether the given value is in the List (uses == operator)
bool contains(const T& item) const;
// returns the index of the given value in the list (uses == operator, -1 if not in list)
int index_of(const T& item) const;
// pops the top element off the list
T pop();
// returns the total number of elements
int count() const { return m_count; }
// returns the internal buffer capacity of the list
int capacity() const { return m_capacity; }
// removes all elements and disposes the internal buffer
void dispose();
};
template<class T>
List<T>::List()
{
m_buffer = nullptr;
m_count = m_capacity = 0;
}
template<class T>
List<T>::List(int capacity)
{
m_buffer = nullptr;
m_count = m_capacity = 0;
Reserve(m_capacity);
}
template<class T>
List<T>::List(const List<T>& src)
{
m_buffer = (T*)(::operator new (sizeof(T) * src.m_capacity));
m_count = src.m_count;
m_capacity = src.m_capacity;
for (int n = 0; n < m_count; n++)
new (m_buffer + n) T(*(src.m_buffer + n));
}
template<class T>
List<T>::List(List<T>&& src) noexcept
{
m_buffer = src.m_buffer;
m_count = src.m_count;
m_capacity = src.m_capacity;
src.m_buffer = nullptr;
src.m_count = src.m_capacity = 0;
}
template<class T>
List<T>& List<T>::operator=(const List<T>& src)
{
clear();
if (m_capacity < src.m_count)
reserve(src.m_count);
m_count = src.m_count;
for (int n = 0; n < m_count; n++)
new (m_buffer + n) T(*(src.m_buffer + n));
return *this;
}
template<class T>
List<T>& List<T>::operator=(List<T>&& src) noexcept
{
dispose();
m_buffer = src.m_buffer;
m_count = src.m_count;
m_capacity = src.m_capacity;
src.m_buffer = nullptr;
src.m_count = src.m_capacity = 0;
return *this;
}
template<class T>
List<T>::~List()
{
dispose();
}
template<class T>
void List<T>::reserve(int new_capacity)
{
if (new_capacity >= m_capacity)
{
auto last_capacity = m_capacity;
if (m_capacity <= 0)
m_capacity = 8;
while (new_capacity >= m_capacity)
m_capacity = m_capacity * 2;
T* new_buffer = (T*)::operator new (sizeof(T) * m_capacity);
if (std::is_trivially_copyable<T>::value)
{
memcpy(new_buffer, m_buffer, sizeof(T) * m_count);
}
else
{
for (int n = 0; n < m_count; n++)
new (new_buffer + n) T(std::move(m_buffer[n]));
}
::operator delete (m_buffer, sizeof(T) * last_capacity);
m_buffer = new_buffer;
}
}
template<class T>
T* List<T>::expand(int amount)
{
reserve(m_count + amount);
for (int n = m_count; n < m_count + amount; n++)
new (m_buffer + n) T();
m_count += amount;
return (m_buffer + m_count - amount);
}
template<class T>
void List<T>::add(const T& item)
{
reserve(m_count + 1);
new (m_buffer + m_count) T(item);
m_count++;
}
template<class T>
template<class ...Args>
void List<T>::emplace(Args&&...args)
{
reserve(m_count + 1);
new (m_buffer + m_count) T(args);
m_count++;
}
template<class T>
void List<T>::add(T&& item)
{
reserve(m_count + 1);
new (m_buffer + m_count) T(std::move(item));
m_count++;
}
template<class T>
T& List<T>::operator[](int index)
{
BLAH_ASSERT(index >= 0 && index < m_count, "Index is out of range");
return m_buffer[index];
}
template<class T>
const T& List<T>::operator[](int index) const
{
BLAH_ASSERT(index >= 0 && index < m_count, "Index is out of range");
return m_buffer[index];
}
template<class T>
void List<T>::clear()
{
for (T* it = m_buffer; it < m_buffer + m_count; it++)
it->~T();
m_count = 0;
}
template<class T>
void List<T>::remove_at(int index)
{
BLAH_ASSERT(index >= 0 && index < m_count, "Index is out of range");
m_buffer[index].~T();
if (index < m_count - 1)
{
int diff = (m_count - index - 1);
if (diff <= 0)
diff = 0;
if (std::is_trivially_copyable<T>::value)
{
memmove(m_buffer + index, m_buffer + index + 1, (size_t)diff * sizeof(T));
}
else
{
for (int i = index; i < m_count - 1; i++)
m_buffer[i] = std::move(m_buffer[i + 1]);
m_buffer[m_count - 1].~T();
}
}
m_count--;
}
template<class T>
inline bool List<T>::contains(const T& item) const
{
for (int i = 0; i < m_count; i++)
if (*(m_buffer + i) == item)
return true;
return false;
}
template<class T>
inline int List<T>::index_of(const T& item) const
{
for (int i = 0; i < m_count; i++)
if (*(m_buffer + i) == item)
return i;
return -1;
}
template<class T>
T List<T>::pop()
{
BLAH_ASSERT(m_count > 0, "There are no elements to pop");
m_count--;
T item = m_buffer[m_count];
m_buffer[m_count].~T();
return item;
}
template<class T>
void List<T>::dispose()
{
clear();
::operator delete (m_buffer, sizeof(T) * m_capacity);
m_buffer = nullptr;
m_count = 0;
m_capacity = 0;
}
}

View File

@ -0,0 +1,212 @@
#pragma once
#include <blah/log.h>
namespace Blah
{
template<class T, int Size>
class StackList
{
private:
// internal list buffer
T m_buffer[Size];
// total elements
int m_count;
public:
StackList();
StackList(const StackList& src);
StackList(StackList&& src) noexcept;
StackList& operator=(const StackList& src);
StackList& operator=(StackList&& src) noexcept;
// adds an element to the list
void add(const T& item);
// moves an element into the list
void add(T&& item);
// expands the list by the given amount, and returns a pointer to the first element
T* expand(int amount = 1);
// returns a reference to the element at the given index
T& operator[](int index);
// returns a reference to the element at the given index
const T& operator[](int index) const;
// returns a pointer to the first element
T* begin() { return m_buffer; }
// returns a pointer to the first element
const T* begin() const { return m_buffer; }
// returns a pointer to the last element
T* end() { return m_buffer + m_count; }
// returns a pointer to the last element
const T* end() const { return m_buffer + m_count; }
// clears all elements
void clear();
// removes the element at the index
void remove_at(int index);
// checks whether the given value is in the StackList (uses == operator)
bool contains(const T& item) const;
// returns the index of the given value in the list (uses == operator, -1 if not in list)
int index_of(const T& item) const;
// pops the top element off the list
T& pop();
// returns the total number of elements
int count() const { return m_count; }
// returns the internal buffer capacity of the list
int capacity() const { return Size; }
};
template<class T, int Size>
StackList<T, Size>::StackList()
{
m_count = 0;
}
template<class T, int Size>
StackList<T, Size>::StackList(const StackList<T, Size>& src)
{
m_count = src.m_count;
for (int n = 0; n < m_count; n++)
m_buffer[n] = src.m_buffer[n];
}
template<class T, int Size>
StackList<T, Size>::StackList(StackList<T, Size>&& src) noexcept
{
m_count = src.m_count;
for (int n = 0; n < m_count; n++)
m_buffer[n] = std::move(src.m_buffer[n]);
src.m_count = 0;
}
template<class T, int Size>
StackList<T, Size>& StackList<T, Size>::operator=(const StackList<T, Size>& src)
{
clear();
m_count = src.m_count;
for (int n = 0; n < m_count; n++)
m_buffer[n] = src.m_buffer[n];
return *this;
}
template<class T, int Size>
StackList<T, Size>& StackList<T, Size>::operator=(StackList<T, Size>&& src) noexcept
{
clear();
m_count = src.m_count;
for (int n = 0; n < m_count; n++)
m_buffer[n] = std::move(src.m_buffer[n]);
src.m_count = 0;
return *this;
}
template<class T, int Size>
T* StackList<T, Size>::expand(int amount)
{
BLAH_ASSERT(m_count + amount < Size, "Exceeded StackList capacity");
for (int n = m_count; n < m_count + amount; n++)
m_buffer[n] = T();
m_count += amount;
return (m_buffer + m_count - amount);
}
template<class T, int Size>
void StackList<T, Size>::add(const T& item)
{
BLAH_ASSERT(m_count < Size, "Exceeded StackList capacity");
m_buffer[m_count] = item;
m_count++;
}
template<class T, int Size>
void StackList<T, Size>::add(T&& item)
{
BLAH_ASSERT(m_count < Size, "Exceeded StackList capacity");
m_buffer[m_count] = std::move(item);
m_count++;
}
template<class T, int Size>
T& StackList<T, Size>::operator[](int index)
{
BLAH_ASSERT(index >= 0 && index < m_count, "Index is out of range");
return m_buffer[index];
}
template<class T, int Size>
const T& StackList<T, Size>::operator[](int index) const
{
BLAH_ASSERT(index >= 0 && index < m_count, "Index is out of range");
return m_buffer[index];
}
template<class T, int Size>
void StackList<T, Size>::clear()
{
m_count = 0;
}
template<class T, int Size>
void StackList<T, Size>::remove_at(int index)
{
BLAH_ASSERT(index >= 0 && index < m_count, "Index is out of range");
if (index < m_count - 1)
{
for (int n = index; n < m_count - 1; n++)
m_buffer[n] = m_buffer[n + 1];
m_buffer[m_count] = T();
}
m_count--;
}
template<class T, int Size>
bool StackList<T, Size>::contains(const T& item) const
{
for (int i = 0; i < m_count; i++)
if (*(m_buffer + i) == item)
return true;
return false;
}
template<class T, int Size>
int StackList<T, Size>::index_of(const T& item) const
{
for (int i = 0; i < m_count; i++)
if (*(m_buffer + i) == item)
return i;
return -1;
}
template<class T, int Size>
T& StackList<T, Size>::pop()
{
BLAH_ASSERT(m_count > 0, "There are no elements to pop");
m_count--;
return m_buffer[m_count];
}
}

View File

@ -0,0 +1,385 @@
#include <blah/containers/str.h>
#include <string.h> // for strcpy etc
#include <stdarg.h> // for format methods
#include <stdio.h> // for sprintf
#include <cctype> // toupper
using namespace Blah;
const uint16_t UTF8_LEAD_SURROGATE_MIN = 0xd800u;
const uint16_t UTF8_LEAD_SURROGATE_MAX = 0xdbffu;
const uint16_t UTF8_TRAIL_SURROGATE_MIN = 0xdc00u;
const uint16_t UTF8_TRAIL_SURROGATE_MAX = 0xdfffu;
const uint16_t UTF8_LEAD_OFFSET = UTF8_LEAD_SURROGATE_MIN - (0x10000 >> 10);
const uint32_t UTF8_SURROGATE_OFFSET = 0x10000u - (UTF8_LEAD_SURROGATE_MIN << 10) - UTF8_TRAIL_SURROGATE_MIN;
char Str::empty_buffer[1] = { '\0' };
bool Str::operator==(const Str& rhs) const
{
return strcmp(cstr(), rhs.cstr()) == 0;
}
bool Str::operator!=(const Str& rhs) const
{
return strcmp(cstr(), rhs.cstr()) != 0;
}
bool Str::operator==(const char* rhs) const
{
return strcmp(cstr(), rhs) == 0;
}
bool Str::operator!=(const char* rhs) const
{
return strcmp(cstr(), rhs) != 0;
}
void Str::reserve(int size)
{
int buffer_length = size + 1;
if (buffer_length > m_capacity)
{
if (m_capacity <= 0)
m_capacity = 16;
while (m_capacity < buffer_length)
m_capacity *= 2;
// expand from local buffer
if (m_buffer == nullptr)
{
char* local = data();
m_buffer = new char[m_capacity];
strncpy(m_buffer, local, m_local_size);
m_buffer[m_local_size] = '\0';
}
// expand from empty buffer
else if (m_buffer == empty_buffer)
{
m_buffer = new char[m_capacity];
m_buffer[0] = '\0';
}
// expand from existing heap buffer
else
{
char* new_buffer = new char[m_capacity];
memcpy(new_buffer, m_buffer, m_length);
new_buffer[m_length] = '\0';
delete[] m_buffer;
m_buffer = new_buffer;
}
}
}
void Str::set_length(int len)
{
reserve(len);
data()[len] = '\0';
m_length = len;
}
Str& Str::append(char c)
{
reserve(m_length + 1);
data()[m_length++] = c;
data()[m_length] = '\0';
return *this;
}
Str& Str::append(uint32_t c)
{
// one octet
if (c < 0x80)
{
append((char)c);
}
// two octets
else if (c < 0x800)
{
append((char)((c >> 6) | 0xc0));
append((char)((c & 0x3f) | 0x80));
}
// three octets
else if (c < 0x10000)
{
append((char)((c >> 12) | 0xe0));
append((char)(((c >> 6) & 0x3f) | 0x80));
append((char)((c & 0x3f) | 0x80));
}
// four octets
else
{
append((char)((c >> 18) | 0xf0));
append((char)(((c >> 12) & 0x3f) | 0x80));
append((char)(((c >> 6) & 0x3f) | 0x80));
append((char)((c & 0x3f) | 0x80));
}
return *this;
}
Str& Str::append(const char* start, const char* end)
{
if (end == nullptr)
end = start + strlen(start);
int add = (int)(end - start);
if (add <= 0)
return *this;
reserve(m_length + add);
memcpy(data() + m_length, start, add);
m_length += add;
data()[m_length] = '\0';
return *this;
}
Str& Str::append(const Str& str, int start, int end)
{
if (end < 0) end = str.m_length;
if (end > str.m_length) end = str.m_length;
if (start < 0) start = 0;
if (start > end) start = end;
return append(str.begin() + start, str.begin() + end);
}
Str& Str::append_fmt(const char* fmt, ...)
{
int add, diff;
// determine arg m_length
va_list args;
va_start(args, fmt);
add = vsnprintf(NULL, 0, fmt, args);
va_end(args);
// reserve
reserve(m_length + add);
diff = m_capacity - m_length;
if (diff <= 0) diff = 0;
// print out
va_start(args, fmt);
vsnprintf(data() + m_length, (size_t)diff, fmt, args);
va_end(args);
// increment size
m_length += add;
data()[m_length] = '\0';
return *this;
}
Str& Str::append_utf16(const uint16_t* start, const uint16_t* end, bool swapEndian)
{
while (start != end)
{
uint16_t next = (*start++);
if (swapEndian)
next = ((next & 0xff) << 8 | ((next & 0xff00) >> 8));
uint32_t cp = 0xffff & next;
if ((cp >= UTF8_LEAD_SURROGATE_MIN && cp <= UTF8_LEAD_SURROGATE_MAX))
{
next = (*start++);
if (swapEndian)
next = ((next & 0xff) << 8 | ((next & 0xff00) >> 8));
uint32_t trail = 0xffff & next;
cp = (cp << 10) + trail + UTF8_SURROGATE_OFFSET;
}
append(cp);
}
return *this;
}
Str& Str::trim()
{
const char* s = begin();
const char* e = end() - 1;
while (isspace(*s) && s != e)
s++;
while (isspace(*e) && s != e)
e--;
set(s, e + 1);
return *this;
}
bool Str::starts_with(const char* str, bool ignoreCase) const
{
int len = (int)strlen(str);
if (len > m_length || len <= 0)
return false;
const char* a = data();
const char* b = str;
if (ignoreCase)
{
for (int n = 0; n < len; n++)
if (tolower(a[n]) != tolower(b[n]))
return false;
}
else
{
for (int n = 0; n < len; n++)
if (a[n] != b[n])
return false;
}
return true;
}
bool Str::contains(const char* str, bool ignoreCase) const
{
int len = (int)strlen(str);
if (len > m_length || len <= 0)
return false;
const char* a = data();
const char* b = str;
for (int start = 0; start < m_length - len + 1; start++)
{
bool match = true;
if (ignoreCase)
{
for (int n = 0; n < len && match; n++)
if (tolower(a[start + n]) != tolower(b[n]))
match = false;
}
else
{
for (int n = 0; n < len && match; n++)
if (a[start + n] != b[n])
match = false;
}
if (match)
return true;
}
return false;
}
bool Str::ends_with(const char* str, bool ignoreCase) const
{
int len = (int)strlen(str);
if (len > m_length || len <= 0)
return false;
const char* a = data();
const char* b = str;
if (ignoreCase)
{
for (int n = m_length - len, i = 0; n < m_length; n++, i++)
if (tolower(a[n]) != tolower(b[i]))
return false;
}
else
{
for (int n = m_length - len, i = 0; n < m_length; n++, i++)
if (a[n] != b[i])
return false;
}
return true;
}
int Str::first_index_of(char ch) const
{
const char* ptr = data();
for (int n = 0; n < m_length; n++)
if (ptr[n] == ch)
return n;
return -1;
}
int Str::last_index_of(char ch) const
{
const char* ptr = data();
for (int n = m_length - 1; n >= 0; n--)
if (ptr[n] == ch)
return n;
return -1;
}
Str Str::substr(int start) const
{
if (start < 0) start = 0;
if (start > m_length) start = m_length;
return Str(data() + start);
}
Str Str::substr(int start, int end) const
{
if (start < 0) start = 0;
if (start > m_length) start = m_length;
if (end < 0) end = m_length + end;
if (end < start) end = start;
if (end > m_length) end = m_length;
return Str(data() + start, data() + end);
}
void Str::replace(char c, char r)
{
char* ptr = data();
for (int n = 0; n < m_length; n++)
if (ptr[n] == c)
ptr[n] = r;
}
void Str::clear()
{
if (m_capacity > 0)
data()[0] = '\0';
m_length = 0;
}
void Str::dispose()
{
if (m_buffer != nullptr && m_buffer != empty_buffer)
delete[] m_buffer;
if (m_local_size > 0)
{
m_buffer = nullptr;
m_capacity = m_local_size;
data()[0] = '\0';
}
else
{
m_buffer = empty_buffer;
m_capacity = 0;
}
m_length = 0;
}
void Str::set(const char* start, const char* end)
{
// find the end
if (end == nullptr)
end = start + strlen(start);
// reserve
m_length = (int)(end - start);
reserve(m_length);
// copy the data over
char* ptr = data();
memcpy(ptr, start, m_length);
ptr[m_length] = '\0';
}

View File

@ -0,0 +1,185 @@
#pragma once
#include <inttypes.h>
namespace Blah
{
class Str
{
public:
Str() { m_buffer = empty_buffer; m_length = m_capacity = m_local_size = 0; }
Str(const char* start, const char* end = nullptr) : Str() { set(start, end); }
Str(const Str& str) : Str() { set(str); }
char& operator[](int index) { return data()[index]; }
const char& operator[](int index) const { return data()[index]; }
// equality operators
bool operator==(const Str& rhs) const;
bool operator!=(const Str& rhs) const;
bool operator==(const char* rhs) const;
bool operator!=(const char* rhs) const;
// assignment operator
Str& operator=(const Str& rhs) { set(rhs.cstr(), rhs.cstr() + rhs.m_length); return *this; }
Str& operator=(const char* rhs) { set(rhs, nullptr); return *this; }
// append string
Str& operator+=(const Str& rhs) { return append(rhs); }
// append cstr
Str& operator+=(const char* rhs) { return append(rhs); }
// combine string
Str operator+(const Str& rhs) { Str str; str.append(*this).append(rhs); return str; }
// combine cstr
Str operator+(const char* rhs) { Str str; str.append(*this).append(rhs); return str; }
// implicit cast to cstr
operator char* () { return cstr(); }
// implicit cast to cstr
operator const char* () const { return cstr(); }
// returns a pointer to the null-terminated string buffer
char* cstr() { return data(); }
// returns a pointer to the null-terminated string buffer
const char* cstr() const { return data(); }
// returns a pointer to the start of the buffer
const char* begin() const { return data(); }
// returns a pointer to the end of the buffer
const char* end() const { return data() + m_length; }
// returns the length of the string
int length() const { return m_length; }
// returns the capacity of the string
int capacity() const { return m_capacity; }
// returns the capacity of the string's stack buffer
int stack_capacity() const { return m_local_size; }
// sets the length of the string.
// this does not set the value of the string!
void set_length(int length);
// ensures the string has the given capacity
void reserve(int capacity);
// appends the given character
Str& append(char c);
// appends the given unicode character
Str& append(uint32_t c);
// appends the given c string
Str& append(const char* start, const char* end = nullptr);
// appends the given string
Str& append(const Str& str, int start = 0, int end = -1);
// appends the given formatted string
Str& append_fmt(const char* fmt, ...);
// appends a utf16 string
Str& append_utf16(const uint16_t* start, const uint16_t* end = nullptr, bool swapEndian = false);
// trims whitespace
Str& trim();
// returns true if the string begins with the given string
bool starts_with(const Str& str, bool ignoreCase = false) const { return starts_with(str.cstr(), ignoreCase); }
// returns true if the string begins with the given string
bool starts_with(const char* str, bool ignoreCase = false) const;
// returns true if the string contains with the given string
bool contains(const Str& str, bool ignoreCase = false) const { return contains(str.cstr(), ignoreCase); }
// returns true if the string contains with the given string
bool contains(const char* str, bool ignoreCase = false) const;
// returns true if the string ends with the given string
bool ends_with(const Str& str, bool ignoreCase = false) const { return ends_with(str.cstr(), ignoreCase); }
// returns true if the string ends with the given string
bool ends_with(const char* str, bool ignoreCase = false) const;
// returns the first index of the given character, or -1 if it isn't found
int first_index_of(char ch) const;
// returns the last index of the given character, or -1 if it isn't found
int last_index_of(char ch) const;
// returns a substring of the string
Str substr(int start) const;
// returns a substring of the string
Str substr(int start, int end) const;
// replaces all occurances of the given character in the string
void replace(char c, char r);
// checks if the string has a length of 0
bool empty() const { return m_length <= 0; }
// clears the string length to 0
void clear();
// clears and disposes the internal string buffer
void dispose();
~Str()
{
if (m_buffer != nullptr && m_buffer != empty_buffer)
delete[] m_buffer;
}
protected:
Str(int local_size)
{
m_buffer = nullptr;
m_length = 0;
m_capacity = local_size;
m_local_size = local_size;
}
// returns a pointer to the heap buffer or to our stack allocation
char* data() { return (m_buffer != nullptr ? m_buffer : ((char*)(this) + sizeof(Str))); }
const char* data() const { return (m_buffer != nullptr ? m_buffer : ((char*)(this) + sizeof(Str))); }
void set(const Str& str) { set(str.cstr(), str.cstr() + str.m_length); }
void set(const char* start, const char* end = nullptr);
private:
static char empty_buffer[1];
char* m_buffer;
int m_length;
int m_capacity;
int m_local_size;
};
template<int T>
class StrOf : public Str
{
private:
char m_local_buffer[T];
public:
StrOf() : Str(T) { m_local_buffer[0] = '\0'; }
StrOf(const char* rhs, const char* end = nullptr) : Str(T) { m_local_buffer[0] = '\0'; set(rhs, end); }
StrOf(const Str& rhs) : Str(T) { m_local_buffer[0] = '\0'; set(rhs); }
StrOf(const StrOf& rhs) : Str(T) { m_local_buffer[0] = '\0'; set(rhs); }
// assignment operators
StrOf& operator=(const char* rhs) { set(rhs); return *this; }
StrOf& operator=(const Str& rhs) { set(rhs); return *this; }
StrOf& operator=(const StrOf& rhs) { set(rhs); return *this; }
};
using String = StrOf<64>;
}

View File

@ -0,0 +1,832 @@
#include <blah/drawing/batch.h>
#include <blah/graphics/texture.h>
#include <blah/graphics/framebuffer.h>
#include <blah/graphics/mesh.h>
#include <blah/graphics/shader.h>
#include <blah/graphics/material.h>
#include <blah/math/calc.h>
using namespace Blah;
// TODO:
// the line drawings methods aren't consistent
// ex. some draw outside the shape, some draw within it, etc.
// -> make them all draw within the provided shape
// TODO:
// This needs to be graphics API agnostic
const ShaderData data = {
// vertex shader
"#version 330\n"
"uniform mat4 u_matrix;\n"
"layout(location=0) in vec2 a_position;\n"
"layout(location=1) in vec2 a_tex;\n"
"layout(location=2) in vec4 a_color;\n"
"layout(location=3) in vec3 a_type;\n"
"out vec2 v_tex;\n"
"out vec4 v_col;\n"
"out vec3 v_type;\n"
"void main(void)\n"
"{\n"
" gl_Position = u_matrix * vec4(a_position.xy, 0, 1);\n"
" v_tex = a_tex;\n"
" v_col = a_color;\n"
" v_type = a_type;\n"
"}",
// fragment shader
"#version 330\n"
"uniform sampler2D u_texture;\n"
"in vec2 v_tex;\n"
"in vec4 v_col;\n"
"in vec3 v_type;\n"
"out vec4 o_color;\n"
"void main(void)\n"
"{\n"
" vec4 color = texture(u_texture, v_tex);\n"
" o_color = \n"
" v_type.x * color * v_col + \n"
" v_type.y * color.a * v_col + \n"
" v_type.z * v_col;\n"
"}"
};
const VertexAttribute attributes[4] = {
{ 0, VertexSemantics::Position, VertexAttributeType::Float, 2, false },
{ 1, VertexSemantics::Texcoord0, VertexAttributeType::Float, 2, false },
{ 2, VertexSemantics::Color0, VertexAttributeType::Byte, 4, true },
{ 3, VertexSemantics::Texcoord1, VertexAttributeType::Byte, 3, true },
};
#define MAKE_VERTEX(vert, mat, px, py, tx, ty, c, m, w, f) \
(vert)->pos.x = ((px) * mat.m11) + ((py) * mat.m21) + mat.m31; \
(vert)->pos.y = ((px) * mat.m12) + ((py) * mat.m22) + mat.m32; \
(vert)->tex.x = tx; \
if (m_batch.flip_vertically) \
(vert)->tex.y = 1.0f - ty; \
else \
(vert)->tex.y = ty; \
(vert)->col = c; \
(vert)->mult = m; \
(vert)->wash = w; \
(vert)->fill = f;
#define PUSH_QUAD(px0, py0, px1, py1, px2, py2, px3, py3, tx0, ty0, tx1, ty1, tx2, ty2, tx3, ty3, col0, col1, col2, col3, mult, fill, wash) \
{ \
m_batch.elements += 2; \
int* _i = m_indices.expand(6); \
*_i++ = m_vertices.count() + 0; \
*_i++ = m_vertices.count() + 1; \
*_i++ = m_vertices.count() + 2; \
*_i++ = m_vertices.count() + 0; \
*_i++ = m_vertices.count() + 2; \
*_i++ = m_vertices.count() + 3; \
Vertex* _v = m_vertices.expand(4); \
MAKE_VERTEX(_v, m_matrix, px0, py0, tx0, ty0, col0, mult, fill, wash); _v++; \
MAKE_VERTEX(_v, m_matrix, px1, py1, tx1, ty1, col1, mult, fill, wash); _v++; \
MAKE_VERTEX(_v, m_matrix, px2, py2, tx2, ty2, col2, mult, fill, wash); _v++; \
MAKE_VERTEX(_v, m_matrix, px3, py3, tx3, ty3, col3, mult, fill, wash); \
}
#define PUSH_TRIANGLE(px0, py0, px1, py1, px2, py2, tx0, ty0, tx1, ty1, tx2, ty2, col0, col1, col2, mult, fill, wash) \
{ \
m_batch.elements += 1; \
int* _i = m_indices.expand(3); \
*_i++ = m_vertices.count() + 0; \
*_i++ = m_vertices.count() + 1; \
*_i++ = m_vertices.count() + 2; \
Vertex* _v = m_vertices.expand(3); \
MAKE_VERTEX(_v, m_matrix, px0, py0, tx0, ty0, col0, mult, fill, wash); _v++; \
MAKE_VERTEX(_v, m_matrix, px1, py1, tx1, ty1, col1, mult, fill, wash); _v++; \
MAKE_VERTEX(_v, m_matrix, px2, py2, tx2, ty2, col2, mult, fill, wash); \
}
// Compares a Batcher variable, and starts a new batch if it has changed
#define SET_BATCH_VAR(variable) \
if (m_batch.elements > 0 && variable != m_batch.variable) \
{ \
m_batches.add(m_batch); \
m_batch.offset += m_batch.elements; \
m_batch.elements = 0; \
} \
m_batch.variable = variable;
ShaderRef Batch::m_default_shader;
Batch::Batch()
{
matrix_uniform = "u_matrix";
texture_uniform = "u_texture";
clear();
}
Batch::~Batch()
{
dispose();
}
void Batch::push_matrix(const Mat3x2& matrix)
{
m_matrix_stack.add(m_matrix);
m_matrix = matrix * m_matrix;
}
void Batch::pop_matrix()
{
m_matrix = m_matrix_stack.pop();
}
void Batch::push_scissor(const Rect& scissor)
{
m_scissor_stack.add(m_batch.scissor);
SET_BATCH_VAR(scissor);
}
void Batch::pop_scissor()
{
Rect scissor = m_scissor_stack.pop();
SET_BATCH_VAR(scissor);
}
void Batch::push_blend(const BlendMode& blend)
{
m_blend_stack.add(m_batch.blend);
SET_BATCH_VAR(blend);
}
void Batch::pop_blend()
{
BlendMode blend = m_blend_stack.pop();
SET_BATCH_VAR(blend);
}
void Batch::push_material(const MaterialRef& material)
{
m_material_stack.add(m_batch.material);
SET_BATCH_VAR(material);
}
void Batch::pop_material()
{
MaterialRef material = m_material_stack.pop();
SET_BATCH_VAR(material);
}
void Batch::push_layer(int layer)
{
m_layer_stack.add(m_batch.layer);
SET_BATCH_VAR(layer);
}
void Batch::pop_layer()
{
int layer = m_layer_stack.pop();
SET_BATCH_VAR(layer);
}
void Batch::push_color_mode(ColorMode mode)
{
m_color_mode_stack.add(m_color_mode);
m_color_mode = mode;
m_tex_mult = (m_color_mode == ColorMode::Normal ? 255 : 0);
m_tex_wash = (m_color_mode == ColorMode::Wash ? 255 : 0);
}
void Batch::pop_color_mode()
{
m_color_mode = m_color_mode_stack.pop();
m_tex_mult = (m_color_mode == ColorMode::Normal ? 255 : 0);
m_tex_wash = (m_color_mode == ColorMode::Wash ? 255 : 0);
}
void Batch::set_texture(const TextureRef& texture)
{
if (m_batch.elements > 0 && texture != m_batch.texture && m_batch.texture && m_batch.texture->is_valid())
{
m_batches.add(m_batch);
m_batch.offset += m_batch.elements;
m_batch.elements = 0;
}
if (m_batch.texture != texture)
{
m_batch.texture = texture;
m_batch.flip_vertically = Graphics::info()->origin_bottom_left && texture && texture->is_valid() && texture->is_framebuffer();
}
}
void Batch::render(const FrameBufferRef& target)
{
Point size;
if (!target || !target->is_valid())
size = Point(App::draw_width(), App::draw_height());
else
size = Point(target->width(), target->height());
render(target, Mat4x4::CreateOrthographicOffCenter(0, (float)size.x, (float)size.y, 0, 0.01f, 1000.0f));
}
void Batch::render(const FrameBufferRef& target, const Mat4x4& matrix)
{
// nothing to draw
if ((m_batches.count() <= 0 && m_batch.elements <= 0) || m_indices.count() <= 0)
return;
// define defaults
{
if (!m_mesh || !m_mesh->is_valid())
{
m_mesh = Graphics::create_mesh();
m_mesh->vertex_format(attributes, 4, sizeof(Vertex));
}
if (!m_default_shader)
m_default_shader = Graphics::create_shader(&data);
if (!m_default_material)
m_default_material = Graphics::create_material(m_default_shader);
}
// upload data
m_mesh->index_data(m_indices.begin(), m_indices.count());
m_mesh->vertex_data(m_vertices.begin(), m_vertices.count());
RenderCall call;
call.target = target;
call.mesh = m_mesh;
call.has_viewport = false;
call.viewport = Rect();
call.instance_count = 0;
call.depth = Compare::None;
call.cull = Cull::None;
for (auto& b : m_batches)
render_single_batch(call, b, matrix);
if (m_batch.elements > 0)
render_single_batch(call, m_batch, matrix);
}
void Batch::render_single_batch(RenderCall& call, const DrawBatch& b, const Mat4x4& matrix)
{
call.material = b.material;
if (!call.material || !call.material->is_valid())
call.material = m_default_material;
call.material->set_texture(texture_uniform, b.texture, 0);
call.material->set_value(matrix_uniform, &matrix.m11, 16);
call.blend = b.blend;
call.has_scissor = b.scissor.w >= 0 && b.scissor.h >= 0;
call.scissor = b.scissor;
call.index_start = (int64_t)b.offset * 3;
call.index_count = (int64_t)b.elements * 3;
Graphics::render(call);
}
void Batch::clear()
{
m_matrix = Mat3x2::identity;
m_color_mode = ColorMode::Normal;
m_tex_mult = 255;
m_tex_wash = 0;
m_vertices.clear();
m_indices.clear();
m_batch.layer = 0;
m_batch.elements = 0;
m_batch.offset = 0;
m_batch.blend = BlendMode::Normal;
m_batch.material.reset();
m_batch.texture.reset();
m_batch.scissor.w = m_batch.scissor.h = -1;
m_batch.flip_vertically = false;
m_matrix_stack.clear();
m_scissor_stack.clear();
m_blend_stack.clear();
m_material_stack.clear();
m_color_mode_stack.clear();
m_layer_stack.clear();
m_batches.clear();
}
void Batch::dispose()
{
m_matrix_stack.dispose();
m_scissor_stack.dispose();
m_blend_stack.dispose();
m_material_stack.dispose();
m_color_mode_stack.dispose();
m_layer_stack.dispose();
m_batches.dispose();
m_default_material.reset();
m_mesh.reset();
m_vertices.dispose();
m_indices.dispose();
}
void Batch::line(const Vec2& from, const Vec2& to, float t, Color color)
{
line(from, to, t, color, color);
}
void Batch::line(const Vec2& from, const Vec2& to, float t, Color fromColor, Color toColor)
{
if (from.x == to.x && from.y == to.y)
return;
Vec2 normal = (to - from).normal();
Vec2 perp = Vec2(normal.y, -normal.x);
Vec2 pos0 = from + perp * t * 0.5f;
Vec2 pos1 = to + perp * t * 0.5f;
Vec2 pos2 = to - perp * t * 0.5f;
Vec2 pos3 = from - perp * t * 0.5f;
PUSH_QUAD(
pos0.x, pos0.y, pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y,
0, 0, 0, 0, 0, 0, 0, 0,
fromColor, toColor, toColor, fromColor,
0, 0, 255);
}
void Batch::bezier_line(const Vec2& from, const Vec2& b, const Vec2& to, int steps, float t, Color color)
{
Vec2 prev = from;
float add = 1.0f / steps;
for (int i = 1; i < steps; i++)
{
Vec2 at = Vec2::bezier_lerp(from, b, to, add * i);
line(prev, at, t, color);
prev = at;
}
line(prev, to, t, color);
}
void Batch::bezier_line(const Vec2& from, const Vec2& b, const Vec2& c, const Vec2& to, int steps, float t, Color color)
{
Vec2 prev = from;
float add = 1.0f / steps;
for (int i = 1; i < steps; i++)
{
Vec2 at = Vec2::bezier_lerp(from, b, c, to, add * i);
line(prev, at, t, color);
prev = at;
}
line(prev, to, t, color);
}
void Batch::tri(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, Color color)
{
PUSH_TRIANGLE(
pos0.x, pos0.y, pos1.x, pos1.y, pos2.x, pos2.y,
0, 0, 0, 0, 0, 0,
color, color, color,
0, 0, 255);
}
void Batch::tri(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, Color col0, Color col1, Color col2)
{
PUSH_TRIANGLE(
pos0.x, pos0.y, pos1.x, pos1.y, pos2.x, pos2.y,
0, 0, 0, 0, 0, 0,
col0, col1, col2,
0, 0, 255);
}
void Batch::tri(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, const Vec2& tex0, const Vec2& tex1, const Vec2& tex2, Color color)
{
PUSH_TRIANGLE(
pos0.x, pos0.y, pos1.x, pos1.y, pos2.x, pos2.y,
tex0.x, tex0.y, tex1.x, tex1.y, tex2.x, tex2.y,
color, color, color,
m_tex_mult, m_tex_wash, 0);
}
void Batch::tri(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, const Vec2& tex0, const Vec2& tex1, const Vec2& tex2, Color col0, Color col1, Color col2)
{
PUSH_TRIANGLE(
pos0.x, pos0.y, pos1.x, pos1.y, pos2.x, pos2.y,
tex0.x, tex0.y, tex1.x, tex1.y, tex2.x, tex2.y,
col0, col1, col2,
m_tex_mult, m_tex_wash, 0);
}
void Batch::tri_line(const Vec2& a, const Vec2& b, const Vec2& c, float t, Color color)
{
}
void Batch::rect(const Rect& rect, Color color)
{
PUSH_QUAD(
rect.x, rect.y,
rect.x + rect.w, rect.y,
rect.x + rect.w, rect.y + rect.h,
rect.x, rect.y + rect.h,
0, 0, 0, 0, 0, 0, 0, 0,
color, color, color, color,
0, 0, 255);
}
void Batch::rect_line(const Rect& rect, float t, Color color)
{
PUSH_QUAD(
rect.x, rect.y,
rect.x + rect.w - t, rect.y,
rect.x + rect.w - t, rect.y + t,
rect.x, rect.y + t,
0, 0, 0, 0, 0, 0, 0, 0,
color, color, color, color,
0, 0, 255);
PUSH_QUAD(
rect.x + rect.w - t, rect.y,
rect.x + rect.w, rect.y,
rect.x + rect.w, rect.y + rect.h - t,
rect.x + rect.w - t, rect.y + rect.h - t,
0, 0, 0, 0, 0, 0, 0, 0,
color, color, color, color,
0, 0, 255);
PUSH_QUAD(
rect.x + t, rect.y + rect.h - t,
rect.x + rect.w, rect.y + rect.h - t,
rect.x + rect.w, rect.y + rect.h,
rect.x, rect.y + rect.h,
0, 0, 0, 0, 0, 0, 0, 0,
color, color, color, color,
0, 0, 255);
PUSH_QUAD(
rect.x, rect.y + t,
rect.x + t, rect.y + t,
rect.x + t, rect.y + rect.h,
rect.x, rect.y + rect.h,
0, 0, 0, 0, 0, 0, 0, 0,
color, color, color, color,
0, 0, 255);
}
void Batch::rect_rounded(const Rect& rect, float radius, int steps, Color color)
{
rect_rounded(rect, radius, steps, radius, steps, radius, steps, radius, steps, color);
}
void Batch::rect_rounded(const Rect& rect, float rtl, int rtl_steps, float rtr, int rtr_steps, float rbr, int rbr_steps, float rbl, int rbl_steps, Color color)
{
// clamp
rtl = Calc::min(Calc::min(Calc::max(0.0f, rtl), rect.w / 2.0f), rect.h / 2.0f);
rtr = Calc::min(Calc::min(Calc::max(0.0f, rtr), rect.w / 2.0f), rect.h / 2.0f);
rbr = Calc::min(Calc::min(Calc::max(0.0f, rbr), rect.w / 2.0f), rect.h / 2.0f);
rbl = Calc::min(Calc::min(Calc::max(0.0f, rbl), rect.w / 2.0f), rect.h / 2.0f);
if (rtl <= 0 && rtr <= 0 && rbr <= 0 && rbl <= 0)
{
this->rect(rect, color);
}
else
{
// get corners
Rect tl = Rect(rect.top_left(), Vec2(rtl, rtl));
Rect tr = Rect(rect.top_right() + Vec2(-rtr, 0), Vec2(rtr, rtr));
Rect bl = Rect(rect.bottom_left() + Vec2(0, -rbl), Vec2(rbl, rbl));
Rect br = Rect(rect.bottom_right() + Vec2(-rbr, -rbr), Vec2(rbr, rbr));
// rounded corners
semi_circle(tl.bottom_right(), Calc::UP, Calc::LEFT, rtl, rtl_steps, color);
semi_circle(tr.bottom_left(), Calc::UP, Calc::RIGHT, rtr, rtr_steps, color);
semi_circle(bl.top_right(), Calc::DOWN, Calc::LEFT, rbl, rbl_steps, color);
semi_circle(br.top_left(), Calc::DOWN, Calc::RIGHT, rbr, rbr_steps, color);
// center quads
quad(tl.top_right(), tr.top_left(), tr.bottom_left(), tl.bottom_right(), color);
quad(tr.bottom_left(), tr.bottom_right(), br.top_right(), br.top_left(), color);
quad(bl.top_right(), br.top_left(), br.bottom_left(), bl.bottom_right(), color);
quad(tl.bottom_left(), tl.bottom_right(), bl.top_right(), bl.top_left(), color);
quad(tl.bottom_right(), tr.bottom_left(), br.top_left(), bl.top_right(), color);
}
}
void Batch::rect_rounded_line(const Rect & rect, float radius, int steps, float t, Color color)
{
rect_rounded_line(rect, radius, steps, radius, steps, radius, steps, radius, steps, t, color);
}
void Batch::rect_rounded_line(const Rect& rect, float rtl, int rtl_steps, float rtr, int rtr_steps, float rbr, int rbr_steps, float rbl, int rbl_steps, float t, Color color)
{
Rect r = rect;
r.x += t * 0.5f;
r.y += t * 0.5f;
r.w -= t;
r.h -= t;
// clamp
rtl = Calc::min(Calc::min(Calc::max(0.0f, rtl), r.w / 2.0f), r.h / 2.0f);
rtr = Calc::min(Calc::min(Calc::max(0.0f, rtr), r.w / 2.0f), r.h / 2.0f);
rbr = Calc::min(Calc::min(Calc::max(0.0f, rbr), r.w / 2.0f), r.h / 2.0f);
rbl = Calc::min(Calc::min(Calc::max(0.0f, rbl), r.w / 2.0f), r.h / 2.0f);
if (rtl <= 0 && rtr <= 0 && rbr <= 0 && rbl <= 0)
{
this->rect_line(r, t, color);
}
else
{
// get corners
Rect tl = Rect(r.top_left(), Vec2(rtl, rtl));
Rect tr = Rect(r.top_right() + Vec2(-rtr, 0), Vec2(rtr, rtr));
Rect bl = Rect(r.bottom_left() + Vec2(0, -rbl), Vec2(rbl, rbl));
Rect br = Rect(r.bottom_right() + Vec2(-rbr, -rbr), Vec2(rbr, rbr));
// rounded corners
semi_circle_line(tl.bottom_right(), Calc::UP, Calc::LEFT, rtl, rtl_steps, t, color);
semi_circle_line(tr.bottom_left(), Calc::UP, Calc::RIGHT, rtr, rtr_steps, t, color);
semi_circle_line(bl.top_right(), Calc::DOWN, Calc::LEFT, rbl, rbl_steps, t, color);
semi_circle_line(br.top_left(), Calc::DOWN, Calc::RIGHT, rbr, rbr_steps, t, color);
// connect sides that aren't touching
if (tl.bottom() < bl.top())
line(tl.bottom_left(), bl.top_left(), t, color);
if (tl.right() < tr.left())
line(tl.top_right(), tr.top_left(), t, color);
if (tr.bottom() < br.top())
line(tr.bottom_right(), br.top_right(), t, color);
if (bl.right() < br.left())
line(bl.bottom_right(), br.bottom_left(), t, color);
}
}
void Batch::semi_circle(Vec2 center, float start_radians, float end_radians, float radius, int steps, Color centerColor, Color edgeColor)
{
Vec2 last = Vec2::from_angle(start_radians, radius);
float add = Calc::angle_diff(start_radians, end_radians);
for (int i = 1; i <= steps; i++)
{
Vec2 next = Vec2::from_angle(start_radians + add * (i / (float)steps), radius);
tri(center + last, center + next, center, edgeColor, edgeColor, centerColor);
last = next;
}
}
void Batch::semi_circle(Vec2 center, float start_radians, float end_radians, float radius, int steps, Color color)
{
semi_circle(center, start_radians, end_radians, radius, steps, color, color);
}
void Batch::semi_circle_line(Vec2 center, float start_radians, float end_radians, float radius, int steps, float t, Color color)
{
float add = Calc::angle_diff(start_radians, end_radians);
Vec2 last_inner = Vec2::from_angle(start_radians, radius - t / 2);
Vec2 last_outer = Vec2::from_angle(start_radians, radius + t / 2);
for (int i = 1; i <= steps; i++)
{
Vec2 next_inner = Vec2::from_angle(start_radians + add * (i / (float)steps), radius - t / 2);
Vec2 next_outer = Vec2::from_angle(start_radians + add * (i / (float)steps), radius + t / 2);
quad(center + last_inner, center + last_outer, center + next_outer, center + next_inner, color);
last_inner = next_inner;
last_outer = next_outer;
}
}
void Batch::circle(const Vec2 center, float radius, int steps, Color color)
{
Vec2 last = Vec2(radius, 0);
for (int i = 1; i <= steps; i++)
{
float radians = (i / (float)steps) * Calc::TAU;
Vec2 next = Vec2::from_angle(radians, radius);
tri(center + last, center + next, center, color);
last = next;
}
}
void Batch::circle_line(const Vec2 center, float radius, float t, int steps, Color color)
{
radius -= t * 0.5f;
Vec2 last = Vec2(radius, 0);
for (int i = 1; i <= steps; i++)
{
float radians = (i / (float)steps) * Calc::TAU;
Vec2 next = Vec2(Calc::cos(radians), Calc::sin(radians)) * radius;
line(center + last, center + next, t, color);
last = next;
}
}
void Batch::quad(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, const Vec2& pos3, Color color)
{
PUSH_QUAD(
pos0.x, pos0.y, pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y,
0, 0, 0, 0, 0, 0, 0, 0,
color, color, color, color,
0, 0, 255);
}
void Batch::quad(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, const Vec2& pos3, Color col0, Color col1, Color col2, Color col3)
{
PUSH_QUAD(
pos0.x, pos0.y, pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y,
0, 0, 0, 0, 0, 0, 0, 0,
col0, col1, col2, col3,
0, 0, 255);
}
void Batch::quad(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, const Vec2& pos3, const Vec2& tex0, const Vec2& tex1, const Vec2& tex2, const Vec2& tex3, Color color)
{
PUSH_QUAD(
pos0.x, pos0.y, pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y,
tex0.x, tex0.y, tex1.x, tex1.y, tex2.x, tex2.y, tex3.x, tex3.y,
color, color, color, color,
m_tex_mult, m_tex_wash, 0);
}
void Batch::quad(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, const Vec2& pos3, const Vec2& tex0, const Vec2& tex1, const Vec2& tex2, const Vec2& tex3, Color col0, Color col1, Color col2, Color col3)
{
PUSH_QUAD(
pos0.x, pos0.y, pos1.x, pos1.y, pos2.x, pos2.y, pos3.x, pos3.y,
tex0.x, tex0.y, tex1.x, tex1.y, tex2.x, tex2.y, tex3.x, tex3.y,
col0, col1, col2, col3,
m_tex_mult, m_tex_wash, 0);
}
void Batch::quad_line(const Vec2& a, const Vec2& b, const Vec2& c, const Vec2& d, float t, Color color)
{
line(a, b, t, color);
line(b, c, t, color);
line(c, d, t, color);
line(d, a, t, color);
}
void Batch::arrow_head(const Vec2& point_pos, float radians, float side_len, Color color)
{
arrow_head(point_pos, point_pos - Vec2::from_angle(radians), side_len, color);
}
void Batch::arrow_head(const Vec2& point_pos, const Vec2& from_pos, float side_len, Color color)
{
float height = sqrtf(side_len * side_len - powf(side_len / 2, 2));
Vec2 dir = (point_pos - from_pos).normal();
Vec2 perp = dir.perpendicular();
Vec2 base = point_pos - dir * height;
tri(point_pos, base + perp * side_len / 2, base - perp * side_len / 2, color);
}
void Batch::tex()
{
}
void Batch::tex(const Subtexture& sub, const Vec2& pos, Color color)
{
if (!sub.texture || !sub.texture->is_valid())
{
PUSH_QUAD(
pos.x + sub.draw_coords[0].x, pos.y + sub.draw_coords[0].y,
pos.x + sub.draw_coords[1].x, pos.y + sub.draw_coords[1].y,
pos.x + sub.draw_coords[2].x, pos.y + sub.draw_coords[2].y,
pos.x + sub.draw_coords[3].x, pos.y + sub.draw_coords[3].y,
0, 0, 0, 0, 0, 0, 0, 0,
color, color, color, color,
0, 0, 255);
}
else
{
set_texture(sub.texture);
PUSH_QUAD(
pos.x + sub.draw_coords[0].x, pos.y + sub.draw_coords[0].y,
pos.x + sub.draw_coords[1].x, pos.y + sub.draw_coords[1].y,
pos.x + sub.draw_coords[2].x, pos.y + sub.draw_coords[2].y,
pos.x + sub.draw_coords[3].x, pos.y + sub.draw_coords[3].y,
sub.tex_coords[0].x, sub.tex_coords[0].y,
sub.tex_coords[1].x, sub.tex_coords[1].y,
sub.tex_coords[2].x, sub.tex_coords[2].y,
sub.tex_coords[3].x, sub.tex_coords[3].y,
color, color, color, color,
m_tex_mult, m_tex_wash, 0);
}
}
void Batch::tex(const Subtexture& sub, const Vec2& pos, const Vec2& origin, const Vec2& scale, float rotation, Color color)
{
push_matrix(Mat3x2::create_transform(pos, origin, scale, rotation));
if (!sub.texture || !sub.texture->is_valid())
{
PUSH_QUAD(
sub.draw_coords[0].x, sub.draw_coords[0].y,
sub.draw_coords[1].x, sub.draw_coords[1].y,
sub.draw_coords[2].x, sub.draw_coords[2].y,
sub.draw_coords[3].x, sub.draw_coords[3].y,
0, 0, 0, 0, 0, 0, 0, 0,
color, color, color, color,
0, 0, 255);
}
else
{
set_texture(sub.texture);
PUSH_QUAD(
sub.draw_coords[0].x, sub.draw_coords[0].y,
sub.draw_coords[1].x, sub.draw_coords[1].y,
sub.draw_coords[2].x, sub.draw_coords[2].y,
sub.draw_coords[3].x, sub.draw_coords[3].y,
sub.tex_coords[0].x, sub.tex_coords[0].y,
sub.tex_coords[1].x, sub.tex_coords[1].y,
sub.tex_coords[2].x, sub.tex_coords[2].y,
sub.tex_coords[3].x, sub.tex_coords[3].y,
color, color, color, color,
m_tex_mult, m_tex_wash, 0);
}
pop_matrix();
}
void Batch::str(const SpriteFont& font, const String& text, const Vec2& pos, Color color)
{
str(font, text, pos, TextAlign::TopLeft, font.size(), color);
}
void Batch::str(const SpriteFont& font, const String& text, const Vec2& pos, TextAlign align, float size, Color color)
{
push_matrix(
Mat3x2::create_scale(size / font.size()) *
Mat3x2::create_translation(pos)
);
Vec2 offset;
if ((align & TextAlign::Left) == TextAlign::Left)
offset.x = 0;
else if ((align & TextAlign::Right) == TextAlign::Right)
offset.x -= font.width_of_line(text);
else
offset.x -= font.width_of_line(text) * 0.5f;
if ((align & TextAlign::Top) == TextAlign::Top)
offset.y = font.ascent() + font.descent();
else if ((align & TextAlign::Bottom) == TextAlign::Bottom)
offset.y = font.ascent() + font.descent() - font.height_of(text);
else
offset.y = font.ascent() + font.descent() - font.height_of(text) * 0.5f;
for (int i = 0, l = text.length(); i < l; i++)
{
if (text[i] == '\n')
{
// increment y
offset.y += font.line_height();
// re-align X for this line
if ((align & TextAlign::Left) == TextAlign::Left)
offset.x = 0;
else if ((align & TextAlign::Right) == TextAlign::Right)
offset.x = -font.width_of_line(text, i + 1);
else
offset.x = -font.width_of_line(text, i + 1) * 0.5f;
continue;
}
// TODO:
// This doesn't parse Unicode!
// It will assume it's a 1-byte ASCII char which is incorrect
auto ch = font[text[i]];
if (!ch.subtexture.texture || !ch.subtexture.texture->is_valid())
{
Vec2 at = offset + ch.offset;
if (i > 0 && text[i - 1] != '\n')
{
// TODO:
// This doesn't parse Unicode!
at.x += font.get_kerning(text[i], text[i - 1]);
}
tex(ch.subtexture, at, color);
}
offset.x += ch.advance;
}
pop_matrix();
}

169
public/blah/drawing/batch.h Normal file
View File

@ -0,0 +1,169 @@
#pragma once
#include <blah/graphics/graphics.h>
#include <blah/containers/list.h>
#include <blah/containers/str.h>
#include <blah/math/vec2.h>
#include <blah/math/rect.h>
#include <blah/math/mat3x2.h>
#include <blah/math/mat4x4.h>
#include <blah/math/color.h>
#include <blah/drawing/subtexture.h>
#include <blah/drawing/spritefont.h>
namespace Blah
{
enum class ColorMode
{
Normal,
Wash
};
enum class TextAlign : char
{
Center = 0,
Left = 1 << 1,
Right = 1 << 2,
Top = 1 << 3,
Bottom = 1 << 4,
TopLeft = Top | Left,
TopRight = Top | Right,
BottomLeft = Bottom | Left,
BottomRight = Bottom | Right
};
inline TextAlign operator|(TextAlign lhs, TextAlign rhs) { return static_cast<TextAlign>(static_cast<char>(lhs) | static_cast<char>(rhs)); }
inline TextAlign operator&(TextAlign lhs, TextAlign rhs) { return static_cast<TextAlign>(static_cast<char>(lhs) & static_cast<char>(rhs)); }
class Batch
{
public:
Batch();
Batch(const Batch& other) = delete;
Batch& operator=(const Batch& other) = delete;
~Batch();
void push_matrix(const Mat3x2& matrix);
void pop_matrix();
void push_scissor(const Rect& scissor);
void pop_scissor();
void push_blend(const BlendMode& blend);
void pop_blend();
void push_material(const MaterialRef& material);
void pop_material();
void push_layer(int layer);
void pop_layer();
void push_color_mode(ColorMode mode);
void pop_color_mode();
void set_texture(const TextureRef& texture);
void render(const FrameBufferRef& target);
void render(const FrameBufferRef& target, const Mat4x4& matrix);
void clear();
void dispose();
void line(const Vec2& from, const Vec2& to, float t, Color color);
void line(const Vec2& from, const Vec2& to, float t, Color fromColor, Color toColor);
void bezier_line(const Vec2& from, const Vec2& b, const Vec2& to, int steps, float t, Color color);
void bezier_line(const Vec2& from, const Vec2& b, const Vec2& c, const Vec2& to, int steps, float t, Color color);
void tri(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, Color color);
void tri(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, Color col0, Color col1, Color col2);
void tri(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, const Vec2& tex0, const Vec2& tex1, const Vec2& tex2, Color color);
void tri(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, const Vec2& tex0, const Vec2& tex1, const Vec2& tex2, Color col0, Color col1, Color col2);
void tri_line(const Vec2& a, const Vec2& b, const Vec2& c, float t, Color color);
void rect(const Rect& rect, Color color);
void rect_line(const Rect& rect, float t, Color color);
void rect_rounded(const Rect& rect, float radius, int steps, Color color);
void rect_rounded(const Rect& rect, float rtl, int rtl_steps, float rtr, int rtr_steps, float rbr, int rbr_steps, float rbl, int rbl_steps, Color color);
void rect_rounded_line(const Rect& rect, float radius, int steps, float t, Color color);
void rect_rounded_line(const Rect& rect, float rtl, int rtl_steps, float rtr, int rtr_steps, float rbr, int rbr_steps, float rbl, int rbl_steps, float t, Color color);
void semi_circle(Vec2 center, float start_radians, float end_radians, float radius, int steps, Color centerColor, Color edgeColor);
void semi_circle(Vec2 center, float start_radians, float end_radians, float radius, int steps, Color color);
void semi_circle_line(Vec2 center, float start_radians, float end_radians, float radius, int steps, float t, Color color);
void circle(const Vec2 center, float radius, int steps, Color color);
void circle_line(const Vec2 center, float raidus, float t, int steps, Color color);
void quad(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, const Vec2& pos3, Color color);
void quad(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, const Vec2& pos3, Color col0, Color col1, Color col2, Color col3);
void quad(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, const Vec2& pos3, const Vec2& tex0, const Vec2& tex1, const Vec2& tex2, const Vec2& tex3, Color color);
void quad(const Vec2& pos0, const Vec2& pos1, const Vec2& pos2, const Vec2& pos3, const Vec2& tex0, const Vec2& tex1, const Vec2& tex2, const Vec2& tex3, Color col0, Color col1, Color col2, Color col3);
void quad_line(const Vec2& a, const Vec2& b, const Vec2& c, const Vec2& d, float t, Color color);
void arrow_head(const Vec2& point_pos, float radians, float side_len, Color color);
void arrow_head(const Vec2& point_pos, const Vec2& from_pos, float side_len, Color color);
void tex();
void tex(const Subtexture& subtexture, const Vec2& pos, Color color);
void tex(const Subtexture& subtexture, const Vec2& pos, const Vec2& origin, const Vec2& scale, float rotation, Color color);
void str(const SpriteFont& font, const String& text, const Vec2& pos, Color color);
void str(const SpriteFont& font, const String& text, const Vec2& pos, TextAlign align, float size, Color color);
const char* matrix_uniform;
const char* texture_uniform;
private:
struct Vertex
{
Vec2 pos;
Vec2 tex;
Color col;
uint8_t mult;
uint8_t wash;
uint8_t fill;
static VertexAttribute attributes[6];
};
struct DrawBatch
{
int layer;
int offset;
int elements;
MaterialRef material;
BlendMode blend;
TextureRef texture;
bool flip_vertically;
Rect scissor;
DrawBatch() :
layer(0),
offset(0),
elements(0),
blend(BlendMode::Normal),
flip_vertically(false),
scissor(0, 0, -1, -1) {}
};
static ShaderRef m_default_shader;
MaterialRef m_default_material;
MeshRef m_mesh;
Mat3x2 m_matrix;
ColorMode m_color_mode;
uint8_t m_tex_mult;
uint8_t m_tex_wash;
DrawBatch m_batch;
List<Vertex> m_vertices;
List<int> m_indices;
List<Mat3x2> m_matrix_stack;
List<Rect> m_scissor_stack;
List<BlendMode> m_blend_stack;
List<MaterialRef> m_material_stack;
List<ColorMode> m_color_mode_stack;
List<int> m_layer_stack;
List<DrawBatch> m_batches;
void render_single_batch(RenderCall& call, const DrawBatch& b, const Mat4x4& matrix);
};
}

View File

@ -0,0 +1,250 @@
#include <blah/drawing/spritefont.h>
#include <blah/images/font.h>
#include <blah/images/packer.h>
using namespace Blah;
SpriteFont::SpriteFont()
{
m_size = 0;
m_ascent = 0;
m_descent = 0;
m_line_gap = 0;
}
const uint32_t ascii[]{ 32, 128, 0 };
const uint32_t* SpriteFont::ASCII = ascii;
SpriteFont::SpriteFont(const char* file, float size)
{
build(file, size, ASCII);
}
SpriteFont::SpriteFont(const char* file, float size, const uint32_t* charset)
{
build(file, size, charset);
}
SpriteFont::SpriteFont(const Font& font, float size)
{
build(font, size, ASCII);
}
SpriteFont::SpriteFont(const Font& font, float size, const uint32_t* charset)
{
build(font, size, charset);
}
SpriteFont::SpriteFont(SpriteFont&& src) noexcept
{
m_name = src.m_name;
m_size = src.m_size;
m_ascent = src.m_ascent;
m_descent = src.m_descent;
m_line_gap = src.m_line_gap;
m_characters = std::move(src.m_characters);
m_kerning = std::move(src.m_kerning);
m_atlas = std::move(src.m_atlas);
}
SpriteFont::~SpriteFont()
{
dispose();
}
void SpriteFont::dispose()
{
m_atlas.dispose();
m_characters.clear();
m_kerning.clear();
m_name.dispose();
}
SpriteFont& SpriteFont::operator=(SpriteFont && src) noexcept
{
m_name = src.m_name;
m_size = src.m_size;
m_ascent = src.m_ascent;
m_descent = src.m_descent;
m_line_gap = src.m_line_gap;
m_characters = std::move(src.m_characters);
m_kerning = std::move(src.m_kerning);
m_atlas = std::move(src.m_atlas);
return *this;
}
float SpriteFont::width_of(const String& text) const
{
float width = 0;
float lineWidth = 0;
for (auto it = text.begin(); it != text.end(); it++)
{
if (*it == '\n')
lineWidth = 0;
// TODO: this doesn't account for Unicode values!
uint32_t codepoint = *it;
lineWidth += this->operator[](codepoint).advance;
if (lineWidth > width)
width = lineWidth;
}
return width;
}
float SpriteFont::width_of_line(const String& text, int start) const
{
if (start < 0) return 0;
if (start >= text.length()) return 0;
float lineWidth = 0;
for (auto it = text.begin() + start; it != text.end(); it++)
{
if (*it == '\n')
return lineWidth;
// TODO: this doesn't account for Unicode values!
uint32_t codepoint = *it;
lineWidth += this->operator[](codepoint).advance;
}
return lineWidth;
}
float SpriteFont::height_of(const String& text) const
{
if (text.length() <= 0)
return 0;
float height = line_height();
for (auto it = text.begin(); it != text.end(); it++)
{
if (*it == '\n')
height += line_height();
}
return height - m_line_gap;
}
void SpriteFont::build(const char* file, float sz, const uint32_t* charset)
{
dispose();
Font font(file);
if (font.IsValid())
build(font, sz, charset);
}
void SpriteFont::build(const Font& font, float size, const uint32_t* charset)
{
dispose();
float scale = font.GetScale(size);
m_name = font.FamilyName();
m_ascent = font.Ascent() * scale;
m_descent = font.Descent() * scale;
m_line_gap = font.LineGap() * scale;
m_size = size;
Packer packer;
packer.spacing = 0;
packer.padding = 1;
packer.max_size = 8192;
packer.power_of_two = true;
std::map<uint32_t, int> glyphs;
List<Color> buffer;
auto ranges = charset;
while (*ranges != 0)
{
auto from = *ranges;
auto to = *(ranges + 1);
BLAH_ASSERT(to >= from, "Charset Range must be in pairs of [min,max]");
for (auto i = from; i < to; i++)
{
auto glyph = font.GetGlyph(i);
if (glyph <= 0)
continue;
glyphs[i] = glyph;
// add character
Font::Char ch = font.GetCharacter(glyph, scale);
m_characters[i].advance = ch.advance;
m_characters[i].offset = Vec2(ch.offsetX, ch.offsetY);
// pack glyph
if (ch.hasGlyph)
{
if (buffer.count() < ch.width * ch.height)
buffer.expand(ch.width * ch.height - buffer.count());
if (font.GetBitmap(ch, buffer.begin()))
packer.add(i, ch.width, ch.height, buffer.begin());
}
}
ranges += 2;
}
buffer.dispose();
packer.pack();
for (auto& it : packer.pages)
m_atlas.add(Graphics::create_texture(it));
// add character subtextures
for (auto& it : packer.entries)
if (!it.empty)
m_characters[(uint32_t)it.id].subtexture = Subtexture(m_atlas[it.page], it.packed, it.frame);
// add kerning
for (auto a = glyphs.begin(); a != glyphs.end(); a++)
for (auto b = glyphs.begin(); b != glyphs.end(); b++)
set_kerning(a->first, b->first, font.GetKerning(a->second, b->second, scale));
}
float SpriteFont::get_kerning(uint32_t codepoint0, uint32_t codepoint1) const
{
Tuple index;
index.first = codepoint0;
index.second = codepoint1;
auto it = m_kerning.find(index);
if (it != m_kerning.end())
return it->second;
return 0.0f;
}
void SpriteFont::set_kerning(uint32_t codepoint0, uint32_t codepoint1, float value)
{
Tuple index;
index.first = codepoint0;
index.second = codepoint1;
m_kerning[index] = value;
}
const SpriteFont::Character& SpriteFont::get_character(uint32_t codepoint) const
{
static const Character empty;
auto it = m_characters.find(codepoint);
if (it != m_characters.end())
return it->second;
return empty;
}
const SpriteFont::Character& SpriteFont::operator[](uint32_t codepoint) const
{
static const Character empty;
auto it = m_characters.find(codepoint);
if (it != m_characters.end())
return it->second;
return empty;
}

View File

@ -0,0 +1,93 @@
#pragma once
#include <inttypes.h>
#include <blah/containers/list.h>
#include <blah/containers/str.h>
#include <blah/drawing/subtexture.h>
#include <blah/math/vec2.h>
#include <map>
namespace Blah
{
class Font;
class SpriteFont
{
public:
struct Character
{
Subtexture subtexture;
float advance = 0;
Vec2 offset;
};
private:
// properties
String m_name;
float m_size;
float m_ascent;
float m_descent;
float m_line_gap;
// tuple between two characters
struct Tuple { uint32_t first; uint32_t second; };
struct TupleCompare
{
bool operator() (const Tuple& lhs, const Tuple& rhs) const
{
return lhs.first < rhs.first && lhs.second < rhs.second;
}
};
// charset & kerning maps
std::map<uint32_t, Character> m_characters;
std::map<Tuple, float, TupleCompare> m_kerning;
// built texture
List<TextureRef> m_atlas;
public:
static const uint32_t* ASCII;
// Note:
// charset is a list of range pairs, until a 0 terminator (ex. 32,128,0)
SpriteFont();
SpriteFont(const char* file, float size);
SpriteFont(const char* file, float size, const uint32_t* charset);
SpriteFont(const Font& font, float size);
SpriteFont(const Font& font, float size, const uint32_t* charset);
SpriteFont(const SpriteFont&) = delete;
SpriteFont(SpriteFont&& src) noexcept;
~SpriteFont();
void dispose();
SpriteFont& operator=(const SpriteFont&) = delete;
SpriteFont& operator=(SpriteFont&& src) noexcept;
const String& name() const { return m_name; }
float size() const { return m_size; }
float ascent() const { return m_ascent; }
float descent() const { return m_descent; }
float line_gap() const { return m_line_gap; }
float height() const { return m_ascent - m_descent; }
float line_height() const { return m_ascent - m_descent + m_line_gap; }
List<TextureRef>& textures() { return m_atlas; }
float width_of(const String& text) const;
float width_of_line(const String& text, int start = 0) const;
float height_of(const String& text) const;
void build(const char* file, float size, const uint32_t* charset);
void build(const Font& font, float size, const uint32_t* charset);
float get_kerning(uint32_t codepoint0, uint32_t codepoint1) const;
void set_kerning(uint32_t codepoint0, uint32_t codepoint1, float kerning);
Character& get_character(uint32_t codepoint) { return m_characters[codepoint]; }
const Character& get_character(uint32_t codepoint) const;
Character& operator[](uint32_t codepoint) { return m_characters[codepoint]; }
const Character& operator[](uint32_t codepoint) const;
};
}

View File

@ -0,0 +1,41 @@
#include <blah/drawing/subtexture.h>
using namespace Blah;
Subtexture::Subtexture()
{
}
Subtexture::Subtexture(const TextureRef& texture, Rect source, Rect frame)
: texture(texture), source(source), frame(frame)
{
update();
}
void Subtexture::update()
{
draw_coords[0].x = -frame.x;
draw_coords[0].y = -frame.y;
draw_coords[1].x = -frame.x + source.w;
draw_coords[1].y = -frame.y;
draw_coords[2].x = -frame.x + source.w;
draw_coords[2].y = -frame.y + source.h;
draw_coords[3].x = -frame.x;
draw_coords[3].y = -frame.y + source.h;
if (texture && texture->is_valid())
{
float uvx = 1.0f / (float)texture->width();
float uvy = 1.0f / (float)texture->height();
tex_coords[0].x = source.x * uvx;
tex_coords[0].y = source.y * uvy;
tex_coords[1].x = (source.x + source.w ) * uvx;
tex_coords[1].y = source.y * uvy;
tex_coords[2].x = (source.x + source.w) * uvx;
tex_coords[2].y = (source.y + source.h) * uvy;
tex_coords[3].x = source.x * uvx;
tex_coords[3].y = (source.y + source.h) * uvy;
}
}

View File

@ -0,0 +1,24 @@
#pragma once
#include <blah/graphics/texture.h>
#include <blah/math/rect.h>
namespace Blah
{
struct Subtexture
{
TextureRef texture;
Rect source;
Rect frame;
Vec2 draw_coords[4]; // draw coords are automatically assigned through Update method
Vec2 tex_coords[4]; // tex coords are automatically assigned through Update method
Subtexture();
Subtexture(const TextureRef& texture, Rect source, Rect frame);
float width() const { return frame.w; }
float height() const { return frame.h; }
// updates the draw and tex coords
void update();
};
}

136
public/blah/filesystem.cpp Normal file
View File

@ -0,0 +1,136 @@
#include <blah/filesystem.h>
#include <blah/internal/platform.h>
using namespace Blah;
bool File::exists(const FilePath& path)
{
return Internal::Platform::file_exists(path.cstr());
}
bool File::remove(const FilePath& path)
{
return Internal::Platform::file_delete(path.cstr());
}
bool Directory::create(const FilePath& path)
{
return Internal::Platform::dir_create(path.cstr());
}
bool Directory::exists(const FilePath& path)
{
return Internal::Platform::dir_exists(path.cstr());
}
bool Directory::remove(const FilePath& path)
{
return Internal::Platform::dir_delete(path.cstr());
}
List<FilePath> Directory::enumerate(const FilePath& path, bool recursive)
{
List<FilePath> list;
// get files
Internal::Platform::dir_enumerate(list, path.cstr(), recursive);
// normalize path names
for (auto& it : list)
it.replace('\\', '/');
return list;
}
void Directory::explore(const FilePath& path)
{
Internal::Platform::dir_explore(path);
}
FilePath Path::get_file_name(const FilePath& path)
{
const char* cstr = path.cstr();
for (int n = path.length() - 2; n >= 0; n --)
if (*(cstr + n) == '/')
{
return FilePath(cstr + n + 1);
}
return path;
}
FilePath Path::get_file_name_no_ext(const FilePath& path)
{
FilePath filename = get_file_name(path);
return get_path_no_ext(filename);
}
FilePath Path::get_path_no_ext(const FilePath& path)
{
const char* cstr = path.cstr();
for (int n = 0; n < path.length(); n++)
if (*(cstr + n) == '.')
return FilePath(cstr, cstr + n);
return path;
}
FilePath Path::get_directory_name(const FilePath& path)
{
FilePath directory = path;
while (directory.ends_with("/"))
directory = directory.substr(0, -1);
int last = directory.last_index_of('/');
if (last >= 0)
directory = directory.substr(0, last + 1);
return directory;
}
FilePath Path::get_path_after(const FilePath& path, const FilePath& after)
{
for (int i = 0; i < path.length(); i++)
{
FilePath substr(path.begin() + i, path.end());
if (substr.starts_with(after, false))
return FilePath(path.begin() + i + after.length(), path.end());
}
return path;
}
FilePath Path::normalize(const FilePath& path)
{
FilePath normalized;
int len = path.length();
for (int n = 0; n < len; n++)
{
// normalize slashes
if (path[n] == '\\' || path[n] == '/')
{
if (normalized.length() == 0 || normalized[normalized.length() - 1] != '/')
normalized.append('/');
}
// move up a directory
else if (path[n] == '.' && n < len - 1 && path[n + 1] == '.')
{
// search backwards for last /
bool couldMoveUp = false;
for (int k = normalized.length() - 2; k >= 0; k--)
if (normalized[k] == '/')
{
normalized = normalized.substr(0, k);
couldMoveUp = true;
break;
}
if (!couldMoveUp)
normalized.append('.');
else
n++;
}
else
normalized.append(path[n]);
}
return normalized;
}

41
public/blah/filesystem.h Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include <blah/containers/list.h>
#include <blah/containers/str.h>
namespace Blah
{
using FilePath = StrOf<265>;
enum class FileMode
{
None = 0,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
};
namespace Directory
{
bool create(const FilePath& path);
bool exists(const FilePath& path);
bool remove(const FilePath& path);
List<FilePath> enumerate(const FilePath& str, bool recursive = true);
void explore(const FilePath& path);
}
namespace File
{
bool exists(const FilePath& path);
bool remove(const FilePath& path);
}
namespace Path
{
FilePath get_file_name(const FilePath& path);
FilePath get_file_name_no_ext(const FilePath& path);
FilePath get_path_no_ext(const FilePath& path);
FilePath get_path_after(const FilePath& path, const FilePath& after);
FilePath get_directory_name(const FilePath& path);
FilePath normalize(const FilePath& path);
}
}

View File

@ -0,0 +1,36 @@
#pragma once
#include <blah/graphics/graphics.h>
#include <blah/graphics/texture.h>
#include <blah/containers/stacklist.h>
#include <memory>
namespace Blah
{
typedef StackList<TextureRef, BLAH_ATTACHMENTS> Attachments;
class FrameBuffer
{
public:
virtual ~FrameBuffer() = default;
// Gets an Attachment from the FrameBuffer
virtual Attachments& attachments() = 0;
// Gets an Attachment from the FrameBuffer
virtual const Attachments& attachments() const = 0;
// Gets the width of the FrameBuffer
virtual int width() const = 0;
// Gets the height of the FrameBuffer
virtual int height() const = 0;
// Returns true if the FrameBuffer is valid
virtual bool is_valid() const = 0;
// Destroys the given FrameBuffer
virtual void dispose() = 0;
};
typedef std::shared_ptr<FrameBuffer> FrameBufferRef;
}

View File

@ -0,0 +1,340 @@
#include <blah/graphics/graphics.h>
#include <blah/graphics/texture.h>
#include <blah/graphics/framebuffer.h>
#include <blah/graphics/mesh.h>
#include <blah/graphics/material.h>
#include <blah/graphics/shader.h>
#include <blah/log.h>
#include <blah/internal/graphics.h>
#include <blah/images/image.h>
#include <string.h>
using namespace Blah;
namespace
{
// active graphics device
Internal::GraphicsDevice* device;
// active graphics device info
Internal::GraphicsDeviceInfo* device_info;
// list of possible device info
Internal::GraphicsDeviceInfo* device_options[] =
{
&Internal::OpenGL_DeviceInfo
};
}
const BlendMode BlendMode::Normal = BlendMode(
BlendOp::Add,
BlendFactor::One,
BlendFactor::OneMinusSrcAlpha,
BlendOp::Add,
BlendFactor::One,
BlendFactor::OneMinusSrcAlpha,
BlendMask::RGBA,
0xffffffff
);
GfxAPI Internal::Graphics::pick_api()
{
for (int i = 0; i < (int)GfxAPI::Count; i++)
{
if (device_options[i]->supported())
return device_options[i]->api;
}
return GfxAPI::Any;
}
bool Internal::Graphics::init(GfxAPI api)
{
for (int i = 0; i < (int)GfxAPI::Count; i++)
{
if (device_options[i]->api == api)
{
device_info = device_options[i];
device = device_info->create();
if (device != nullptr)
{
device->startup();
if (device->valid)
break;
device_info->destroy(device);
device = nullptr;
}
}
}
return device != nullptr && device->valid;
}
const GraphicsInfo* Graphics::info()
{
if (device == nullptr || !device->valid)
return nullptr;
return &device->info;
}
void Internal::Graphics::shutdown()
{
if (device != nullptr && device_info != nullptr)
{
device->shutdown();
device_info->destroy(device);
device = nullptr;
device_info = nullptr;
}
}
void Internal::Graphics::frame()
{
if (device != nullptr && device->valid)
device->update();
}
void Internal::Graphics::before_render()
{
if (device != nullptr && device->valid)
device->before_render();
}
void Internal::Graphics::after_render()
{
if (device != nullptr && device->valid)
device->after_render();
}
TextureRef Graphics::create_texture(const Image& image)
{
auto tex = create_texture(image.width, image.height, TextureFormat::RGBA);
tex->set_data((unsigned char*)image.pixels);
return tex;
}
TextureRef Graphics::create_texture(int width, int height, unsigned char* rgba)
{
auto tex = create_texture(width, height, TextureFormat::RGBA);
tex->set_data(rgba);
return tex;
}
TextureRef Graphics::create_texture(int width, int height, TextureFormat format)
{
BLAH_ASSERT(width > 0 && height > 0, "Texture width and height must be larger than 0");
BLAH_ASSERT((int)format > (int)TextureFormat::None && (int)format < (int)TextureFormat::Count, "Invalid texture format");
BLAH_ASSERT(device != nullptr && device->valid, "The graphics device has not been created");
return device->create_texture(width, height, TextureFilter::Linear, TextureWrap::Repeat, TextureWrap::Repeat, format);
}
TextureRef Graphics::create_texture(Stream& stream)
{
Image img = Image(stream);
if (img.pixels != nullptr && img.width > 0 && img.height > 0)
{
auto tex = create_texture(img.width, img.height, TextureFormat::RGBA);
tex->set_data((unsigned char*)img.pixels);
return tex;
}
return TextureRef();
}
TextureRef Graphics::create_texture(const char* file)
{
Image img = Image(file);
if (img.pixels != nullptr)
{
auto tex = create_texture(img.width, img.height, TextureFormat::RGBA);
tex->set_data((unsigned char*)img.pixels);
return tex;
}
return TextureRef();
}
FrameBufferRef Graphics::create_framebuffer(int width, int height)
{
static const TextureFormat attachment = TextureFormat::RGBA;
return create_framebuffer(width, height, &attachment, 1);
}
FrameBufferRef Graphics::create_framebuffer(int width, int height, const TextureFormat* attachments, int attachment_count)
{
BLAH_ASSERT(width > 0 && height > 0, "FrameBuffer width and height must be larger than 0");
BLAH_ASSERT(attachment_count <= BLAH_MAX_FRAMEBUFFER_ATTACHMENTS, "Exceeded maximum attachment count");
BLAH_ASSERT(attachment_count > 0, "At least one attachment must be provided");
for (int i = 0; i < attachment_count; i++)
BLAH_ASSERT((int)attachments[i] > (int)TextureFormat::None && (int)attachments[i] < (int)TextureFormat::Count, "Invalid texture format");
BLAH_ASSERT(device != nullptr && device->valid, "The graphics device has not been created");
return device->create_framebuffer(width, height, attachments, attachment_count);
}
ShaderRef Graphics::create_shader(const ShaderData* data)
{
BLAH_ASSERT(device != nullptr && device->valid, "The graphics device has not been created");
return device->create_shader(data);
}
MaterialRef Graphics::create_material(const ShaderRef& shader)
{
BLAH_ASSERT(device != nullptr && device->valid, "The graphics device has not been created");
BLAH_ASSERT(shader && shader->is_valid(), "The provided shader is invalid");
// TODO:
// use a pool for Materials
return MaterialRef(new Material(shader));
}
MeshRef Graphics::create_mesh()
{
BLAH_ASSERT(device != nullptr && device->valid, "Graphics device has not been created");
return device->create_mesh();
}
RenderCall::RenderCall()
{
blend = BlendMode::Normal;
target = Graphics::backbuffer;
mesh = MeshRef();
material = MaterialRef();
has_viewport = false;
has_scissor = false;
viewport = Rect();
scissor = Rect();
index_start = 0;
index_count = 0;
instance_count = 0;
depth = Compare::None;
cull = Cull::None;
}
void Graphics::render(const RenderCall& render_call)
{
BLAH_ASSERT(device != null && device->valid, "Graphics device has not been created");
// Validate Material
if (!render_call.material || !render_call.material->is_valid())
{
Log::warn("Trying to draw with an invalid Material");
return;
}
// Validate Shader
if (!render_call.material->shader() || !render_call.material->shader()->is_valid())
{
Log::warn("Trying to draw with an invalid Shader");
return;
}
// Validate Mesh
if (!render_call.mesh || !render_call.mesh->is_valid())
{
Log::warn("Trying to draw with an invalid Mesh");
return;
}
// Validate FrameBuffer
if (render_call.target && !render_call.target->is_valid())
{
Log::warn("Trying to draw with an invalid FrameBuffer");
return;
}
// copy call
RenderCall call = render_call;
// Validate Index Count
int64_t index_count = call.mesh->index_count();
if (call.index_start + call.index_count > index_count)
{
Log::warn(
"Trying to draw more indices than exist in the index buffer (%i-%i / %i)",
call.index_start,
call.index_start + call.index_count,
index_count);
if (call.index_start > call.index_count)
return;
call.index_count = call.index_count - call.index_start;
}
// Validate Instance Count
int64_t instance_count = call.mesh->instance_count();
if (call.instance_count > instance_count)
{
Log::warn(
"Trying to draw more instances than exist in the index buffer (%i / %i)",
call.instance_count,
instance_count);
call.instance_count = instance_count;
}
// Validate Viewport
if (!call.has_viewport)
{
Point drawSize;
if (!call.target)
drawSize = Point(App::draw_width(), App::draw_height());
else
drawSize = Point(call.target->width(), call.target->height());
call.viewport.x = 0;
call.viewport.y = 0;
call.viewport.w = (float)drawSize.x;
call.viewport.h = (float)drawSize.y;
}
else
{
if (call.viewport.x < 0)
{
call.viewport.w += call.viewport.x;
call.viewport.x = 0;
}
if (call.viewport.y < 0)
{
call.viewport.h += call.viewport.y;
call.viewport.y = 0;
}
if (call.viewport.w < 0)
call.viewport.w = 0;
if (call.viewport.h < 0)
call.viewport.h = 0;
}
// Validate Scissor
if (call.has_scissor)
{
if (call.scissor.x < 0)
{
call.scissor.w += call.scissor.x;
call.scissor.x = 0;
}
if (call.scissor.y < 0)
{
call.scissor.h += call.scissor.y;
call.scissor.y = 0;
}
if (call.scissor.w < 0)
call.scissor.w = 0;
if (call.scissor.h < 0)
call.scissor.h = 0;
}
device->render(&call);
}
void Graphics::clear(const FrameBufferRef& target, uint32_t rgba)
{
BLAH_ASSERT(device != null && device->valid, "Graphics device has not been created");
device->clear(target, rgba);
}

View File

@ -0,0 +1,349 @@
#pragma once
#include <inttypes.h>
#include <blah/app.h>
#include <blah/math/point.h>
#include <blah/math/rect.h>
#include <blah/containers/str.h>
#include <memory>
#define BLAH_ATTACHMENTS 16
#define BLAH_UNIFORMS 16
#define BLAH_ATTRIBUTES 16
#define BLAH_ATTRIBUTE_NAME 32
#define BLAH_UNIFORM_NAME 32
namespace Blah
{
class Stream;
struct Image;
class Texture;
typedef std::shared_ptr<Texture> TextureRef;
class FrameBuffer;
typedef std::shared_ptr<FrameBuffer> FrameBufferRef;
class Material;
typedef std::shared_ptr<Material> MaterialRef;
class Shader;
typedef std::shared_ptr<Shader> ShaderRef;
class Mesh;
typedef std::shared_ptr<Mesh> MeshRef;
struct GraphicsInfo
{
GfxAPI api;
bool instancing;
bool origin_bottom_left;
int max_texture_size;
};
enum class TextureFilter
{
None,
Linear,
Nearest
};
enum class TextureWrap
{
None,
Clamp,
Repeat
};
enum class TextureFormat
{
None,
R,
RG,
RGB,
RGBA,
DepthStencil,
Count
};
enum class Compare
{
None,
Always,
Never,
Less,
Equal,
LessOrEqual,
Greater,
NotEqual,
GreatorOrEqual
};
enum class Cull
{
None = 0,
Front = 1,
Back = 2,
Both = 3
};
enum class BlendOp
{
Add,
Subtract,
ReverseSubtract,
Min,
Max
};
enum class BlendFactor
{
Zero,
One,
SrcColor,
OneMinusSrcColor,
DstColor,
OneMinusDstColor,
SrcAlpha,
OneMinusSrcAlpha,
DstAlpha,
OneMinusDstAlpha,
ConstantColor,
OneMinusConstantColor,
ConstantAlpha,
OneMinusConstantAlpha,
SrcAlphaSaturate,
Src1Color,
OneMinusSrc1Color,
Src1Alpha,
OneMinusSrc1Alpha
};
enum class BlendMask
{
None = 0,
Red = 1,
Green = 2,
Blue = 4,
Alpha = 8,
RGB = Red | Green | Blue,
RGBA = Red | Green | Blue | Alpha,
};
struct BlendMode
{
BlendOp colorOp;
BlendFactor colorSrc;
BlendFactor colorDst;
BlendOp alphaOp;
BlendFactor alphaSrc;
BlendFactor alphaDst;
BlendMask mask;
uint32_t rgba;
BlendMode() = default;
BlendMode(BlendOp op, BlendFactor src, BlendFactor dst)
{
colorOp = op;
colorSrc = src;
colorDst = dst;
alphaOp = op;
alphaSrc = src;
alphaDst = dst;
mask = BlendMask::RGBA;
rgba = 0xffffffff;
}
BlendMode(BlendOp rgbOp, BlendFactor rgbSrc, BlendFactor rgbDst, BlendOp aOp, BlendFactor aSrc, BlendFactor aDst, BlendMask blendMask, uint32_t blendColor)
{
colorOp = rgbOp;
colorSrc = rgbSrc;
colorDst = rgbDst;
alphaOp = aOp;
alphaSrc = aSrc;
alphaDst = aDst;
mask = blendMask;
rgba = blendColor;
}
inline bool operator==(const BlendMode& rhs) const {
return colorOp == rhs.colorOp && colorSrc == rhs.colorSrc && colorDst == rhs.colorDst &&
alphaOp == rhs.alphaOp && alphaSrc == rhs.alphaSrc && alphaDst == rhs.alphaDst &&
mask == rhs.mask && rgba == rhs.rgba;
}
inline bool operator!=(const BlendMode& rhs) const { return !(*this == rhs); }
static const BlendMode Normal;
};
enum class UniformType
{
None,
Float,
Float2,
Float3,
Float4,
Mat3x2,
Mat4x4,
Texture
};
enum class VertexSemantics
{
None,
Position,
Normal,
Bitangent,
Color0,
Color1,
Color2,
Color3,
Indices,
Weight,
Texcoord0,
Texcoord1,
Texcoord2,
Texcoord3,
Texcoord4,
Texcoord5,
Texcoord6,
Texcoord7
};
enum class VertexAttributeType
{
None,
Byte,
Short,
Int,
Float
};
struct VertexAttribute
{
int index;
VertexSemantics semantics;
VertexAttributeType type;
int components;
bool normalized;
};
struct ShaderData
{
const char* vertex;
const char* fragment;
};
struct ShaderUniform
{
StrOf<BLAH_UNIFORM_NAME> name;
UniformType type;
int array_length;
};
struct ShaderAttribute
{
StrOf<BLAH_ATTRIBUTE_NAME> name;
StrOf<BLAH_ATTRIBUTE_NAME> semantic_name;
int semantic_location;
};
struct RenderCall
{
// Framebuffer to draw to
FrameBufferRef target;
// Mesh to draw with
MeshRef mesh;
// Material to draw with
MaterialRef material;
// Whether the Render Call should use a specific viewport
bool has_viewport;
// Whether the Render Call should use a scissor rectangle
bool has_scissor;
// The viewport (only used if hasViewport is true)
Rect viewport;
// The scissor rectangle (only used if hasScissor is true)
Rect scissor;
// First index in the Mesh to draw from
int64_t index_start;
// Total amount of indices to draw from the Mesh
int64_t index_count;
// Total amount of instances to draw from the Mesh
int64_t instance_count;
// Depth Compare Function
Compare depth;
// Cull Mode
Cull cull;
// Blend Mode
BlendMode blend;
// Initializes a default Render Call
RenderCall();
};
namespace Graphics
{
const FrameBufferRef backbuffer;
// Gets graphics information from the graphics device
const GraphicsInfo* info();
// Creates a new Texture.
// If the Texture creation fails, it will return an invalid TextureRef.
TextureRef create_texture(const Image& image);
// Creates a new Texture.
// If the Texture creation fails, it will return an invalid TextureRef.
TextureRef create_texture(int width, int height, unsigned char* rgba);
// Creates a new Texture.
// If the Texture creation fails, it will return an invalid TextureRef.
TextureRef create_texture(int width, int height, TextureFormat format);
// Creates a new Texture from a Stream.
// If the Texture creation fails, it will return an invalid TextureRef.
TextureRef create_texture(Stream& stream);
// Creates a new Texture from a File.
// If the Texture creation fails, it will return an invalid TextureRef.
TextureRef create_texture(const char* file);
// Creates a new FrameBuffer with a single Color attachment
// If the FrameBuffer creation fails, it will return an invalid FrameBufferRef.
FrameBufferRef create_framebuffer(int width, int height);
// Creates a new FrameBuffer with the given Texture Attachments. You must provide at least one Attachment.
// If the FrameBuffer creation fails, it will return an invalid FrameBufferRef.
FrameBufferRef create_framebuffer(int width, int height, const TextureFormat* attachments, int attachmentCount);
// Creates a Shader with the given Shader Data.
// If the Shader creation fails, it will return an invalid ShaderRef.
ShaderRef create_shader(const ShaderData* data);
// Creates a new Material from the given Shader.
// If the Shader is invalid, it will return an invalid MaterialRef.
MaterialRef create_material(const ShaderRef& shader);
// Creates a new Mesh.
// If the Mesh creation fails, it will return an invalid Mesh.
MeshRef create_mesh();
// Submits and Flushes the given render call to the graphics device.
void render(const RenderCall& render_call);
// Clears the given FrameBuffer to the given color.
void clear(const FrameBufferRef& target, uint32_t rgba);
}
}

View File

@ -0,0 +1,252 @@
#include <blah/graphics/material.h>
using namespace Blah;
namespace
{
size_t calc_uniform_size(const ShaderUniform& uniform)
{
size_t components = 0;
switch (uniform.type)
{
case UniformType::Float: components = 1; break;
case UniformType::Float2: components = 2; break;
case UniformType::Float3: components = 3; break;
case UniformType::Float4: components = 4; break;
case UniformType::Mat3x2: components = 6; break;
case UniformType::Mat4x4: components = 16; break;
default:
BLAH_ERROR("Unespected Uniform Type");
break;
}
return components * uniform.array_length;
}
}
Material::Material(const ShaderRef& shader)
{
m_shader = shader;
m_data = nullptr;
m_disposed = false;
// invalid shader
if (!m_shader || !m_shader->is_valid())
{
m_disposed = true;
return;
}
Uniforms uniforms = shader->uniforms();
StackList<size_t, BLAH_UNIFORMS> float_offsets;
size_t float_size = 0;
for (auto& uniform : uniforms)
{
if (uniform.type == UniformType::None)
continue;
if (uniform.type == UniformType::Texture)
{
for (int i = 0; i < uniform.array_length; i ++)
m_textures.add(TextureRef());
continue;
}
float_offsets.add(float_size);
float_size += calc_uniform_size(uniform);
}
m_data = new float[float_size];
memset(m_data, 0, sizeof(float) * float_size);
for (auto& it : float_offsets)
m_floats.add(m_data + it);
}
Material::~Material()
{
dispose();
}
const ShaderRef Material::shader() const
{
return m_shader;
}
void Material::set_texture(const char* name, const TextureRef& texture, int index)
{
BLAH_ASSERT(!m_disposed, "Material has been disposed");
BLAH_ASSERT(m_shader && m_shader->is_valid(), "Material Shader is invalid");
if (m_textures.count() > 0)
{
int offset = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type != UniformType::Texture)
continue;
if (strcmp(uniform.name, name) == 0)
{
m_textures[offset + index] = texture;
return;
}
offset += uniform.array_length;
if (offset + index >= m_textures.count())
break;
}
}
Log::warn("No Texture Uniform '%s' at index [%i] exists", name, index);
}
TextureRef Material::get_texture(const char* name, int index) const
{
BLAH_ASSERT(!m_disposed, "Material has been disposed");
BLAH_ASSERT(m_shader && m_shader->is_valid(), "Material Shader is invalid");
int offset = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type != UniformType::Texture)
continue;
if (strcmp(uniform.name, name) == 0)
return m_textures[offset + index];
offset += uniform.array_length;
if (offset + index >= m_textures.count())
break;
}
Log::warn("No Texture Uniform '%s' at index [%i] exists", name, index);
return TextureRef();
}
TextureRef Material::get_texture(int slot, int index) const
{
BLAH_ASSERT(!m_disposed, "Material has been disposed");
BLAH_ASSERT(m_shader && m_shader->is_valid(), "Material Shader is invalid");
int offset = 0;
int s = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type == UniformType::Texture)
{
if (s == slot)
return m_textures[offset + index];
offset += uniform.array_length;
if (offset + index >= m_textures.count())
break;
}
s++;
}
Log::warn("No Texture Uniform ['%i'] at index [%i] exists", slot, index);
return TextureRef();
}
void Material::set_value(const char* name, const float* value, int64_t length)
{
BLAH_ASSERT(!m_disposed, "Material has been disposed");
BLAH_ASSERT(m_shader && m_shader->is_valid(), "Material Shader is invalid");
BLAH_ASSERT(length >= 0, "Length must be >= 0");
int index = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type == UniformType::Texture || uniform.type == UniformType::None)
continue;
if (strcmp(uniform.name, name) == 0)
{
size_t max = calc_uniform_size(uniform);
if (length > max)
{
Log::warn("Exceeding length of Uniform '%s' (%i / %i)", name, length, max);
length = max;
}
memcpy(m_floats[index], value, sizeof(float) * length);
return;
}
index++;
}
Log::warn("No Uniform '%s' exists", name);
}
const float* Material::get_value(const char* name, int64_t* length) const
{
BLAH_ASSERT(!m_disposed, "Material has been disposed");
BLAH_ASSERT(m_shader && m_shader->is_valid(), "Material Shader is invalid");
int index = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type == UniformType::Texture || uniform.type == UniformType::None)
continue;
if (strcmp(uniform.name, name) == 0)
{
if (length != nullptr)
*length = calc_uniform_size(uniform);
return m_floats[index];
}
index++;
}
*length = 0;
return nullptr;
Log::warn("No Uniform '%s' exists", name);
}
const float* Material::get_value(int slot, int64_t* length) const
{
BLAH_ASSERT(!m_disposed, "Material has been disposed");
BLAH_ASSERT(m_shader && m_shader->is_valid(), "Material Shader is invalid");
int index = 0;
int s = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type != UniformType::Texture && uniform.type != UniformType::None)
{
if (index == slot)
{
if (length != nullptr)
*length = calc_uniform_size(uniform);
return m_floats[index];
}
index++;
}
s++;
}
*length = 0;
return nullptr;
Log::warn("No Uniform [%i] exists", slot);
}
bool Material::is_valid() const
{
return !m_disposed && m_shader && m_shader->is_valid();
}
void Material::dispose()
{
delete[] m_data;
m_data = nullptr;
m_shader.reset();
m_textures.clear();
m_floats.clear();
}

View File

@ -0,0 +1,66 @@
#pragma once
#include <blah/graphics/texture.h>
#include <blah/graphics/shader.h>
#include <blah/containers/list.h>
#include <memory>
namespace Blah
{
class Material
{
public:
Material(const ShaderRef& shader);
~Material();
Material(const Material& src) = delete;
Material(Material&& src) = delete;
Material& operator=(const Material& src) = delete;
Material& operator=(Material&& src) = delete;
// Returns the Shader assigned to the Material.
const ShaderRef shader() const;
// Sets the texture. If the index is out of bounds, the Material is not valid, the Uniform
// is not a Texture Uniform, or the Uniform does not exist, this will do nothing.
void set_texture(const char* name, const TextureRef& texture, int index = 0);
// Gets the texture.
// If the Uniform does not exist, or the index is out of bounds, this will return
// an invalid Texture reference.
TextureRef get_texture(const char* name, int index = 0) const;
// Gets the texture from the given slot.
// If the slot is not a Texture Uniform, or the index is out of bounds, this will return
// an invalid Texture reference.
TextureRef get_texture(int slot, int index = 0) const;
// Sets the value. Length is the total amount of values to set. For example, if the Uniform type
// is a float2, and there are 4 elements, the maximum length should be 8.
void set_value(const char* name, const float* value, int64_t length);
// Gets a pointer to the values of the given Uniform, or nullptr if it doesn't exist.
// Length is the total amount of values. For example, if the Uniform type
// is a float2, and there are 4 elements, the length should be 8.
const float* get_value(const char* name, int64_t* length = nullptr) const;
// Gets a pointer to the values of the given Uniform, or nullptr if it doesn't exist.
// Length is the total amount of values. For example, if the Uniform type
// is a float2, and there are 4 elements, the length should be 8.
const float* get_value(int slot, int64_t* length = nullptr) const;
// Returns true if the Material is valid
bool is_valid() const;
// Destroys the Material
void dispose();
private:
ShaderRef m_shader;
List<TextureRef> m_textures;
List<float*> m_floats;
float* m_data;
bool m_disposed;
};
typedef std::shared_ptr<Material> MaterialRef;
}

View File

@ -0,0 +1,47 @@
#pragma once
#include <inttypes.h>
#include <blah/graphics/graphics.h>
#include <memory>
namespace Blah
{
class Mesh
{
public:
virtual ~Mesh() = default;
// Sets the Vertex Format of the Mesh
virtual void vertex_format(const VertexAttribute* attributes, int attribute_count, int stride = -1) = 0;
// Sets the Instance Format of the Mesh
virtual void instance_format(const VertexAttribute* attributes, int attribute_count, int stride = -1) = 0;
// Uploads the given index buffer to the Mesh
virtual void index_data(const void* indices, int64_t count) = 0;
// Uploads the given vertex buffer to the Mesh
// Note you must call vertex_format at least once before uploading vertices.
virtual void vertex_data(const void* vertices, int64_t count) = 0;
// Uploads the given instance buffer to the Mesh
// Note you must call instance_format at least once before uploading instances.
virtual void instance_data(const void* instances, int64_t count) = 0;
// Gets the index count of the Mesh
virtual int64_t index_count() const = 0;
// Gets the vertex count of the Mesh
virtual int64_t vertex_count() const = 0;
// Gets the instance count of the Mesh
virtual int64_t instance_count() const = 0;
// Returns true if the Mesh is valid
virtual bool is_valid() const = 0;
// Destroys the given Mesh
virtual void dispose() = 0;
};
typedef std::shared_ptr<Mesh> MeshRef;
}

View File

@ -0,0 +1,36 @@
#pragma once
#include <blah/graphics/graphics.h>
#include <blah/containers/stacklist.h>
#include <memory>
namespace Blah
{
typedef StackList<ShaderUniform, BLAH_UNIFORMS> Uniforms;
typedef StackList<ShaderAttribute, BLAH_ATTRIBUTES> Attributes;
class Shader
{
public:
virtual ~Shader() = default;
// Gets a list of Shader Uniforms from Shader
virtual Uniforms& uniforms() = 0;
// Gets a list of Shader Uniforms from Shader
virtual const Uniforms& uniforms() const = 0;
// Gets a list of Shader Attributes from Shader
virtual Attributes& attributes() = 0;
// Gets a list of Shader Attributes from Shader
virtual const Attributes& attributes() const = 0;
// Returns true if the Shader is valid
virtual bool is_valid() const = 0;
// Destroys the given Shader
virtual void dispose() = 0;
};
typedef std::shared_ptr<Shader> ShaderRef;
}

View File

@ -0,0 +1,57 @@
#pragma once
#include <blah/graphics/graphics.h>
#include <memory>
namespace Blah
{
class Texture
{
public:
virtual ~Texture() = default;
// gets the width of the texture
virtual int width() const = 0;
// gets the height of the texture
virtual int height() const = 0;
// Gets the format of the Texture
virtual TextureFormat format() const = 0;
// Sets the filter of the Texture
virtual void set_filter(TextureFilter filter) = 0;
// Gets the filter of the Texture
virtual TextureFilter get_filter() const = 0;
// Sets the wrap of the Texture
virtual void set_wrap(TextureWrap x, TextureWrap y) = 0;
// Gets the wrap of the Texture
virtual TextureWrap get_wrap_x() const = 0;
// Gets the wrap of the Texture
virtual TextureWrap get_wrap_y() const = 0;
// Sets the data of the Texture.
// Note that the pixel buffer should be in the same format as the Texture. There is no row padding.
// If the pixel buffer isn't the same size as the texture, it will set the minimum available amount of data.
virtual void set_data(unsigned char* data) = 0;
// Gets the data of the Texture.
// Note that the pixel buffer will be written to in the same format as the Texture,
// and you should allocate enough space for the full texture. There is no row padding.
virtual void get_data(unsigned char* data) = 0;
// Returns true if the Texture is part of a FrameBuffer
virtual bool is_framebuffer() const = 0;
// Returns true if the Texture
virtual bool is_valid() const = 0;
// Destroys the given Texture
virtual void dispose() = 0;
};
typedef std::shared_ptr<Texture> TextureRef;
}

View File

@ -0,0 +1,466 @@
#include <blah/images/aseprite.h>
#include <blah/streams/filestream.h>
#define STBI_NO_STDIO
#define STBI_NO_JPEG
#define STBI_NO_PNG
#define STBI_NO_BMP
#define STBI_NO_PSD
#define STBI_NO_TGA
#define STBI_NO_GIF
#define STBI_NO_HDR
#define STBI_NO_PIC
#define STBI_NO_PNM
#include <blah/third_party/stb_image.h>
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define MUL_UN8(a, b, t) \
((t) = (a) * (uint16_t)(b) + 0x80, ((((t) >> 8) + (t) ) >> 8))
using namespace Blah;
Aseprite::Aseprite()
{
}
Aseprite::Aseprite(const char* path)
{
FileStream fs(path, FileMode::Read);
Parse(fs);
}
Aseprite::Aseprite(Stream& stream)
{
Parse(stream);
}
Aseprite::Aseprite(const Aseprite& src)
{
mode = src.mode;
width = src.width;
height = src.height;
layers = src.layers;
frames = src.frames;
tags = src.tags;
slices = src.slices;
palette = src.palette;
}
Aseprite& Aseprite::operator=(const Aseprite& src)
{
mode = src.mode;
width = src.width;
height = src.height;
layers = src.layers;
frames = src.frames;
tags = src.tags;
slices = src.slices;
palette = src.palette;
return *this;
}
Aseprite::Aseprite(Aseprite&& src) noexcept
{
mode = src.mode;
width = src.width;
height = src.height;
layers = std::move(src.layers);
frames = std::move(src.frames);
tags = std::move(src.tags);
slices = std::move(src.slices);
palette = std::move(src.palette);
}
Aseprite& Aseprite::operator=(Aseprite&& src) noexcept
{
mode = src.mode;
width = src.width;
height = src.height;
layers = std::move(src.layers);
frames = std::move(src.frames);
tags = std::move(src.tags);
slices = std::move(src.slices);
palette = std::move(src.palette);
return *this;
}
Aseprite::~Aseprite()
{
}
void Aseprite::Parse(Stream& stream)
{
if (!stream.is_readable())
{
BLAH_ERROR("Stream is not readable");
return;
}
int frameCount = 0;
// header
{
// filesize
stream.read<uint32_t>(Endian::Little);
// magic number
auto magic = stream.read<uint16_t>(Endian::Little);
if (magic != 0xA5E0)
{
BLAH_ERROR("File is not a valid Aseprite file");
return;
}
// main info
frameCount = stream.read<uint16_t>(Endian::Little);
width = stream.read<uint16_t>(Endian::Little);
height = stream.read<uint16_t>(Endian::Little);
mode = static_cast<Aseprite::Modes>(stream.read<uint16_t>(Endian::Little) / 8);
// don't care about other info
stream.read<uint32_t>(Endian::Little); // Flags
stream.read<uint16_t>(Endian::Little); // Speed (deprecated)
stream.read<uint32_t>(Endian::Little); // Should be 0
stream.read<uint32_t>(Endian::Little); // Should be 0
stream.read<uint8_t>(Endian::Little); // Palette entry
stream.seek(stream.position() + 3); // Ignore these bytes
stream.read<uint16_t>(Endian::Little); // Number of colors (0 means 256 for old sprites)
stream.read<int8_t>(Endian::Little); // Pixel width
stream.read<int8_t>(Endian::Little); // Pixel height
stream.seek(stream.position() + 92); // For Future
}
frames.expand(frameCount);
// frames
for (int i = 0; i < frameCount; i ++)
{
auto frameStart = stream.position();
auto frameEnd = frameStart + stream.read<uint32_t>(Endian::Little);
unsigned int chunks = 0;
// frame header
{
auto magic = stream.read<uint16_t>(Endian::Little); // magic number
if (magic != 0xF1FA)
{
BLAH_ERROR("File is not a valid Aseprite file");
return;
}
auto old_chunk_count = stream.read<uint16_t>(Endian::Little);
frames[i].duration = stream.read<uint16_t>(Endian::Little);
stream.seek(stream.position() + 2); // for future
auto new_chunk_count = stream.read<uint32_t>(Endian::Little);
if (old_chunk_count == 0xFFFF)
chunks = new_chunk_count;
else
chunks = old_chunk_count;
}
// make frame image
frames[i].image = Image(width, height);
// frame chunks
for (unsigned int j = 0; j < chunks; j ++)
{
auto chunkStart = stream.position();
auto chunkEnd = chunkStart + stream.read<uint32_t>(Endian::Little);
auto chunkType = static_cast<Chunks>(stream.read<uint16_t>(Endian::Little));
switch (chunkType)
{
case Chunks::Layer: ParseLayer(stream, i); break;
case Chunks::Cel: ParseCel(stream, i, (size_t)chunkEnd); break;
case Chunks::Palette: ParsePalette(stream, i); break;
case Chunks::UserData: ParseUserData(stream, i); break;
case Chunks::FrameTags: ParseTag(stream, i); break;
case Chunks::Slice: ParseSlice(stream, i); break;
default: break;
}
stream.seek(chunkEnd);
}
stream.seek(frameEnd);
}
}
void Aseprite::ParseLayer(Stream& stream, int frame)
{
auto layer = layers.expand(1);
layer->flag = static_cast<LayerFlags>(stream.read<uint16_t>(Endian::Little));
layer->visible = ((int)layer->flag & (int)LayerFlags::Visible) == (int)LayerFlags::Visible;
layer->type = static_cast<LayerTypes>(stream.read<uint16_t>(Endian::Little));
layer->child_level = stream.read<uint16_t>(Endian::Little);
stream.read<uint16_t>(Endian::Little); // width
stream.read<uint16_t>(Endian::Little); // height
layer->blendmode = stream.read<uint16_t>(Endian::Little);
layer->alpha = stream.read<uint8_t>(Endian::Little);
stream.seek(stream.position() + 3); // for future
layer->name.set_length(stream.read<uint16_t>(Endian::Little));
stream.read(layer->name.cstr(), layer->name.length());
layer->userdata.color = 0xffffff;
layer->userdata.text = "";
last_userdata = &(layer->userdata);
}
void Aseprite::ParseCel(Stream& stream, int frameIndex, size_t maxPosition)
{
Frame& frame = frames[frameIndex];
auto cel = frame.cels.expand(1);
cel->layer_index = stream.read<uint16_t>(Endian::Little);
cel->x = stream.read<uint16_t>(Endian::Little);
cel->y = stream.read<uint16_t>(Endian::Little);
cel->alpha = stream.read<uint8_t>(Endian::Little);
cel->linked_frame_index = -1;
cel->linked_cel_index = -1;
auto celType = stream.read<uint16_t>(Endian::Little);
stream.seek(stream.position() + 7);
// RAW or DEFLATE
if (celType == 0 || celType == 2)
{
auto width = stream.read<uint16_t>(Endian::Little);
auto height = stream.read<uint16_t>(Endian::Little);
auto count = width * height * (int)mode;
cel->image = Image(width, height);
// RAW
if (celType == 0)
{
stream.read(cel->image.pixels, count);
}
// DEFLATE (zlib)
else
{
// this could be optimized to use a buffer on the stack if we only read set chunks at a time
// stbi's zlib doesn't have that functionality though
auto size = maxPosition - stream.position();
if (size > INT32_MAX)
size = INT32_MAX;
if (size < 0)
size = 0;
char* buffer = new char[size];
stream.read(buffer, size);
int olen = width * height * sizeof(Color);
int res = stbi_zlib_decode_buffer((char*)cel->image.pixels, olen, buffer, (int)size);
delete[] buffer;
if (res < 0)
{
BLAH_ERROR("Unable to parse Aseprite file");
return;
}
}
// convert to pixels
// note: we work in-place to save having to store stuff in a buffer
if (mode == Modes::Grayscale)
{
auto src = (unsigned char*)cel->image.pixels;
auto dst = cel->image.pixels;
for (int d = width * height - 1, s = (width * height - 1) * 2; d >= 0; d --, s -= 2)
dst[d] = Color(src[s], src[s], src[s], src[s + 1]);
}
else if (mode == Modes::Indexed)
{
auto src = (unsigned char*)cel->image.pixels;
auto dst = cel->image.pixels;
for (int i = width * height - 1; i >= 0; i --)
dst[i] = palette[src[i]];
}
}
// REFERENCE
// this cel directly references a previous cel
else if (celType == 1)
{
cel->linked_frame_index = stream.read<uint16_t>(Endian::Little);
cel->linked_cel_index = static_cast<int>(frame.cels.count() - 1);
}
// draw to frame if visible
if ((int)layers[cel->layer_index].flag & (int)LayerFlags::Visible)
{
RenderCel(cel, &frame);
}
cel->userdata.color = 0xffffff;
cel->userdata.text = "";
last_userdata = &(cel->userdata);
}
void Aseprite::ParsePalette(Stream& stream, int frame)
{
/* size */ stream.read<uint32_t>(Endian::Little);
auto start = stream.read<uint32_t>(Endian::Little);
auto end = stream.read<uint32_t>(Endian::Little);
stream.seek(stream.position() + 8);
palette.expand(palette.count() + (end - start + 1));
for (int p = 0, len = static_cast<int>(end - start) + 1; p < len; p++)
{
auto hasName = stream.read<uint16_t>(Endian::Little);
palette[start + p] = stream.read<Color>(Endian::Little);
if (hasName & 0xF000)
{
int len = stream.read<uint16_t>(Endian::Little);
stream.seek(stream.position() + len);
}
}
}
void Aseprite::ParseUserData(Stream& stream, int frame)
{
if (last_userdata != nullptr)
{
auto flags = stream.read<uint32_t>(Endian::Little);
// has text
if (flags & (1 << 0))
{
last_userdata->text.set_length(stream.read<uint16_t>(Endian::Little));
stream.read(last_userdata->text.cstr(), last_userdata->text.length());
}
// has color
if (flags & (1 << 1))
last_userdata->color = stream.read<Color>(Endian::Little);
}
}
void Aseprite::ParseTag(Stream& stream, int frame)
{
auto count = stream.read<uint16_t>(Endian::Little);
stream.seek(stream.position() + 8);
for (int t = 0; t < count; t++)
{
auto tag = tags.expand(1);
tag->from = stream.read<uint16_t>(Endian::Little);
tag->to = stream.read<uint16_t>(Endian::Little);
tag->loops = static_cast<LoopDirections>(stream.read<int8_t>(Endian::Little));
stream.seek(stream.position() + 8);
tag->color = Color(stream.read<int8_t>(), stream.read<int8_t>(), stream.read<int8_t>(Endian::Little), 255);
stream.seek(stream.position() + 1);
tag->name.set_length(stream.read<uint16_t>(Endian::Little));
stream.read(tag->name .cstr(), tag->name.length());
}
}
void Aseprite::ParseSlice(Stream& stream, int frame)
{
int count = stream.read<uint32_t>(Endian::Little);
int flags = stream.read<uint32_t>(Endian::Little);
stream.read<uint32_t>(Endian::Little); // reserved
String name;
name.set_length(stream.read<uint16_t>(Endian::Little));
stream.read(name.cstr(), name.length());
for (int s = 0; s < count; s++)
{
auto slice = slices.expand(1);
slice->name = name;
slice->frame = stream.read<uint32_t>(Endian::Little);
slice->origin.x = stream.read<int32_t>(Endian::Little);
slice->origin.y = stream.read<int32_t>(Endian::Little);
slice->width = stream.read<uint32_t>(Endian::Little);
slice->height = stream.read<uint32_t>(Endian::Little);
// 9 slice (ignored atm)
if (flags & (1 << 0))
{
stream.read<int32_t>(Endian::Little);
stream.read<int32_t>(Endian::Little);
stream.read<uint32_t>(Endian::Little);
stream.read<uint32_t>(Endian::Little);
}
// pivot point
slice->has_pivot = false;
if (flags & (1 << 1))
{
slice->has_pivot = true;
slice->pivot.x = stream.read<uint32_t>(Endian::Little);
slice->pivot.y = stream.read<uint32_t>(Endian::Little);
}
slice->userdata.color = 0xffffff;
slice->userdata.text = "";
last_userdata = &(slice->userdata);
}
}
void Aseprite::RenderCel(Cel* cel, Frame* frame)
{
Layer& layer = layers[cel->layer_index];
while (cel->linked_frame_index >= 0)
cel = &(frames[cel->linked_frame_index].cels[cel->linked_cel_index]);
int t;
unsigned char opacity = MUL_UN8(cel->alpha, layer.alpha, t);
if (opacity <= 0)
return;
auto src = cel->image.pixels;
auto srcX = cel->x;
auto srcY = cel->y;
auto srcW = cel->image.width;
auto srcH = cel->image.height;
auto dst = frame->image.pixels;
auto dstW = frame->image.width;
auto dstH = frame->image.height;
// blit pixels
int left = MAX(0, srcX);
int right = MIN(dstW, srcX + srcW);
int top = MAX(0, srcY);
int bottom = MIN(dstH, srcY + srcH);
if (layer.blendmode == 0)
{
for (int dx = left, sx = -MIN(srcX, 0); dx < right; dx++, sx++)
{
for (int dy = top, sy = -MIN(srcY, 0); dy < bottom; dy++, sy++)
{
Color* srcColor = (src + sx + sy * srcW);
Color* dstColor = (dst + dx + dy * dstW);
if (srcColor->a != 0)
{
auto sa = MUL_UN8(srcColor->a, opacity, t);
auto ra = dstColor->a + sa - MUL_UN8(dstColor->a, sa, t);
dstColor->r = (unsigned char)(dstColor->r + (srcColor->r - dstColor->r) * sa / ra);
dstColor->g = (unsigned char)(dstColor->g + (srcColor->g - dstColor->g) * sa / ra);
dstColor->b = (unsigned char)(dstColor->b + (srcColor->b - dstColor->b) * sa / ra);
dstColor->a = (unsigned char)ra;
}
}
}
}
else
{
BLAH_ERROR("Aseprite blendmodes aren't implemented");
}
}

View File

@ -0,0 +1,152 @@
#pragma once
#include <blah/math/color.h>
#include <blah/images/image.h>
#include <blah/containers/str.h>
#include <blah/containers/list.h>
#include <blah/streams/stream.h>
namespace Blah
{
class Aseprite
{
public:
enum class Modes
{
Indexed = 1,
Grayscale = 2,
RGBA = 4
};
enum class Chunks
{
OldPaletteA = 0x0004,
OldPaletteB = 0x0011,
Layer = 0x2004,
Cel = 0x2005,
CelExtra = 0x2006,
Mask = 0x2016,
Path = 0x2017,
FrameTags = 0x2018,
Palette = 0x2019,
UserData = 0x2020,
Slice = 0x2022
};
enum class LoopDirections
{
Forward = 0,
Reverse = 1,
PingPong = 2
};
enum class LayerFlags
{
Visible = 1,
Editable = 2,
LockMovement = 4,
Background = 8,
PreferLinkedCels = 16,
Collapsed = 32,
Reference = 64
};
enum class LayerTypes
{
Normal = 0,
Group = 1
};
struct UserData
{
String text;
Color color;
};
struct Layer;
struct Cel
{
int layer_index = 0;
int linked_frame_index = 0;
int linked_cel_index = 0;
int x = 0;
int y = 0;
unsigned char alpha = 0;
Image image;
UserData userdata;
};
struct Frame
{
int duration = 0;
Image image;
List<Cel> cels;
};
struct Layer
{
LayerFlags flag = (LayerFlags)0;
LayerTypes type = LayerTypes::Normal;
String name;
int child_level = 0;
int blendmode = 0;
unsigned char alpha = 0;
bool visible = true;
UserData userdata;
};
struct Tag
{
String name;
LoopDirections loops = LoopDirections::Forward;
int from = 0;
int to = 0;
Color color;
UserData userdata;
};
struct Slice
{
int frame = 0;
String name;
Point origin;
int width = 0;
int height = 0;
bool has_pivot = false;
Point pivot;
UserData userdata;
};
Modes mode = Modes::RGBA;
int width = 0;
int height = 0;
List<Layer> layers;
List<Frame> frames;
List<Tag> tags;
List<Slice> slices;
List<Color> palette;
Aseprite();
Aseprite(const char* path);
Aseprite(Stream& stream);
Aseprite(const Aseprite& src);
Aseprite& operator=(const Aseprite& src);
Aseprite(Aseprite&& src) noexcept;
Aseprite& operator=(Aseprite&& src) noexcept;
~Aseprite();
private:
UserData* last_userdata = nullptr;
void Parse(Stream& stream);
void ParseLayer(Stream& stream, int frame);
void ParseCel(Stream& stream, int frame, size_t maxPosition);
void ParsePalette(Stream& stream, int frame);
void ParseUserData(Stream& stream, int frame);
void ParseTag(Stream& stream, int frame);
void ParseSlice(Stream& stream, int frame);
void RenderCel(Cel* cel, Frame* frame);
};
}

244
public/blah/images/font.cpp Normal file
View File

@ -0,0 +1,244 @@
#include <blah/images/font.h>
#include <blah/streams/filestream.h>
#include <blah/math/calc.h>
#include <blah/log.h>
using namespace Blah;
#define STBTT_STATIC
#define STB_TRUETYPE_IMPLEMENTATION
#include <blah/third_party/stb_truetype.h>
String GetName(stbtt_fontinfo* font, int nameId)
{
int length = 0;
// get the name
const uint16_t* ptr = (const uint16_t*)stbtt_GetFontNameStr(font, &length,
STBTT_PLATFORM_ID_MICROSOFT,
STBTT_MS_EID_UNICODE_BMP,
STBTT_MS_LANG_ENGLISH,
nameId);
// we want the size in wide chars
length /= 2;
String str;
if (length > 0)
str.append_utf16(ptr, ptr + length, Calc::is_little_endian());
return str;
}
Font::Font()
{
font = nullptr;
data = nullptr;
ascent = 0;
descent = 0;
lineGap = 0;
valid = false;
}
Font::Font(Stream& stream) : Font()
{
Load(stream);
}
Font::Font(const char* path) : Font()
{
FileStream fs(path, FileMode::Read);
if (fs.is_readable())
Load(fs);
}
Font::Font(Font&& src) noexcept
{
font = src.font;
data = src.data;
familyName = src.familyName;
styleName = src.styleName;
ascent = src.ascent;
descent = src.descent;
lineGap = src.lineGap;
valid = src.valid;
src.familyName.clear();
src.styleName.clear();
src.valid = false;
src.font = nullptr;
src.data = nullptr;
}
Font& Font::operator=(Font&& src) noexcept
{
font = src.font;
data = src.data;
familyName = src.familyName;
styleName = src.styleName;
ascent = src.ascent;
descent = src.descent;
lineGap = src.lineGap;
valid = src.valid;
src.familyName.clear();
src.styleName.clear();
src.valid = false;
src.font = nullptr;
src.data = nullptr;
return *this;
}
Font::~Font()
{
dispose();
}
void Font::Load(Stream& stream)
{
dispose();
if (!stream.is_readable())
{
BLAH_ERROR("Unable to load a font as the Stream was not readable");
return;
}
// create data buffer
auto size = stream.length();
data = new unsigned char[size];
stream.read(data, size);
// init font
font = new stbtt_fontinfo();
auto fn = (stbtt_fontinfo*)font;
stbtt_InitFont(fn, data, 0);
familyName = GetName(fn, 1);
styleName = GetName(fn, 2);
// properties
stbtt_GetFontVMetrics(fn, &ascent, &descent, &lineGap);
valid = true;
}
void Font::dispose()
{
delete (stbtt_fontinfo*)font;
delete[] data;
font = nullptr;
data = nullptr;
familyName.dispose();
styleName.dispose();
}
const char* Font::FamilyName() const
{
return familyName.cstr();
}
const char* Font::StyleName() const
{
return styleName.cstr();
}
int Font::Ascent() const
{
return ascent;
}
int Font::Descent() const
{
return descent;
}
int Font::LineGap() const
{
return lineGap;
}
int Font::Height() const
{
return ascent - descent;
}
int Font::LineHeight() const
{
return ascent - descent + lineGap;
}
int Font::GetGlyph(Codepoint codepoint) const
{
if (font == nullptr)
return 0;
return stbtt_FindGlyphIndex((stbtt_fontinfo*)font, codepoint);
}
float Font::GetScale(float size) const
{
if (font == nullptr)
return 0;
return stbtt_ScaleForPixelHeight((stbtt_fontinfo*)font, size);
}
float Font::GetKerning(int glyph1, int glyph2, float scale) const
{
if (font == nullptr)
return 0;
return stbtt_GetGlyphKernAdvance((stbtt_fontinfo*)font, glyph1, glyph2) * scale;
}
Font::Char Font::GetCharacter(int glyph, float scale) const
{
Char ch;
if (font == nullptr)
return ch;
int advance, offsetX, x0, y0, x1, y1;
stbtt_GetGlyphHMetrics((stbtt_fontinfo*)font, glyph, &advance, &offsetX);
stbtt_GetGlyphBitmapBox((stbtt_fontinfo*)font, glyph, scale, scale, &x0, &y0, &x1, &y1);
int w = (x1 - x0);
int h = (y1 - y0);
// define character
ch.glyph = glyph;
ch.width = w;
ch.height = h;
ch.advance = advance * scale;
ch.offsetX = offsetX * scale;
ch.offsetY = (float)y0;
ch.scale = scale;
ch.hasGlyph = (w > 0 && h > 0 && stbtt_IsGlyphEmpty((stbtt_fontinfo*)font, glyph) == 0);
return ch;
}
bool Font::GetBitmap(const Font::Char& ch, Color* pixels) const
{
if (ch.hasGlyph)
{
// we actually use the image buffer as our temporary buffer, and fill the pixels out backwards after
// kinda weird but it works & saves creating more memory
unsigned char* src = (unsigned char*)pixels;
stbtt_MakeGlyphBitmap((stbtt_fontinfo*)font, src, ch.width, ch.height, ch.width, ch.scale, ch.scale, ch.glyph);
int len = ch.width * ch.height;
for (int a = (len - 1) * 4, b = (len - 1); b >= 0; a -= 4, b -= 1)
{
src[a + 0] = src[b];
src[a + 1] = src[b];
src[a + 2] = src[b];
src[a + 3] = src[b];
}
return true;
}
return false;
}
bool Font::IsValid() const
{
return valid;
}

62
public/blah/images/font.h Normal file
View File

@ -0,0 +1,62 @@
#pragma once
#include <blah/streams/stream.h>
#include <blah/images/image.h>
#include <blah/containers/str.h>
namespace Blah
{
typedef uint32_t Codepoint;
class Font
{
public:
struct Char
{
int glyph = 0;
int width = 0;
int height = 0;
float advance = 0;
float offsetX = 0;
float offsetY = 0;
float scale = 0;
bool hasGlyph = false;
};
Font();
Font(Stream& stream);
Font(const char* path);
Font(const Font&) = delete;
Font& operator=(const Font&) = delete;
Font(Font&& src) noexcept;
Font& operator=(Font&& src) noexcept;
~Font();
void dispose();
const char* FamilyName() const;
const char* StyleName() const;
int Ascent() const;
int Descent() const;
int LineGap() const;
int Height() const;
int LineHeight() const;
int GetGlyph(Codepoint codepoint) const;
float GetScale(float size) const;
float GetKerning(int glyph1, int glyph2, float scale) const;
Char GetCharacter(int glyph, float scale) const;
bool GetBitmap(const Char& ch, Color* pixels) const;
bool IsValid() const;
private:
void Load(Stream& stream);
void* font;
unsigned char* data;
String familyName;
String styleName;
int ascent;
int descent;
int lineGap;
bool valid;
};
}

View File

@ -0,0 +1,284 @@
#include <blah/images/image.h>
#include <blah/streams/stream.h>
#include <blah/streams/filestream.h>
#include <blah/log.h>
using namespace Blah;
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_JPEG
#define STBI_ONLY_PNG
#define STBI_ONLY_BMP
#include <blah/third_party/stb_image.h>
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include <blah/third_party/stb_image_write.h>
int Engine_STBI_Read(void *user, char *data, int size)
{
int64_t read = ((Stream*)user)->read(data, size);
return (int)read;
}
void Engine_STBI_Skip(void *user, int n)
{
((Stream*)user)->seek(((Stream*)user)->position() + n);
}
int Engine_STBI_Eof(void *user)
{
int64_t position = ((Stream*)user)->position();
int64_t length = ((Stream*)user)->length();
if (position >= length)
return 1;
return 0;
}
void Engine_STBI_Write(void *context, void *data, int size)
{
((Stream*)context)->write((char*)data, size);
}
Image::Image()
{
width = height = 0;
pixels = nullptr;
}
Image::Image(Stream& stream)
{
width = height = 0;
pixels = nullptr;
from_stream(stream);
}
Image::Image(const char* file)
{
width = height = 0;
pixels = nullptr;
FileStream fs(file, FileMode::Read);
if (fs.is_readable())
from_stream(fs);
}
Image::Image(int width, int height)
{
BLAH_ASSERT(width >= 0 && height >= 0, "Image width and height must be larger than 0");
this->width = width;
this->height = height;
pixels = new Color[width * height];
memset(pixels, 0, (size_t)width * (size_t)height * sizeof(Color));
}
Image::Image(const Image& src)
{
width = src.width;
height = src.height;
pixels = nullptr;
if (src.pixels != nullptr && width > 0 && height > 0)
{
pixels = new Color[width * height];
memcpy(pixels, src.pixels, sizeof(Color) * width * height);
}
}
Image& Image::operator=(const Image& src)
{
width = src.width;
height = src.height;
pixels = nullptr;
if (src.pixels != nullptr && width > 0 && height > 0)
{
pixels = new Color[width * height];
memcpy(pixels, src.pixels, sizeof(Color) * width * height);
}
return *this;
}
Image::Image(Image&& src) noexcept
{
width = src.width;
height = src.height;
pixels = src.pixels;
src.width = src.height = 0;
src.pixels = nullptr;
}
Image& Image::operator=(Image&& src) noexcept
{
width = src.width;
height = src.height;
pixels = src.pixels;
src.width = src.height = 0;
src.pixels = nullptr;
return *this;
}
Image::~Image()
{
dispose();
}
void Image::from_stream(Stream& stream)
{
dispose();
if (!stream.is_readable())
{
BLAH_ERROR("Unable to load image as the Stream was not readable");
return;
}
stbi_io_callbacks callbacks;
callbacks.eof = Engine_STBI_Eof;
callbacks.read = Engine_STBI_Read;
callbacks.skip = Engine_STBI_Skip;
int x, y, comps;
uint8_t* data = stbi_load_from_callbacks(&callbacks, &stream, &x, &y, &comps, 4);
if (data == nullptr)
{
BLAH_ERROR("Unable to load image as the Stream's data was not a valid image");
return;
}
width = x;
height = y;
pixels = (Color*)data;
}
void Image::dispose()
{
delete[] pixels;
pixels = nullptr;
width = height = 0;
}
void Image::premultiply()
{
if (pixels != nullptr)
{
for (int n = 0; n < width * height; n ++)
{
pixels[n].r = (uint8_t)(pixels[n].r * pixels[n].a / 255);
pixels[n].g = (uint8_t)(pixels[n].g * pixels[n].a / 255);
pixels[n].b = (uint8_t)(pixels[n].b * pixels[n].a / 255);
}
}
}
void Image::set_pixels(const RectI& rect, Color* data)
{
for (int y = 0; y < rect.h; y++)
{
int to = rect.x + ((rect.y + y) * width);
int from = (y * rect.w);
memcpy(pixels + to, data + from, sizeof(Color) * rect.w);
}
}
bool Image::save_png(const char* file) const
{
FileStream fs(file, FileMode::Write);
return save_png(fs);
}
bool Image::save_png(Stream& stream) const
{
BLAH_ASSERT(pixels != nullptr, "Image Pixel data cannot be null");
BLAH_ASSERT(width > 0 && height > 0, "Image Width and Height must be larger than 0");
if (stream.is_writable())
{
stbi_write_force_png_filter = 0;
stbi_write_png_compression_level = 0;
if (stbi_write_png_to_func(Engine_STBI_Write, &stream, width, height, 4, pixels, width * 4) != 0)
return true;
else
Log::error("stbi_write_png_to_func failed");
}
else
{
Log::error("Cannot save Image, the Stream is not writable");
}
return false;
}
bool Image::save_jpg(const char* file, int quality) const
{
FileStream fs(file, FileMode::Write);
return save_jpg(fs, quality);
}
bool Image::save_jpg(Stream& stream, int quality) const
{
BLAH_ASSERT(pixels != nullptr, "Image Pixel data cannot be null");
BLAH_ASSERT(width > 0 && height > 0, "Image Width and Height must be larger than 0");
if (quality < 1)
{
Log::warn("jpg quality value should be between 1 and 100; input was %i", quality);
quality = 1;
}
else if (quality > 100)
{
Log::warn("jpg quality value should be between 1 and 100; input was %i", quality);
quality = 100;
}
if (stream.is_writable())
{
if (stbi_write_jpg_to_func(Engine_STBI_Write, &stream, width, height, 4, pixels, quality) != 0)
return true;
else
Log::error("stbi_write_jpg_to_func failed");
}
else
{
Log::error("Cannot save Image, the Stream is not writable");
}
return false;
}
void Image::get_pixels(Color* dest, const Point& destPos, const Point& destSize, RectI sourceRect)
{
// can't be outside of the source image
if (sourceRect.x < 0) sourceRect.x = 0;
if (sourceRect.y < 0) sourceRect.y = 0;
if (sourceRect.x + sourceRect.w > width) sourceRect.w = width - sourceRect.x;
if (sourceRect.y + sourceRect.h > height) sourceRect.h = height - sourceRect.y;
// can't be larger than our destination
if (sourceRect.w > destSize.x - destPos.x)
sourceRect.w = destSize.x - destPos.x;
if (sourceRect.h > destSize.y - destPos.y)
sourceRect.h = destSize.y - destPos.y;
for (int y = 0; y < sourceRect.h; y++)
{
int to = destPos.x + (destPos.y + y) * destSize.x;
int from = sourceRect.x + (sourceRect.y + y) * width;
memcpy(dest + to, pixels + from, sizeof(Color) * (int)sourceRect.w);
}
}
Image Image::get_sub_image(const RectI& sourceRect)
{
Image img(sourceRect.w, sourceRect.h);
get_pixels(img.pixels, Point::zero, Point(img.width, img.height), sourceRect);
return img;
}

View File

@ -0,0 +1,38 @@
#pragma once
#include <blah/math/color.h>
#include <blah/math/rectI.h>
#include <blah/math/point.h>
namespace Blah
{
class Stream;
struct Image
{
int width = 0;
int height = 0;
Color* pixels = nullptr;
Image();
Image(Stream& stream);
Image(const char* file);
Image(int width, int height);
Image(const Image& src);
Image& operator=(const Image& src);
Image(Image&& src) noexcept;
Image& operator=(Image&& src) noexcept;
~Image();
void from_stream(Stream& stream);
void dispose();
void premultiply();
void set_pixels(const RectI& rect, Color* data);
bool save_png(const char* file) const;
bool save_png(Stream& stream) const;
bool save_jpg(const char* file, int quality) const;
bool save_jpg(Stream& stream, int quality) const;
void get_pixels(Color* dest, const Point& destPos, const Point& destSize, RectI sourceRect);
Image get_sub_image(const RectI& sourceRect);
};
}

View File

@ -0,0 +1,336 @@
#include <blah/images/packer.h>
#include <algorithm>
#include <cstring>
using namespace Blah;
Packer::Packer()
: max_size(8192), power_of_two(true), spacing(1), padding(1), dirty(false) { }
Packer::Packer(int max_size, int spacing, bool power_of_two)
: max_size(max_size), power_of_two(power_of_two), spacing(spacing), padding(1), dirty(false) { }
Packer::Packer(Packer&& src) noexcept
{
max_size = src.max_size;
power_of_two = src.power_of_two;
spacing = src.spacing;
padding = src.padding;
dirty = src.dirty;
pages = std::move(src.pages);
entries = std::move(src.entries);
buffer = std::move(src.buffer);
}
Packer& Packer::operator=(Packer&& src) noexcept
{
max_size = src.max_size;
power_of_two = src.power_of_two;
spacing = src.spacing;
padding = src.padding;
dirty = src.dirty;
pages = std::move(src.pages);
entries = std::move(src.entries);
buffer = std::move(src.buffer);
return *this;
}
Packer::~Packer()
{
dispose();
}
void Packer::add(uint64_t id, int width, int height, const Color* pixels)
{
add_entry(id, width, height, pixels);
}
void Packer::add(uint64_t id, const Image& image)
{
add_entry(id, image.width, image.height, image.pixels);
}
void Packer::add(uint64_t id, const String& path)
{
add(id, Image(path.cstr()));
}
void Packer::add_entry(uint64_t id, int w, int h, const Color* pixels)
{
dirty = true;
Entry* entry = entries.expand(1);
entry->id = id;
entry->page = 0;
entry->empty = true;
entry->frame = RectI(0, 0, w, h);
entry->packed = RectI(0, 0, 0, 0);
// trim
int top = 0, left = 0, right = w, bottom = h;
// TOP:
for (int y = 0; y < h; y++)
for (int x = 0, s = y * w; x < w; x++, s++)
if (pixels[s].a > 0)
{
top = y;
goto JUMP_LEFT;
}
JUMP_LEFT:
for (int x = 0; x < w; x++)
for (int y = top, s = x + y * w; y < h; y++, s += w)
if (pixels[s].a > 0)
{
left = x;
goto JUMP_RIGHT;
}
JUMP_RIGHT:
for (int x = w - 1; x >= left; x--)
for (int y = top, s = x + y * w; y < h; y++, s += w)
if (pixels[s].a > 0)
{
right = x + 1;
goto JUMP_BOTTOM;
}
JUMP_BOTTOM:
for (int y = h - 1; y >= top; y--)
for (int x = left, s = x + y * w; x < right; x++, s++)
if (pixels[s].a > 0)
{
bottom = y + 1;
goto JUMP_END;
}
JUMP_END:;
// pixels actually exist in this source
if (right >= left && bottom >= top)
{
entry->empty = false;
// store size
entry->frame.x = -left;
entry->frame.y = -top;
entry->packed.w = (right - left);
entry->packed.h = (bottom - top);
// create pixel data
entry->memoryIndex = buffer.position();
// copy pixels over
if (entry->packed.w == w && entry->packed.h == h)
{
buffer.write((char*)pixels, sizeof(Color) * w * h);
}
else
{
for (int i = 0; i < entry->packed.h; i++)
buffer.write((char*)(pixels + left + (top + i) * entry->frame.w), sizeof(Color) * entry->packed.w);
}
}
}
void Packer::pack()
{
if (!dirty)
return;
dirty = false;
pages.clear();
// only if we have stuff to pack
auto count = entries.count();
if (count > 0)
{
// get all the sources sorted largest -> smallest
List<Entry*> sources;
{
sources.expand(count);
int index = 0;
for (int i = 0; i < entries.count(); i++)
sources[index++] = &entries[i];
std::sort(sources.begin(), sources.end(), [](Packer::Entry* a, Packer::Entry* b)
{
return a->packed.w * a->packed.h > b->packed.w * b->packed.h;
});
}
// make sure the largest isn't too large
if (sources[0]->packed.w + padding * 2 > max_size || sources[0]->packed.h + padding * 2 > max_size)
{
BLAH_ERROR("Source image is larger than max atlas size");
return;
}
// we should never need more nodes than source images * 3
// if this causes problems we could change it to use push_back I suppose
List<Node> nodes;
nodes.expand(count * 4);
int packed = 0, page = 0;
while (packed < count)
{
if (sources[packed]->empty)
{
packed++;
continue;
}
int from = packed;
int index = 0;
Node* root = nodes[index++].Reset(RectI(0, 0, sources[from]->packed.w + padding * 2 + spacing, sources[from]->packed.h + padding * 2 + spacing));
while (packed < count)
{
if (sources[packed]->empty)
{
packed++;
continue;
}
int w = sources[packed]->packed.w + padding * 2 + spacing;
int h = sources[packed]->packed.h + padding * 2 + spacing;
Node* node = root->Find(w, h);
// try to expand
if (node == nullptr)
{
bool canGrowDown = (w <= root->rect.w) && (root->rect.h + h < max_size);
bool canGrowRight = (h <= root->rect.h) && (root->rect.w + w < max_size);
bool shouldGrowRight = canGrowRight && (root->rect.h >= (root->rect.w + w));
bool shouldGrowDown = canGrowDown && (root->rect.w >= (root->rect.h + h));
if (canGrowDown || canGrowRight)
{
// grow right
if (shouldGrowRight || (!shouldGrowDown && canGrowRight))
{
Node* next = nodes[index++].Reset(RectI(0, 0, root->rect.w + w, root->rect.h));
next->used = true;
next->down = root;
next->right = node = nodes[index++].Reset(RectI(root->rect.w, 0, w, root->rect.h));
root = next;
}
// grow down
else
{
Node* next = nodes[index++].Reset(RectI(0, 0, root->rect.w, root->rect.h + h));
next->used = true;
next->down = node = nodes[index++].Reset(RectI(0, root->rect.h, root->rect.w, h));
next->right = root;
root = next;
}
}
}
// doesn't fit
if (node == nullptr)
break;
// add
node->used = true;
node->down = nodes[index++].Reset(RectI(node->rect.x, node->rect.y + h, node->rect.w, node->rect.h - h));
node->right = nodes[index++].Reset(RectI(node->rect.x + w, node->rect.y, node->rect.w - w, h));
sources[packed]->packed.x = node->rect.x + padding;
sources[packed]->packed.y = node->rect.y + padding;
packed++;
}
// get page size
int pageWidth, pageHeight;
if (power_of_two)
{
pageWidth = 2;
pageHeight = 2;
while (pageWidth < root->rect.w)
pageWidth *= 2;
while (pageHeight < root->rect.h)
pageHeight *= 2;
}
else
{
pageWidth = root->rect.w;
pageHeight = root->rect.h;
}
// create each page
{
pages.add(Image(pageWidth, pageHeight));
// copy image data to image
for (int i = from; i < packed; i++)
{
sources[i]->page = page;
if (!sources[i]->empty)
{
RectI dst = sources[i]->packed;
Color* src = (Color*)(buffer.data() + sources[i]->memoryIndex);
// TODO:
// Optimize this?
if (padding > 0)
{
pages[page].set_pixels(RectI(dst.x - padding, dst.y, dst.w, dst.h), src);
pages[page].set_pixels(RectI(dst.x + padding, dst.y, dst.w, dst.h), src);
pages[page].set_pixels(RectI(dst.x, dst.y - padding, dst.w, dst.h), src);
pages[page].set_pixels(RectI(dst.x, dst.y + padding, dst.w, dst.h), src);
}
pages[page].set_pixels(dst, src);
}
}
}
page++;
}
}
}
void Packer::clear()
{
pages.clear();
entries.clear();
dirty = false;
}
void Packer::dispose()
{
pages.dispose();
entries.dispose();
buffer.close();
max_size = 0;
power_of_two = 0;
spacing = 0;
dirty = false;
}
Packer::Node::Node()
: used(false), rect(0, 0, 0, 0), right(nullptr), down(nullptr) { }
Packer::Node* Packer::Node::Find(int w, int h)
{
if (used)
{
Packer::Node* r = right->Find(w, h);
if (r != nullptr)
return r;
return down->Find(w, h);
}
else if (w <= rect.w && h <= rect.h)
return this;
return nullptr;
}
Packer::Node* Packer::Node::Reset(const RectI& rect)
{
used = false;
this->rect = rect;
right = nullptr;
down = nullptr;
return this;
}

View File

@ -0,0 +1,71 @@
#pragma once
#include <blah/images/image.h>
#include <blah/math/color.h>
#include <blah/math/rectI.h>
#include <blah/math/point.h>
#include <blah/containers/list.h>
#include <blah/containers/str.h>
#include <blah/streams/bufferstream.h>
namespace Blah
{
class Packer
{
public:
class Entry
{
friend class Packer;
private:
int64_t memoryIndex = 0;
public:
int page = 0;
uint64_t id;
bool empty = true;
RectI packed;
RectI frame;
};
int max_size;
bool power_of_two;
int spacing;
int padding;
List<Image> pages;
List<Entry> entries;
Packer();
Packer(int max_size, int spacing, bool power_of_two);
Packer(const Packer&) = delete;
Packer& operator=(const Packer&) = delete;
Packer(Packer&& src) noexcept;
Packer& operator=(Packer&& src) noexcept;
~Packer();
void add(uint64_t id, int width, int height, const Color* pixels);
void add(uint64_t id, const Image& bitmap);
void add(uint64_t id, const String& path);
void pack();
void clear();
void dispose();
private:
struct Node
{
bool used;
RectI rect;
Node* right;
Node* down;
Node();
Node* Find(int w, int h);
Node* Reset(const RectI& rect);
};
bool dirty;
BufferStream buffer;
void add_entry(uint64_t id, int w, int h, const Color* pixels);
};
}

378
public/blah/input/input.cpp Normal file
View File

@ -0,0 +1,378 @@
#include <blah/input/input.h>
#include <blah/app.h>
#include <blah/time.h>
#include <blah/log.h>
#include <blah/math/point.h>
#include <blah/internal/input.h>
#include <string.h>
using namespace Blah;
namespace
{
InputState g_last_state;
InputState g_curr_state;
InputState g_next_state;
InputState g_empty_state;
ControllerState g_empty_controller;
}
void Internal::Input::init()
{
g_empty_controller.name = "Disconnected";
for (int i = 0; i < BLAH_MAX_CONTROLLERS; i++)
g_empty_state.controllers[i].name = g_empty_controller.name;
g_last_state = g_empty_state;
g_curr_state = g_empty_state;
g_next_state = g_empty_state;
}
void Internal::Input::frame()
{
// cycle states
g_last_state = g_curr_state;
g_curr_state = g_next_state;
// copy state, clear pressed / released values
{
for (int i = 0; i < BLAH_MAX_KEYBOARD_KEYS; i++)
{
g_next_state.keyboard.pressed[i] = false;
g_next_state.keyboard.released[i] = false;
}
for (int i = 0; i < BLAH_MAX_MOUSE_BUTTONS; i++)
{
g_next_state.mouse.pressed[i] = false;
g_next_state.mouse.released[i] = false;
}
g_next_state.mouse.wheel = Point::zero;
for (int i = 0; i < BLAH_MAX_TEXT; i ++)
g_next_state.keyboard.text[i] = 0;
for (int i = 0; i < BLAH_MAX_CONTROLLERS; i++)
{
ControllerState* controller = &(g_next_state.controllers[i]);
if (!controller->is_connected)
controller->name = nullptr;
for (int j = 0; j < BLAH_MAX_CONTROLLER_BUTTONS; j++)
{
controller->pressed[j] = false;
controller->released[j] = false;
}
}
}
}
void Internal::Input::on_mouse_move(float x, float y)
{
g_next_state.mouse.position.x = x;
g_next_state.mouse.position.y = y;
Point size = Point(App::width(), App::height());
Point draw = Point(App::draw_width(), App::draw_height());
g_next_state.mouse.draw_position.x = (x / (float)size.x) * draw.x;
g_next_state.mouse.draw_position.y = (y / (float)size.y) * draw.y;
}
void Internal::Input::on_mouse_screen_move(float x, float y)
{
g_next_state.mouse.screen_position.x = x;
g_next_state.mouse.screen_position.y = y;
}
void Internal::Input::on_mouse_down(MouseButton button)
{
int i = (int)button;
if (i >= 0 && i < BLAH_MAX_MOUSE_BUTTONS)
{
g_next_state.mouse.down[i] = true;
g_next_state.mouse.pressed[i] = true;
g_next_state.mouse.timestamp[i] = Time::milliseconds;
}
}
void Internal::Input::on_mouse_up(MouseButton button)
{
int i = (int)button;
if (i >= 0 && i < BLAH_MAX_MOUSE_BUTTONS)
{
g_next_state.mouse.down[i] = false;
g_next_state.mouse.released[i] = true;
}
}
void Internal::Input::on_key_down(Key key)
{
int i = (int)key;
if (i >= 0 && i < BLAH_MAX_KEYBOARD_KEYS)
{
g_next_state.keyboard.down[i] = true;
g_next_state.keyboard.pressed[i] = true;
g_next_state.keyboard.timestamp[i] = Time::milliseconds;
}
}
void Internal::Input::on_mouse_wheel(Point wheel)
{
g_next_state.mouse.wheel = wheel;
}
void Internal::Input::on_key_up(Key key)
{
int i = (int)key;
if (i >= 0 && i < BLAH_MAX_KEYBOARD_KEYS)
{
g_next_state.keyboard.down[i] = false;
g_next_state.keyboard.released[i] = true;
}
}
void Internal::Input::on_text_utf8(const char* text)
{
strncat(g_next_state.keyboard.text, text, BLAH_MAX_TEXT);
}
void Internal::Input::on_controller_connect(int index, const char* name, int is_gamepad, int button_count, int axis_count)
{
if (index < BLAH_MAX_CONTROLLERS)
{
ControllerState* controller = &(g_next_state.controllers[index]);
*controller = g_empty_controller;
controller->name = name;
controller->is_connected = 1;
controller->is_gamepad = is_gamepad;
controller->button_count = button_count;
controller->axis_count = axis_count;
}
}
void Internal::Input::on_controller_disconnect(int index)
{
if (index < BLAH_MAX_CONTROLLERS)
g_next_state.controllers[index] = g_empty_controller;
}
void Internal::Input::on_button_down(int index, int button)
{
if (index < BLAH_MAX_CONTROLLERS &&
button < BLAH_MAX_CONTROLLER_BUTTONS &&
g_next_state.controllers[index].is_connected &&
button < g_next_state.controllers[index].button_count)
{
g_next_state.controllers[index].down[button] = 1;
g_next_state.controllers[index].pressed[button] = 1;
g_next_state.controllers[index].button_timestamp[button] = Time::milliseconds;
}
}
void Internal::Input::on_button_up(int index, int button)
{
if (index < BLAH_MAX_CONTROLLERS &&
button < BLAH_MAX_CONTROLLER_BUTTONS &&
g_next_state.controllers[index].is_connected &&
button < g_next_state.controllers[index].button_count)
{
g_next_state.controllers[index].down[button] = 0;
g_next_state.controllers[index].released[button] = 1;
}
}
void Internal::Input::on_axis_move(int index, int axis, float value)
{
if (index < BLAH_MAX_CONTROLLERS &&
axis < BLAH_MAX_CONTROLLER_AXIS &&
g_next_state.controllers[index].is_connected &&
axis < g_next_state.controllers[index].axis_count)
{
g_next_state.controllers[index].axis[axis] = value;
g_next_state.controllers[index].axis_timestamp[axis] = Time::milliseconds;
}
}
const InputState* Input::state()
{
return &g_curr_state;
}
const InputState* Input::last_state()
{
return &g_last_state;
}
Vec2 Input::mouse_draw()
{
return Vec2(g_curr_state.mouse.draw_position);
}
bool Input::pressed(MouseButton button)
{
int i = (int)button;
return i >= 0 && i < BLAH_MAX_MOUSE_BUTTONS && g_curr_state.mouse.pressed[i];
}
bool Input::down(MouseButton button)
{
int i = (int)button;
return i >= 0 && i < BLAH_MAX_MOUSE_BUTTONS && g_curr_state.mouse.down[i];
}
bool Input::released(MouseButton button)
{
int i = (int)button;
return i >= 0 && i < BLAH_MAX_MOUSE_BUTTONS && g_curr_state.mouse.released[i];
}
Point Input::mouse_wheel()
{
return g_curr_state.mouse.wheel;
}
bool Input::pressed(Key key)
{
int i = (int)key;
return i > 0 && i < BLAH_MAX_KEYBOARD_KEYS && g_curr_state.keyboard.pressed[i];
}
bool Input::down(Key key)
{
int i = (int)key;
return i > 0 && i < BLAH_MAX_KEYBOARD_KEYS && g_curr_state.keyboard.down[i];
}
bool Input::released(Key key)
{
int i = (int)key;
return i > 0 && i < BLAH_MAX_KEYBOARD_KEYS && g_curr_state.keyboard.released[i];
}
bool Input::ctrl()
{
return down(Key::LeftControl) || down(Key::RightControl);
}
bool Input::shift()
{
return down(Key::LeftShift) || down(Key::RightShift);
}
bool Input::alt()
{
return down(Key::LeftAlt) || down(Key::RightAlt);
}
const char* Input::text()
{
return g_curr_state.keyboard.text;
}
const ControllerState* Input::controller(int controllerIndex)
{
if (controllerIndex >= BLAH_MAX_CONTROLLERS)
{
Log::warn("Trying to access a controller at %i, outside of EX_MAX_CONTROLLERS", controllerIndex);
return &g_empty_controller;
}
else if (!g_curr_state.controllers[controllerIndex].is_connected)
{
return &g_empty_controller;
}
else
{
return &g_curr_state.controllers[controllerIndex];
}
}
bool Input::pressed(int controllerIndex, Button button)
{
int i = (int)button;
if (controllerIndex < BLAH_MAX_CONTROLLERS && i >= 0 && i < BLAH_MAX_CONTROLLER_BUTTONS)
return g_curr_state.controllers[controllerIndex].pressed[i];
return false;
}
bool Input::down(int controllerIndex, Button button)
{
int i = (int)button;
if (controllerIndex < BLAH_MAX_CONTROLLERS && i >= 0 && i < BLAH_MAX_CONTROLLER_BUTTONS)
return g_curr_state.controllers[controllerIndex].down[i];
return false;
}
bool Input::released(int controllerIndex, Button button)
{
int i = (int)button;
if (controllerIndex < BLAH_MAX_CONTROLLERS && i >= 0 && i < BLAH_MAX_CONTROLLER_BUTTONS)
return g_curr_state.controllers[controllerIndex].released[i];
return false;
}
float Input::axis_check(int controllerIndex, Axis axis)
{
int i = (int)axis;
if (controllerIndex < BLAH_MAX_CONTROLLERS && i >= 0 && i < BLAH_MAX_CONTROLLER_AXIS)
return g_curr_state.controllers[controllerIndex].axis[i];
return 0;
}
int Input::axis_check(int prev, Key negative, Key positive)
{
if (Input::pressed(positive))
return 1;
else if (Input::pressed(negative))
return -1;
else
{
bool pos = Input::down(positive);
bool neg = Input::down(negative);
if (pos && neg)
return prev;
else if (pos)
return 1;
else if (neg)
return -1;
else
return 0;
}
}
int Input::axis_check(int prev, int controllerIndex, Button negative, Button positive)
{
if (Input::pressed(controllerIndex, positive))
return 1;
else if (Input::pressed(controllerIndex, negative))
return -1;
else
{
bool pos = Input::down(controllerIndex, positive);
bool neg = Input::down(controllerIndex, negative);
if (pos && neg)
return prev;
else if (pos)
return 1;
else if (neg)
return -1;
else
return 0;
}
}
const char* Input::name_of(Key key)
{
switch (key)
{
#define DEFINE_KEY(name, value) case Key::name: return #name;
BLAH_KEY_DEFINITIONS
#undef DEFINE_KEY
}
return "Unknown";
}

431
public/blah/input/input.h Normal file
View File

@ -0,0 +1,431 @@
#pragma once
#include <inttypes.h>
#include <blah/math/vec2.h>
#define BLAH_MAX_CONTROLLERS 4
#define BLAH_MAX_CONTROLLER_BUTTONS 64
#define BLAH_MAX_CONTROLLER_AXIS 16
#define BLAH_MAX_MOUSE_BUTTONS 16
#define BLAH_MAX_KEYBOARD_KEYS 512
#define BLAH_MAX_TEXT 256
#define BLAH_MAX_VIRTUAL_NODES 32
#define BLAH_KEY_DEFINITIONS \
DEFINE_KEY(Unknown, 0) \
DEFINE_KEY(A, 4) \
DEFINE_KEY(B, 5) \
DEFINE_KEY(C, 6) \
DEFINE_KEY(D, 7) \
DEFINE_KEY(E, 8) \
DEFINE_KEY(F, 9) \
DEFINE_KEY(G, 10) \
DEFINE_KEY(H, 11) \
DEFINE_KEY(I, 12) \
DEFINE_KEY(J, 13) \
DEFINE_KEY(K, 14) \
DEFINE_KEY(L, 15) \
DEFINE_KEY(M, 16) \
DEFINE_KEY(N, 17) \
DEFINE_KEY(O, 18) \
DEFINE_KEY(P, 19) \
DEFINE_KEY(Q, 20) \
DEFINE_KEY(R, 21) \
DEFINE_KEY(S, 22) \
DEFINE_KEY(T, 23) \
DEFINE_KEY(U, 24) \
DEFINE_KEY(V, 25) \
DEFINE_KEY(W, 26) \
DEFINE_KEY(X, 27) \
DEFINE_KEY(Y, 28) \
DEFINE_KEY(Z, 29) \
DEFINE_KEY(D1, 30) \
DEFINE_KEY(D2, 31) \
DEFINE_KEY(D3, 32) \
DEFINE_KEY(D4, 33) \
DEFINE_KEY(D5, 34) \
DEFINE_KEY(D6, 35) \
DEFINE_KEY(D7, 36) \
DEFINE_KEY(D8, 37) \
DEFINE_KEY(D9, 38) \
DEFINE_KEY(D0, 39) \
DEFINE_KEY(Enter, 40) \
DEFINE_KEY(Escape, 41) \
DEFINE_KEY(Backspace, 42) \
DEFINE_KEY(Tab, 43) \
DEFINE_KEY(Space, 44) \
DEFINE_KEY(Minus, 45) \
DEFINE_KEY(Equals, 46) \
DEFINE_KEY(LeftBracket, 47) \
DEFINE_KEY(RightBracket, 48) \
DEFINE_KEY(BackSlash, 49) \
DEFINE_KEY(NonUSHash, 50) \
DEFINE_KEY(Semicolon, 51) \
DEFINE_KEY(Apostrophe, 52) \
DEFINE_KEY(Grave, 53) \
DEFINE_KEY(Comma, 54) \
DEFINE_KEY(Period, 55) \
DEFINE_KEY(Slash, 56) \
DEFINE_KEY(Capslock, 57) \
DEFINE_KEY(F1, 58) \
DEFINE_KEY(F2, 59) \
DEFINE_KEY(F3, 60) \
DEFINE_KEY(F4, 61) \
DEFINE_KEY(F5, 62) \
DEFINE_KEY(F6, 63) \
DEFINE_KEY(F7, 64) \
DEFINE_KEY(F8, 65) \
DEFINE_KEY(F9, 66) \
DEFINE_KEY(F10, 67) \
DEFINE_KEY(F11, 68) \
DEFINE_KEY(F12, 69) \
DEFINE_KEY(PrintScreen, 70) \
DEFINE_KEY(ScrollLock, 71) \
DEFINE_KEY(Pause, 72) \
DEFINE_KEY(Insert, 73) \
DEFINE_KEY(Home, 74) \
DEFINE_KEY(PageUp, 75) \
DEFINE_KEY(Delete, 76) \
DEFINE_KEY(End, 77) \
DEFINE_KEY(PageDown, 78) \
DEFINE_KEY(Right, 79) \
DEFINE_KEY(Left, 80) \
DEFINE_KEY(Down, 81) \
DEFINE_KEY(Up, 82) \
DEFINE_KEY(NumlockClear, 83) \
DEFINE_KEY(KP_Divide, 84) \
DEFINE_KEY(KP_Multiply, 85) \
DEFINE_KEY(KP_Minus, 86) \
DEFINE_KEY(KP_Plus, 87) \
DEFINE_KEY(KP_Enter, 88) \
DEFINE_KEY(KP_1, 89) \
DEFINE_KEY(KP_2, 90) \
DEFINE_KEY(KP_3, 91) \
DEFINE_KEY(KP_4, 92) \
DEFINE_KEY(KP_5, 93) \
DEFINE_KEY(KP_6, 94) \
DEFINE_KEY(KP_7, 95) \
DEFINE_KEY(KP_8, 96) \
DEFINE_KEY(KP_9, 97) \
DEFINE_KEY(KP_0, 98) \
DEFINE_KEY(KP_Period, 99) \
DEFINE_KEY(NonUSBackSlash, 100) \
DEFINE_KEY(Application, 101) \
DEFINE_KEY(Power, 102) \
DEFINE_KEY(KP_Equals, 103) \
DEFINE_KEY(F13, 104) \
DEFINE_KEY(F14, 105) \
DEFINE_KEY(F15, 106) \
DEFINE_KEY(F16, 107) \
DEFINE_KEY(F17, 108) \
DEFINE_KEY(F18, 109) \
DEFINE_KEY(F19, 110) \
DEFINE_KEY(F20, 111) \
DEFINE_KEY(F21, 112) \
DEFINE_KEY(F22, 113) \
DEFINE_KEY(F23, 114) \
DEFINE_KEY(F24, 115) \
DEFINE_KEY(Execute, 116) \
DEFINE_KEY(Help, 117) \
DEFINE_KEY(Menu, 118) \
DEFINE_KEY(Select, 119) \
DEFINE_KEY(Stop, 120) \
DEFINE_KEY(Again, 121) \
DEFINE_KEY(Undo, 122) \
DEFINE_KEY(Cut, 123) \
DEFINE_KEY(Copy, 124) \
DEFINE_KEY(Paste, 125) \
DEFINE_KEY(Find, 126) \
DEFINE_KEY(Mute, 127) \
DEFINE_KEY(VolumeUp, 128) \
DEFINE_KEY(VolumeDown, 129) \
DEFINE_KEY(KP_Comma, 133) \
DEFINE_KEY(KP_EqualsAs400, 134) \
DEFINE_KEY(International1, 135) \
DEFINE_KEY(International2, 136) \
DEFINE_KEY(International3, 137) \
DEFINE_KEY(International4, 138) \
DEFINE_KEY(International5, 139) \
DEFINE_KEY(International6, 140) \
DEFINE_KEY(International7, 141) \
DEFINE_KEY(International8, 142) \
DEFINE_KEY(International9, 143) \
DEFINE_KEY(Language1, 144) \
DEFINE_KEY(Language2, 145) \
DEFINE_KEY(Language3, 146) \
DEFINE_KEY(Language4, 147) \
DEFINE_KEY(Language5, 148) \
DEFINE_KEY(Language6, 149) \
DEFINE_KEY(Language7, 150) \
DEFINE_KEY(Language8, 151) \
DEFINE_KEY(Language9, 152) \
DEFINE_KEY(AltErase, 153) \
DEFINE_KEY(SysReq, 154) \
DEFINE_KEY(Cancel, 155) \
DEFINE_KEY(clear, 156) \
DEFINE_KEY(Prior, 157) \
DEFINE_KEY(Return2, 158) \
DEFINE_KEY(Separator, 159) \
DEFINE_KEY(Out, 160) \
DEFINE_KEY(Oper, 161) \
DEFINE_KEY(ClearAgain, 162) \
DEFINE_KEY(CRSEL, 163) \
DEFINE_KEY(EXSEL, 164) \
DEFINE_KEY(KP_00, 176) \
DEFINE_KEY(KP_000, 177) \
DEFINE_KEY(ThousandsSeparator, 178) \
DEFINE_KEY(DecimalSeparator, 179) \
DEFINE_KEY(CurrencyUnit, 180) \
DEFINE_KEY(CurrencySubUnit, 181) \
DEFINE_KEY(KP_LeftParen, 182) \
DEFINE_KEY(KP_RightParent, 183) \
DEFINE_KEY(KP_LeftBrace, 184) \
DEFINE_KEY(KP_RightBrace, 185) \
DEFINE_KEY(KP_Tab, 186) \
DEFINE_KEY(KP_BackSpace, 187) \
DEFINE_KEY(KP_A, 188) \
DEFINE_KEY(KP_B, 189) \
DEFINE_KEY(KP_C, 190) \
DEFINE_KEY(KP_D, 191) \
DEFINE_KEY(KP_E, 192) \
DEFINE_KEY(KP_F, 193) \
DEFINE_KEY(KP_XOR, 194) \
DEFINE_KEY(KP_Power, 195) \
DEFINE_KEY(KP_Percent, 196) \
DEFINE_KEY(KP_Less, 197) \
DEFINE_KEY(KP_Greater, 198) \
DEFINE_KEY(KP_Ampersand, 199) \
DEFINE_KEY(KP_DoubleAmpersand, 200) \
DEFINE_KEY(KP_VerticalBar, 201) \
DEFINE_KEY(KP_DoubleVerticalBar, 202) \
DEFINE_KEY(KP_Colon, 203) \
DEFINE_KEY(KP_Hash, 204) \
DEFINE_KEY(KP_Space, 205) \
DEFINE_KEY(KP_At, 206) \
DEFINE_KEY(KP_EXCLAM, 207) \
DEFINE_KEY(KP_MemStore, 208) \
DEFINE_KEY(KP_MemRecall, 209) \
DEFINE_KEY(KP_MemClear, 210) \
DEFINE_KEY(KP_MemAdd, 211) \
DEFINE_KEY(KP_MemSubstract, 212) \
DEFINE_KEY(KP_MemMultiply, 213) \
DEFINE_KEY(KP_MemDivide, 214) \
DEFINE_KEY(KP_PlusMinus, 215) \
DEFINE_KEY(KP_Clear, 216) \
DEFINE_KEY(KP_ClearEntry, 217) \
DEFINE_KEY(KP_Binary, 218) \
DEFINE_KEY(KP_Octal, 219) \
DEFINE_KEY(KP_Decimal, 220) \
DEFINE_KEY(KP_Hexadecimal, 221) \
DEFINE_KEY(LeftControl, 224) \
DEFINE_KEY(LeftShift, 225) \
DEFINE_KEY(LeftAlt, 226) \
DEFINE_KEY(LeftGui, 227) \
DEFINE_KEY(RightControl, 228) \
DEFINE_KEY(RightShift, 229) \
DEFINE_KEY(RightAlt, 230) \
DEFINE_KEY(RightGui, 231)
namespace Blah
{
struct ControllerState
{
// The name of the gamepad, or NULL if not connected
const char* name;
// Whether this gamepad is connected
bool is_connected;
// Whether this gamepad is a standard Game Controller
bool is_gamepad;
// The total button count for this controller, with a maximum of EX_MAX_CONTROLLER_BUTTONS
int button_count;
// The total axis count for this controller, with a maximum of EX_MAX_CONTROLLER_AXIS
int axis_count;
// An array holding the pressed state of each button
bool pressed[BLAH_MAX_CONTROLLER_BUTTONS];
// An array holding the down state of each button
bool down[BLAH_MAX_CONTROLLER_BUTTONS];
// An array holding the released state of each button
bool released[BLAH_MAX_CONTROLLER_BUTTONS];
// An array holding the value state of each axis
float axis[BLAH_MAX_CONTROLLER_AXIS];
// Timestamp, in milliseconds, since each button was last pressed
uint64_t button_timestamp[BLAH_MAX_CONTROLLER_BUTTONS];
// Timestamp, in milliseconds, since each axis last had a value set
uint64_t axis_timestamp[BLAH_MAX_CONTROLLER_AXIS];
};
struct KeyboardState
{
bool pressed[BLAH_MAX_KEYBOARD_KEYS];
bool down[BLAH_MAX_KEYBOARD_KEYS];
bool released[BLAH_MAX_KEYBOARD_KEYS];
uint64_t timestamp[BLAH_MAX_KEYBOARD_KEYS];
char text[BLAH_MAX_TEXT];
};
struct MouseState
{
bool pressed[BLAH_MAX_MOUSE_BUTTONS];
bool down[BLAH_MAX_MOUSE_BUTTONS];
bool released[BLAH_MAX_MOUSE_BUTTONS];
uint64_t timestamp[BLAH_MAX_MOUSE_BUTTONS];
Vec2 screen_position;
Vec2 draw_position;
Vec2 position;
Point wheel;
};
// An Input State, which stores the state for gamepads, keyboard, and mouse
struct InputState
{
// All the Gamepads. Note that not all gamepads are necessarily connected,
// and each one must be checked before use.
ControllerState controllers[BLAH_MAX_CONTROLLERS];
// The current Keyboard state
KeyboardState keyboard;
// The current Mouse state
MouseState mouse;
};
// Keyboard Keys
// These are generally copied from the SDL2 Scancode Keys
enum class Key
{
#define DEFINE_KEY(name, value) name = value,
BLAH_KEY_DEFINITIONS
#undef DEFINE_KEY
};
// Game Controller Buttons
enum class Button
{
None = -1,
A = 0,
B = 1,
X = 2,
Y = 3,
Back = 4,
Select = 5,
Start = 6,
LeftStick= 7,
RightStick = 8,
LeftShoulder = 9,
RightShoulder = 10,
Up = 11,
Down = 12,
Left = 13,
Right = 14
};
// Game Controller Axes
enum class Axis
{
None = -1,
LeftX = 0,
LeftY = 1,
RightX = 2,
RightY = 3,
LeftTrigger = 4,
RightTrigger = 5
};
// Mouse Buttons
enum class MouseButton
{
None = -1,
Left = 0,
Middle = 1,
Right = 2,
};
namespace Input
{
// Returns the Input State of the current frame.
// This pointer is only valid for the current frame and should not be stored.
const InputState* state();
// Returns the Input State of the previous frame.
// This pointer is only valid for the current frame and should not be stored.
const InputState* last_state();
// Gets the Mouse Position
Vec2 mouse();
// Gets the Draw Mouse Position (Mouse Position / Window Size * Draw Size)
Vec2 mouse_draw();
// Gets the Mouse Position in Screen Coordinates
Vec2 mouse_screen();
// Checks if the given Mouse Button is pressed
bool pressed(MouseButton button);
// Checks if the given Mouse Button is down
bool down(MouseButton button);
// Checks if the given Mouse Button is released
bool released(MouseButton button);
// Gets the Mouse Wheel
Point mouse_wheel();
// Returns 1 if the keyboard key was pressed this frame, and 0 otherwise.
bool pressed(Key key);
// Returns 1 if the keyboard key was held this frame, and 0 otherwise.
bool down(Key key);
// Returns 1 if the keyboard key was released this frame, and 0 otherwise.
bool released(Key key);
// Checks if the Left or Right Ctrl Key is down
bool ctrl();
// Checks if the Left or Right Shift Key is down
bool shift();
// Checks if the Left or Right Alt Key is down
bool alt();
// Get the current Text Input
const char* text();
// Gets the controller info for the current controller index.
// If the controller is not connected or the index it out of range, this will set an unconnected controller.
const ControllerState* controller(int controllerIndex);
// Returns 1 if the button on the controller was pressed this frame, and 0 otherwise.
// If the controller is not connected, or the index is out of range, this will return 0.
bool pressed(int controllerIndex, Button button);
// Returns 1 if the button on the controller was held this frame, and 0 otherwise.
// If the controller is not connected, or the index is out of range, this will return 0.
bool down(int controllerIndex, Button button);
// Returns 1 if the button on the controller was released this frame, and 0 otherwise.
// If the controller is not connected, or the index is out of range, this will return 0.
bool released(int controllerIndex, Button button);
float axis_check(int controllerIndex, Axis axis);
int axis_check(int prev, Key negative, Key positive);
int axis_check(int prev, int controllerIndex, Button negative, Button positive);
// returns a string name of the given key
const char* name_of(Key key);
}
}

View File

@ -0,0 +1,4 @@
#pragma once
#include <blah/input/virtual_axis.h>
using namespace Blah;

View File

@ -0,0 +1,84 @@
#pragma once
#include <blah/input/input.h>
namespace Blah
{
class VirtualAxis
{
private:
struct KeysNode
{
Key positive = Key::Unknown;
Key negative = Key::Unknown;
int value = 0;
void init(Key negative, Key positive);
void update();
};
struct ButtonsNode
{
int gamepad_id = 0;
Button positive = Button::None;
Button negative = Button::None;
int value = 0;
void init(int gamepad_id, Button negative, Button positive);
void update();
};
struct AxisNode
{
int gamepad_id = 0;
Axis axis = Axis::None;
float deadzone = 0;
float value = 0;
void init(int gamepad_id, Axis axis, float deadzone);
void update();
};
KeysNode m_keys[BLAH_MAX_VIRTUAL_NODES];
ButtonsNode m_buttons[BLAH_MAX_VIRTUAL_NODES];
AxisNode m_axes[BLAH_MAX_VIRTUAL_NODES];
int m_keys_len = 0;
int m_buttons_len = 0;
int m_axes_len = 0;
float m_press_buffer = 0;
float m_release_buffer = 0;
float m_repeat_delay = 0;
float m_repeat_interval = 0;
float m_value = 0;
int m_value_i = 0;
float m_last_value = 0;
int m_last_value_i = 0;
bool m_pressed = false;
bool m_released = false;
float m_last_press_time = 0;
float m_last_release_time = 0;
float m_repeat_press_time = 0;
public:
VirtualAxis& add_keys(Key negative, Key positive);
VirtualAxis& add_buttons(int gamepad_id, Button negative, Button positive);
VirtualAxis& add_axis(int gamepad_id, Axis axis, float deadzone);
VirtualAxis& repeat(float m_repeat_delay, float m_repeat_interval);
VirtualAxis& press_buffer(float duration);
VirtualAxis& release_buffer(float duration);
void update();
float value() const { return m_value; }
int value_i() const { return m_value_i; }
float last_value() const { return m_last_value; }
int last_value_i() const { return m_last_value_i; }
bool pressed() const { return m_pressed; }
bool released() const { return m_released; }
void clear_press_buffer() { m_last_press_time = -1; m_pressed = false; }
void clear_release_buffer() { m_last_release_time = -1; m_released = false; }
};
}

View File

@ -0,0 +1,4 @@
#pragma once
#include <blah/input/virtual_button.h>
using namespace Blah;

View File

@ -0,0 +1,83 @@
#pragma once
#include <blah/input/input.h>
namespace Blah
{
class VirtualButton
{
private:
struct KeyNode
{
Key key = Key::Unknown;
bool down = false;
bool pressed = false;
bool released = false;
void init(Key key);
void update();
};
struct ButtonNode
{
int gamepad_id = 0;
Button button = Button::None;
bool down = false;
bool pressed = false;
bool released = false;
void init(int gamepad_id, Button button);
void update();
};
struct AxisNode
{
int gamepad_id = 0;
Axis axis = Axis::None;
float threshold = 0;
bool greater_than = false;
bool down = false;
bool pressed = false;
bool released = false;
void init(int gamepad_id, Axis axis, float threshold, bool greater_than);
void update();
};
KeyNode m_keys[BLAH_MAX_VIRTUAL_NODES];
ButtonNode m_buttons[BLAH_MAX_VIRTUAL_NODES];
AxisNode m_axes[BLAH_MAX_VIRTUAL_NODES];
int m_keys_len = 0;
int m_buttons_len = 0;
int m_axes_len = 0;
float m_press_buffer = 0;
float m_release_buffer = 0;
float m_repeat_delay = 0;
float m_repeat_interval = 0;
bool m_down = false;
bool m_pressed = false;
bool m_released = false;
float m_last_press_time = 0;
float m_last_release_time = 0;
float m_repeat_press_time = 0;
public:
VirtualButton& add_key(Key key);
VirtualButton& add_button(int gamepad_id, Button button);
VirtualButton& add_axis(int gamepad_id, Axis axis, float threshold, bool greater_than);
VirtualButton& repeat(float m_repeat_delay, float m_repeat_interval);
VirtualButton& press_buffer(float duration);
VirtualButton& release_buffer(float duration);
void update();
bool down() const { return m_down; }
bool pressed() const { return m_pressed; }
bool released() const { return m_released; }
void clear_press_buffer() { m_last_press_time = -1; m_pressed = false; }
void clear_release_buffer() { m_last_release_time = -1; m_released = false; }
};
}

View File

@ -0,0 +1,4 @@
#pragma once
#include <blah/input/virtual_stick.h>
using namespace Blah;

View File

@ -0,0 +1,95 @@
#pragma once
#include <blah/input/input.h>
#include <blah/math/vec2.h>
#include <blah/math/point.h>
namespace Blah
{
class VirtualStick
{
private:
struct KeysNode
{
Key left;
Key right;
Key up;
Key down;
Point value;
void init(Key left, Key right, Key up, Key down);
void update();
};
struct ButtonsNode
{
int gamepad_id;
Button left;
Button right;
Button up;
Button down;
Point value;
void init(int gamepad_id, Button left, Button right, Button up, Button down);
void update();
};
struct AxesNode
{
int gamepad_id;
Axis horizontal;
Axis vertical;
float deadzone;
Vec2 value;
void init(int gamepad_id, Axis horizontal, Axis vertical, float deadzone);
void update();
};
KeysNode m_keys[BLAH_MAX_VIRTUAL_NODES];
ButtonsNode m_buttons[BLAH_MAX_VIRTUAL_NODES];
AxesNode m_axes[BLAH_MAX_VIRTUAL_NODES];
int m_keys_len = 0;
int m_buttons_len = 0;
int m_axes_len = 0;
float m_press_buffer = 0;
float m_release_buffer = 0;
float m_repeat_delay = 0;
float m_repeat_interval = 0;
Vec2 m_value = Vec2();
Point m_value_i = Point();
Vec2 m_last_value = Vec2();
Point m_last_value_i = Point();
bool m_pressed = false;
bool m_released = false;
float m_i_deadzone;
float m_last_press_time = 0;
float m_last_release_time = 0;
float m_repeat_press_time = 0;
public:
VirtualStick();
VirtualStick(float iDeadzone);
VirtualStick& add_keys(Key left, Key right, Key up, Key down);
VirtualStick& add_buttons(int gamepad_id, Button left, Button right, Button up, Button down);
VirtualStick& add_axes(int gamepad_id, Axis horizontal, Axis vertical, float deadzone);
VirtualStick& repeat(float m_repeat_delay, float m_repeat_interval);
VirtualStick& press_buffer(float duration);
VirtualStick& release_buffer(float duration);
void update();
const Vec2& value() const { return m_value; }
const Point& value_i() const { return m_value_i; }
const Vec2& last_value() const { return m_last_value; }
const Point& last_value_i() const { return m_last_value_i; }
bool pressed() const { return m_pressed; }
bool released() const { return m_released; }
void clear_press_buffer() { m_last_press_time = 0; }
void clear_release_buffer() { m_last_release_time = 0; }
};
}

60
public/blah/log.cpp Normal file
View File

@ -0,0 +1,60 @@
#include <blah/log.h>
#include <blah/app.h>
#include <stdarg.h> // for logging methods
#include <stdio.h> // for sprintf
using namespace Blah;
void Log::print(const char* format, ...)
{
char msg[BLAH_MAX_MESSAGE];
va_list ap;
va_start(ap, format);
vsnprintf(msg, sizeof(char) * BLAH_MAX_MESSAGE, format, ap);
va_end(ap);
if (App::is_running() && App::config()->on_info != nullptr)
{
App::config()->on_info(msg);
}
else
{
printf("%s\n", msg);
}
}
void Log::warn(const char* format, ...)
{
char msg[BLAH_MAX_MESSAGE];
va_list ap;
va_start(ap, format);
vsnprintf(msg, sizeof(char) * BLAH_MAX_MESSAGE, format, ap);
va_end(ap);
if (App::is_running() && App::config()->on_warn != nullptr)
{
App::config()->on_warn(msg);
}
else
{
printf("\033[01;33mWARN:\033[0m\t%s\n", msg);
}
}
void Log::error(const char* format, ...)
{
char msg[BLAH_MAX_MESSAGE];
va_list ap;
va_start(ap, format);
vsnprintf(msg, sizeof(char) * BLAH_MAX_MESSAGE, format, ap);
va_end(ap);
if (App::is_running() && App::config()->on_error != nullptr)
{
App::config()->on_error(msg);
}
else
{
printf("\033[1;31mERROR:\033[0m\t%s\n", msg);
}
}

34
public/blah/log.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
// Error / Abort
#ifdef DEBUG
#define BLAH_ERROR(message) \
do { Log::error(message "\n\tin file: %s:%d", __FILE__, __LINE__); abort(); } while(0)
#define BLAH_ASSERT(condition, message) \
do { if (!(condition)) { BLAH_ERROR(message); } } while(0)
#else
#define BLAH_ERROR(message) \
Log::error(message "\n\tin file: %s:%d", __FILE__, __LINE__)
#define BLAH_ASSERT(condition, message) \
do { } while(false)
#endif
#ifndef BLAH_MAX_MESSAGE
#define BLAH_MAX_MESSAGE 1024
#endif
namespace Blah
{
namespace Log
{
void print(const char* info, ...);
void warn(const char* info, ...);
void error(const char* info, ...);
}
}

135
public/blah/math/calc.cpp Normal file
View File

@ -0,0 +1,135 @@
#include <blah/math/calc.h>
#include <math.h>
#include <stdlib.h>
using namespace Blah;
float Calc::rand_float(float min, float maxExc)
{
return min + rand_float(maxExc - min);
}
float Calc::rand_float(float maxExc)
{
return (rand() / (float)RAND_MAX) * maxExc;
}
int Calc::rand_int(int min, int maxExc)
{
return min + rand_int(maxExc - min);
}
int Calc::rand_int(int maxExc)
{
if (maxExc <= 0)
return 0;
return rand() % maxExc;
}
int Calc::rand_int()
{
return rand();
}
float Calc::approach(float t, float target, float maxDelta)
{
return t < target ? min(t + maxDelta, target) : max(t - maxDelta, target);
}
float Calc::clamp(float t, float min, float max)
{
return t < min ? min : (t > max ? max : t);
}
int Calc::clamp_int(int t, int min, int max)
{
return t < min ? min : (t > max ? max : t);
}
int Calc::sign(int x)
{
return (x < 0 ? -1 : (x > 0 ? 1 : 0));
}
float Calc::sign(float x)
{
return (x < 0 ? -1.0f : (x > 0 ? 1.0f : 0.0f));
}
int Calc::abs(int x)
{
return x < 0 ? -x : x;
}
float Calc::abs(float x)
{
return x < 0 ? -x : x;
}
float Calc::floor(float x)
{
return floorf(x);
}
float Calc::ceiling(float x)
{
return ceilf(x);
}
float Calc::mod(float x, float m)
{
return x - (int)(x / m) * m;
}
float Calc::sin(float x)
{
return sinf(x);
}
float Calc::cos(float x)
{
return cosf(x);
}
float Calc::atan2(float y, float x)
{
return atan2f(y, x);
}
float Calc::pow(float x, float n)
{
return powf(x, n);
}
float Calc::sqrt(float x)
{
return sqrtf(x);
}
float Calc::snap(float val, float interval)
{
if (val > 0)
return ((int)((val + interval / 2) / interval)) * interval;
else
return ((int)((val - interval / 2) / interval)) * interval;
}
float Calc::angle_diff(float radians_a, float radians_b)
{
return mod((radians_b - radians_a) + PI, TAU) - PI;
}
float Calc::lerp(float a, float b, float t)
{
return a + (b - a) * t;
}
bool Calc::is_big_endian()
{
return (*((short*)"AB") == 0x4243);
}
bool Calc::is_little_endian()
{
return (*((short*)"AB") != 0x4243);
}

71
public/blah/math/calc.h Normal file
View File

@ -0,0 +1,71 @@
#pragma once
#include <inttypes.h>
namespace Blah
{
namespace Calc
{
constexpr float PI = 3.141592653f;
constexpr float TAU = PI * 2.0f;
constexpr float RIGHT = 0;
constexpr float LEFT = PI;
constexpr float UP = PI / -2;
constexpr float DOWN = PI / 2;
float rand_float(float min, float maxExc);
float rand_float(float maxExc);
int rand_int(int min, int maxExc);
int rand_int(int maxExc);
int rand_int();
float approach(float t, float target, float maxDelta);
float clamp(float t, float min, float max);
int clamp_int(int t, int min, int max);
int sign(int x);
float sign(float x);
int abs(int x);
float abs(float x);
template<class T>
T min(T a, T b) { return a < b ? a : b; }
template<class T>
T max(T a, T b) { return a > b ? a : b; }
float floor(float x);
float ceiling(float x);
float mod(float x, float m);
float sin(float x);
float cos(float x);
float atan2(float y, float x);
float pow(float x, float n);
float sqrt(float x);
float snap(float val, float interval);
float angle_diff(float radians_a, float radians_b);
float lerp(float a, float b, float t);
bool is_big_endian();
bool is_little_endian();
};
}

View File

@ -0,0 +1,11 @@
#include <blah/math/circle.h>
#include <blah/math/calc.h>
using namespace Blah;
void Circle::project(const Vec2& axis, float* min, float* max) const
{
*min = Vec2::dot(center - axis * radius, axis);
*max = Vec2::dot(center + axis * radius, axis);
}

19
public/blah/math/circle.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <blah/math/vec2.h>
namespace Blah
{
struct Circle
{
Vec2 center;
float radius;
Circle()
: center(), radius(0) {}
Circle(Vec2 center, float radius)
: center(center), radius(radius) {}
void project(const Vec2& axis, float* min, float* max) const;
};
}

156
public/blah/math/color.cpp Normal file
View File

@ -0,0 +1,156 @@
#include <blah/math/color.h>
#include <blah/math/vec4.h>
using namespace Blah;
char const hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
#define LT_HEX_VALUE(n) ((n >= '0' && n <= '9') ? (n - '0') : ((n >= 'A' && n <= 'F') ? (10 + n - 'A') : ((n >= 'a' && n <= 'f') ? (10 + n - 'a') : 0)))
Color::Color()
: r(0), g(0), b(0), a(0) {}
Color::Color(int rgb) :
r((uint8_t)((rgb & 0xFF0000) >> 16)),
g((uint8_t)((rgb & 0x00FF00) >> 8)),
b((uint8_t)(rgb & 0x0000FF)),
a(255) {}
Color::Color(int rgb, float alpha) :
r((int)(((uint8_t)((rgb & 0xFF0000) >> 16)) * alpha)),
g((int)(((uint8_t)((rgb & 0x00FF00) >> 8)) * alpha)),
b((int)(((uint8_t)(rgb & 0x0000FF)) * alpha)),
a((int)(255 * alpha)) {}
Color::Color(uint8_t r, uint8_t g, uint8_t b)
: r(r), g(g), b(b), a(255) {}
Color::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
: r(r), g(g), b(b), a(a) {}
Color::Color(const char* value) : r(0), g(0), b(0), a(255)
{
int length = 0;
while (value[length] != '\0' && length < 10)
length ++;
int offset = 0;
if (length > 0 && value[0] == '#')
offset = 1;
else if (length >= 1 && value[0] == '0' && (value[1] == 'x' || value[1] == 'X'))
offset = 2;
if (length - offset >= 8)
a = (LT_HEX_VALUE(value[offset + 6]) << 4) + LT_HEX_VALUE(value[offset + 7]);
if (length - offset >= 6)
{
r = (LT_HEX_VALUE(value[offset + 0]) << 4) + LT_HEX_VALUE(value[offset + 1]);
g = (LT_HEX_VALUE(value[offset + 2]) << 4) + LT_HEX_VALUE(value[offset + 3]);
b = (LT_HEX_VALUE(value[offset + 4]) << 4) + LT_HEX_VALUE(value[offset + 5]);
}
}
void Color::premultiply()
{
r = r * a / 255;
g = g * a / 255;
b = b * a / 255;
}
uint32_t Color::to_rgba() const
{
return
((uint32_t)r << 24) |
((uint32_t)g << 16) |
((uint32_t)b << 8) |
(uint32_t)a;
}
Vec4 Color::to_vec4() const
{
return Vec4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
}
void Color::to_hex_rgba(char* buffer) const
{
buffer[0] = hex[(r & 0xF0) >> 4];
buffer[1] = hex[(r & 0x0F) >> 0];
buffer[2] = hex[(g & 0xF0) >> 4];
buffer[3] = hex[(g & 0x0F) >> 0];
buffer[4] = hex[(b & 0xF0) >> 4];
buffer[5] = hex[(b & 0x0F) >> 0];
buffer[6] = hex[(a & 0xF0) >> 4];
buffer[7] = hex[(a & 0x0F) >> 0];
}
void Color::to_hex_rgb(char* buffer) const
{
buffer[0] = hex[(r & 0xF0) >> 4];
buffer[1] = hex[(r & 0x0F) >> 0];
buffer[2] = hex[(g & 0xF0) >> 4];
buffer[3] = hex[(g & 0x0F) >> 0];
buffer[4] = hex[(b & 0xF0) >> 4];
buffer[5] = hex[(b & 0x0F) >> 0];
}
Color Color::from_rgba(uint32_t value)
{
return
{
(uint8_t)((value & 0xFF000000) >> 24),
(uint8_t)((value & 0x00FF0000) >> 16),
(uint8_t)((value & 0x0000FF00) >> 8),
(uint8_t)((value & 0x000000FF))
};
}
Color Color::from_rgb(uint32_t value)
{
return
{
(uint8_t)((value & 0xFF0000) >> 16),
(uint8_t)((value & 0x00FF00) >> 8),
(uint8_t)((value & 0x0000FF))
};
}
Color Color::lerp(Color a, Color b, float amount)
{
if (amount < 0) amount = 0;
if (amount > 1) amount = 1;
return Color(
(uint8_t)(a.r + (b.r - a.r) * amount),
(uint8_t)(a.g + (b.g - a.g) * amount),
(uint8_t)(a.b + (b.b - a.b) * amount),
(uint8_t)(a.a + (b.a - a.a) * amount)
);
}
Color Color::operator*(float multiply) const
{
return Color(
(int)(r * multiply),
(int)(g * multiply),
(int)(b * multiply),
(int)(a * multiply));
}
Color& Color::operator= (const int rgb)
{
r = (uint8_t)((rgb & 0xFF0000) >> 16);
g = (uint8_t)((rgb & 0x00FF00) >> 8);
b = (uint8_t)(rgb & 0x0000FF);
a = 255;
return *this;
}
const Color Color::transparent = Color(0, 0, 0, 0);
const Color Color::white = Color(255, 255, 255, 255);
const Color Color::black = Color(0, 0, 0, 255);
const Color Color::red = Color(255, 0, 0, 255);
const Color Color::green = Color(0, 255, 0, 255);
const Color Color::blue = Color(0, 0, 255, 255);
const Color Color::yellow = Color(255, 255, 0, 255);
const Color Color::purple = Color(255, 0, 255, 255);
const Color Color::teal = Color(0, 255, 255, 255);

64
public/blah/math/color.h Normal file
View File

@ -0,0 +1,64 @@
#pragma once
#include <inttypes.h>
namespace Blah
{
struct Vec4;
struct Color
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
Color();
Color(int rgb);
Color(int rgb, float alpha);
Color(uint8_t r, uint8_t g, uint8_t b);
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a);
// Parses a Hex string in the format of "#00000000" or "0x00000000" or "00000000"
Color(const char* hexCstr);
// Premultiplies the Color
void premultiply();
// Sets a Hex string to the given buffer, in the format of RRGGBBAA
// The buffer must be at least 8 bytes long
void to_hex_rgba(char* buffer) const;
// Sets a Hex string to the given buffer, in the format of RRGGBB
// The buffer must be at least 6 bytes long
void to_hex_rgb(char* buffer) const;
uint32_t to_rgba() const;
Vec4 to_vec4() const;
// Returns a RGBA Color representation of the integer value
static Color from_rgba(uint32_t value);
// Returns a RGB Color representation of the integer value, with Alpha = 255
static Color from_rgb(uint32_t value);
static Color lerp(Color a, Color b, float amount);
// Mutliplties the Color
Color operator*(float multiply) const;
// assignment from int
Color& operator= (const int rgb);
static const Color transparent;
static const Color white;
static const Color black;
static const Color red;
static const Color green;
static const Color blue;
static const Color yellow;
static const Color orange;
static const Color purple;
static const Color teal;
};
}

336
public/blah/math/ease.h Normal file
View File

@ -0,0 +1,336 @@
#pragma once
#include <blah/math/calc.h>
#include <blah/log.h>
namespace Blah
{
using Easer = float (*)(float);
enum class Easers
{
QuadIn, QuadOut, QuadInOut,
CubeIn, CubeOut, CubeInOut,
QuartIn, QuartOut, QuartInOut,
QuintIn, QuintOut, QuintInOut,
SineIn, SineOut, SineInOut,
CircIn, CircOut, CircInOut,
ExpIn, ExpOut, ExpInOut,
ElasticIn, ElasticOut, ElasticInOut,
BackIn, BackOut, BackInOut,
BounceIn, BounceOut, BounceInOut,
_Count
};
namespace Ease
{
/*
Adapted from functions here: https://github.com/warrenm/AHEasing/blob/master/AHEasing/easing.c
For previews go here: https://easings.net/
*/
float linear(float t)
{
return t;
}
float quad_in(float t)
{
return t * t;
}
float quad_out(float t)
{
return -(t * (t - 2));
}
float quad_in_out(float t)
{
if (t < 0.5f)
return 2 * t * t;
else
{
return (-2 * t * t) + (4 * t) - 1;
}
}
float cube_in(float t)
{
return t * t * t;
}
float cube_out(float t)
{
float f = (t - 1);
return f * f * f + 1;
}
float cube_in_out(float t)
{
if (t < 0.5f)
return 4 * t * t * t;
else
{
float f = ((2 * t) - 2);
return 0.5f * f * f * f + 1;
}
}
float quart_in(float t)
{
return t * t * t * t;
}
float quart_out(float t)
{
float f = (t - 1);
return f * f * f * (1 - t) + 1;
}
float quart_in_out(float t)
{
if (t < 0.5f)
return 8 * t * t * t * t;
else
{
float f = (t - 1);
return -8 * f * f * f * f + 1;
}
}
float quint_in(float t)
{
return t * t * t * t * t;
}
float quint_out(float t)
{
float f = (t - 1);
return f * f * f * f * f + 1;
}
float quint_in_out(float t)
{
if (t < 0.5f)
return 16 * t * t * t * t * t;
else
{
float f = ((2 * t) - 2);
return 0.5f * f * f * f * f * f + 1;
}
}
float sine_in(float t)
{
return Calc::sin((t - 1) * Calc::PI * 0.5f) + 1;
}
float sine_out(float t)
{
return Calc::sin(t * (Calc::PI * 0.5f));
}
float sine_in_out(float t)
{
return 0.5f * (1 - Calc::cos(t * Calc::PI));
}
float circ_in(float t)
{
return 1 - Calc::sqrt(1 - (t * t));
}
float circ_out(float t)
{
return Calc::sqrt((2 - t) * t);
}
float circ_in_out(float t)
{
if (t < 0.5f)
return 0.5f * (1 - Calc::sqrt(1 - 4 * (t * t)));
else
return 0.5f * (Calc::sqrt(-((2 * t) - 3) * ((2 * t) - 1)) + 1);
}
float exp_in(float t)
{
return (t == 0) ? 0 : Calc::pow(2, 10 * (t - 1));
}
float exp_out(float t)
{
return (t == 1) ? 1 : 1 - Calc::pow(2, -10 * t);
}
float exp_in_out(float t)
{
if (t == 0 || t == 1)
return t;
if (t < 0.5f)
return 0.5f * Calc::pow(2, (20 * t) - 10);
else
return -0.5f * Calc::pow(2, (-20 * t) + 10) + 1;
}
float elastic_in(float t)
{
return Calc::sin(13 * (Calc::PI * 0.5f) * t) * Calc::pow(2, 10 * (t - 1));
}
float elastic_out(float t)
{
return Calc::sin(-13 * (Calc::PI * 0.5f) * (t + 1)) * Calc::pow(2, -10 * t) + 1;
}
float elastic_in_out(float t)
{
if (t < 0.5f)
return 0.5f * Calc::sin(13 * (Calc::PI * 0.5f) * (2 * t)) * Calc::pow(2, 10 * ((2 * t) - 1));
else
return 0.5f * (Calc::sin(-13 * (Calc::PI * 0.5f) * ((2 * t - 1) + 1)) * Calc::pow(2, -10 * (2 * t - 1)) + 2);
}
float back_in(float t)
{
return t * t * t - t * Calc::sin(t * Calc::PI);
}
float back_out(float t)
{
float f = (1 - t);
return 1 - (f * f * f - f * Calc::sin(f * Calc::PI));
}
float back_in_out(float t)
{
if (t < 0.5f)
{
float f = 2 * t;
return 0.5f * (f * f * f - f * Calc::sin(f * Calc::PI));
}
else
{
float f = (1 - (2 * t - 1));
return 0.5f * (1 - (f * f * f - f * Calc::sin(f * Calc::PI))) + 0.5f;
}
}
float bounce_out(float t)
{
if (t < 4 / 11.0f)
return (121 * t * t) / 16.0f;
else if (t < 8 / 11.0f)
return (363 / 40.0f * t * t) - (99 / 10.0f * t) + 17 / 5.0f;
else if (t < 9 / 10.0f)
return (4356 / 361.0f * t * t) - (35442 / 1805.0f * t) + 16061 / 1805.0f;
else
return (54 / 5.0f * t * t) - (513 / 25.0f * t) + 268 / 25.0f;
}
float bounce_in(float t)
{
return 1 - bounce_out(1 - t);
}
float bounce_in_out(float t)
{
if (t < 0.5f)
return 0.5f * bounce_in(t * 2);
else
return 0.5f * bounce_out(t * 2 - 1) + 0.5f;
}
Easer get(Easers e)
{
switch (e)
{
case Easers::CubeIn: return &cube_in;
case Easers::CubeOut: return &cube_out;
case Easers::CubeInOut: return &cube_in_out;
case Easers::QuadIn: return &quad_in;
case Easers::QuadOut: return &quad_out;
case Easers::QuadInOut: return &quad_in_out;
case Easers::QuartIn: return &quart_in;
case Easers::QuartOut: return &quart_out;
case Easers::QuartInOut: return &quart_in_out;
case Easers::QuintIn: return &quint_in;
case Easers::QuintOut: return &quint_out;
case Easers::QuintInOut: return &quint_in_out;
case Easers::SineIn: return &sine_in;
case Easers::SineOut: return &sine_out;
case Easers::SineInOut: return &sine_in_out;
case Easers::CircIn: return &circ_in;
case Easers::CircOut: return &circ_out;
case Easers::CircInOut: return &circ_in_out;
case Easers::ExpIn: return &exp_in;
case Easers::ExpOut: return &exp_out;
case Easers::ExpInOut: return &exp_in_out;
case Easers::ElasticIn: return &elastic_in;
case Easers::ElasticOut: return &elastic_out;
case Easers::ElasticInOut: return &elastic_in_out;
case Easers::BackIn: return &back_in;
case Easers::BackOut: return &back_out;
case Easers::BackInOut: return &back_in_out;
case Easers::BounceIn: return &bounce_in;
case Easers::BounceOut: return &bounce_out;
case Easers::BounceInOut: return &bounce_in_out;
case Easers::_Count:
break;
}
BLAH_ERROR("Invalid Easer Type");
return nullptr;
}
const char* name(Easers e)
{
switch (e)
{
case Easers::CubeIn: return "CubeIn";
case Easers::CubeOut: return "CubeOut";
case Easers::CubeInOut: return "CubeInOut";
case Easers::QuadIn: return "QuadIn";
case Easers::QuadOut: return "QuadOut";
case Easers::QuadInOut: return "QuadInOut";
case Easers::QuartIn: return "QuartIn";
case Easers::QuartOut: return "QuartOut";
case Easers::QuartInOut: return "QuartInOut";
case Easers::QuintIn: return "QuintIn";
case Easers::QuintOut: return "QuintOut";
case Easers::QuintInOut: return "QuintInOut";
case Easers::SineIn: return "SineIn";
case Easers::SineOut: return "SineOut";
case Easers::SineInOut: return "SineInOut";
case Easers::CircIn: return "CircIn";
case Easers::CircOut: return "CircOut";
case Easers::CircInOut: return "CircInOut";
case Easers::ExpIn: return "ExpIn";
case Easers::ExpOut: return "ExpOut";
case Easers::ExpInOut: return "ExpInOut";
case Easers::ElasticIn: return "ElasticIn";
case Easers::ElasticOut: return "ElasticOut";
case Easers::ElasticInOut: return "ElasticInOut";
case Easers::BackIn: return "BackIn";
case Easers::BackOut: return "BackOut";
case Easers::BackInOut: return "BackInOut";
case Easers::BounceIn: return "BounceIn";
case Easers::BounceOut: return "BounceOut";
case Easers::BounceInOut: return "BounceInOut";
case Easers::_Count:
break;
}
return "<Invalid>";
}
}
}

157
public/blah/math/line.cpp Normal file
View File

@ -0,0 +1,157 @@
#include <blah/math/line.h>
#include <blah/math/rect.h>
#include <blah/math/vec2.h>
#include <blah/math/calc.h>
using namespace Blah;
Line::Line(float x0, float y0, float x1, float y1)
{
a.x = x0;
a.y = y0;
b.x = x1;
b.y = y1;
}
Line::Line(const Vec2& start, const Vec2& end)
: a(start), b(end) {}
Rect Line::bounds() const
{
Vec2 pos = Vec2(Calc::min(a.x, b.x), Calc::min(a.y, b.y));
return Rect(
pos,
Vec2(Calc::max(a.x, b.x) - pos.x, Calc::max(a.y, b.y) - pos.y)
);
}
Vec2 Line::closest_point(const Vec2& pt) const
{
Vec2 v = b - a;
Vec2 w = pt - a;
float t = Vec2::dot(w, v) / Vec2::dot(v, v);
t = Calc::clamp(t, 0, 1);
return v * t + a;
}
bool Line::intersects(const Rect& rect) const
{
char ca = rect.get_sector(a);
char cb = rect.get_sector(b);
if (ca == cb || (ca & cb) != 0)
return false;
char both = ca | cb;
// top
if ((both & 0b0100) != 0 && intersects(rect.top_line()))
return true;
// bottom
if ((both & 0b1000) != 0 && intersects(rect.bottom_line()))
return true;
// left
if ((both & 0b0001) != 0 && intersects(rect.left_line()))
return true;
// right
if ((both & 0b0010) != 0 && intersects(rect.right_line()))
return true;
return false;
}
bool Line::intersects(const Rect& rect, Vec2* out_intersection_point) const
{
char ca = rect.get_sector(a);
char cb = rect.get_sector(b);
if (ca == cb || (ca & cb) != 0)
return false;
char both = ca | cb;
// top
if ((both & 0b0100) != 0 && intersects(rect.top_line(), out_intersection_point))
return true;
// bottom
if ((both & 0b1000) != 0 && intersects(rect.bottom_line(), out_intersection_point))
return true;
// left
if ((both & 0b0001) != 0 && intersects(rect.left_line(), out_intersection_point))
return true;
// right
if ((both & 0b0010) != 0 && intersects(rect.right_line(), out_intersection_point))
return true;
return false;
}
bool Line::intersects(const Line& line) const
{
Vec2 e = b - a;
Vec2 d = line.b - line.a;
float e_dot_d_perp = e.x * d.y - e.y * d.x;
// if e dot d == 0, it means the lines are parallel
// so have infinite intersection points
if (e_dot_d_perp < 0.0001 && e_dot_d_perp > -0.0001)
return false;
Vec2 c = line.a - a;
float t = (c.x * d.y - c.y * d.x) / e_dot_d_perp;
if (t < 0 || t > 1)
return false;
float u = (c.x * e.y - c.y * e.x) / e_dot_d_perp;
if (u < 0 || u > 1)
return false;
return true;
}
bool Line::intersects(const Line& line, Vec2* intersection_point) const
{
Vec2 e = b - a;
Vec2 d = line.b - line.a;
float e_dot_d_perp = e.x * d.y - e.y * d.x;
// if e dot d == 0, it means the lines are parallel
// so have infinite intersection points
if (e_dot_d_perp < 0.0001 && e_dot_d_perp > -0.0001)
return false;
Vec2 c = line.a - a;
float t = (c.x * d.y - c.y * d.x) / e_dot_d_perp;
if (t < 0 || t > 1)
return false;
float u = (c.x * e.y - c.y * e.x) / e_dot_d_perp;
if (u < 0 || u > 1)
return false;
Vec2 i = (e * t) + a;
intersection_point->x = i.x;
intersection_point->y = i.y;
return true;
}
void Line::project(const Vec2& axis, float* min, float* max) const
{
float dot = a.x * axis.x + a.y * axis.y;
*min = dot;
*max = dot;
dot = b.x * axis.x + b.y * axis.y;
*min = Calc::min(dot, *min);
*max = Calc::max(dot, *max);
}
Line Line::operator +(const Vec2& rhs) const { return Line(a + rhs, b + rhs); }
Line Line::operator -(const Vec2& rhs) const { return Line(a - rhs, b - rhs); }

30
public/blah/math/line.h Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <blah/math/vec2.h>
namespace Blah
{
struct Rect;
struct Line
{
Vec2 a;
Vec2 b;
Line() {}
Line(float x0, float y0, float x1, float y1);
Line(const Vec2& start, const Vec2& end);
Rect bounds() const;
Vec2 closest_point(const Vec2& pt) const;
bool intersects(const Rect& rect) const;
bool intersects(const Rect& rect, Vec2* out_intersection_point) const;
bool intersects(const Line& line) const;
bool intersects(const Line& line, Vec2* out_intersection_point) const;
void project(const Vec2& axis, float* min, float* max) const;
Line operator +(const Vec2& rhs) const;
Line operator -(const Vec2& rhs) const;
};
}

187
public/blah/math/mat3x2.cpp Normal file
View File

@ -0,0 +1,187 @@
#include <blah/math/mat3x2.h>
#include <blah/math/vec2.h>
#include <string.h>
#include <math.h>
using namespace Blah;
Mat3x2::Mat3x2()
: m11(0), m12(0), m21(0), m22(0), m31(0), m32(0) {}
Mat3x2::Mat3x2(float m11, float m12, float m21, float m22, float m31, float m32)
: m11(m11), m12(m12), m21(m21), m22(m22), m31(m31), m32(m32) {}
Mat3x2 Mat3x2::invert() const
{
auto det = (m11 * m22) - (m21 * m12);
auto invDet = 1.0f / det;
return Mat3x2
{
m22 * invDet,
-m12 * invDet,
-m21 * invDet,
m11 * invDet,
(m21 * m32 - m31 * m22) * invDet,
(m31 * m12 - m11 * m32) * invDet
};
}
float Mat3x2::scaling_factor() const
{
return (float)sqrt((double)m11 * m11 + (double)m12 * m12);
}
const Mat3x2 Mat3x2::identity = Mat3x2(1, 0, 0, 1, 0, 0);
Mat3x2 Mat3x2::operator *(const Mat3x2& rhs) const { return multiply(*this, rhs); }
Mat3x2 Mat3x2::operator +(const Mat3x2& rhs) const { return add(*this, rhs); }
Mat3x2 Mat3x2::operator -(const Mat3x2& rhs) const { return subtract(*this, rhs); }
Mat3x2& Mat3x2::operator*=(const Mat3x2& rhs)
{
*this = multiply(*this, rhs);
return *this;
}
bool Mat3x2::operator ==(const Mat3x2& rhs)
{
return memcmp(this, &rhs, sizeof(Mat3x2));
}
bool Mat3x2::operator !=(const Mat3x2& rhs)
{
return !(*this == rhs);
}
Mat3x2 Mat3x2::create_translation(const Vec2& Vec2)
{
return create_translation(Vec2.x, Vec2.y);
}
Mat3x2 Mat3x2::create_translation(float x, float y)
{
return Mat3x2(1, 0, 0, 1, x, y);
}
Mat3x2 Mat3x2::create_scale(float scale)
{
return create_scale(scale, scale);
}
Mat3x2 Mat3x2::create_scale(Vec2 Vec2)
{
return create_scale(Vec2.x, Vec2.y);
}
Mat3x2 Mat3x2::create_scale(float x, float y)
{
return Mat3x2(x, 0, 0, y, 0, 0);
}
Mat3x2 Mat3x2::create_scale(float scale, Vec2 centerPoint)
{
Mat3x2 result;
float tx = centerPoint.x * (1 - scale);
float ty = centerPoint.y * (1 - scale);
result.m11 = scale;
result.m12 = 0.0f;
result.m21 = 0.0f;
result.m22 = scale;
result.m31 = tx;
result.m32 = ty;
return result;
}
Mat3x2 Mat3x2::create_scale(Vec2 scale, Vec2 centerPoint)
{
Mat3x2 result;
float tx = centerPoint.x * (1 - scale.x);
float ty = centerPoint.y * (1 - scale.y);
result.m11 = scale.x;
result.m12 = 0.0f;
result.m21 = 0.0f;
result.m22 = scale.y;
result.m31 = tx;
result.m32 = ty;
return result;
}
Mat3x2 Mat3x2::create_scale(float scaleX, float scaleY, Vec2 centerPoint)
{
Mat3x2 result;
float tx = centerPoint.x * (1 - scaleX);
float ty = centerPoint.y * (1 - scaleY);
result.m11 = scaleX;
result.m12 = 0.0f;
result.m21 = 0.0f;
result.m22 = scaleY;
result.m31 = tx;
result.m32 = ty;
return result;
}
Mat3x2 Mat3x2::create_rotation(float radians)
{
float c = cosf(radians);
float s = sinf(radians);
return Mat3x2(c, s, -s, c, 0, 0);
}
Mat3x2 Mat3x2::create_transform(const Vec2& position, const Vec2& origin, const Vec2& scale, float rotation)
{
Mat3x2 matrix = identity;
if (origin.x != 0 || origin.y != 0)
matrix = create_translation(-origin.x, -origin.y);
if (scale.x != 1 || scale.y != 1)
matrix = matrix * create_scale(scale);
if (rotation != 0)
matrix = matrix * create_rotation(rotation);
if (position.x != 0 || position.y != 0)
matrix = matrix * create_translation(position);
return matrix;
}
Mat3x2 Mat3x2::add(const Mat3x2& a, const Mat3x2& b)
{
return Mat3x2(
a.m11 + b.m11,
a.m12 + b.m12,
a.m21 + b.m21,
a.m22 + b.m22,
a.m31 + b.m31,
a.m21 + b.m32);
}
Mat3x2 Mat3x2::subtract(const Mat3x2& a, const Mat3x2& b)
{
return Mat3x2(
a.m11 - b.m11,
a.m12 - b.m12,
a.m21 - b.m21,
a.m22 - b.m22,
a.m31 - b.m31,
a.m21 - b.m32);
}
Mat3x2 Mat3x2::multiply(const Mat3x2& a, const Mat3x2& b)
{
return Mat3x2(a.m11 * b.m11 + a.m12 * b.m21,
a.m11 * b.m12 + a.m12 * b.m22,
a.m21 * b.m11 + a.m22 * b.m21,
a.m21 * b.m12 + a.m22 * b.m22,
a.m31 * b.m11 + a.m32 * b.m21 + b.m31,
a.m31 * b.m12 + a.m32 * b.m22 + b.m32);
}

47
public/blah/math/mat3x2.h Normal file
View File

@ -0,0 +1,47 @@
#pragma once
namespace Blah
{
struct Vec2;
struct Mat3x2
{
float m11;
float m12;
float m21;
float m22;
float m31;
float m32;
Mat3x2();
Mat3x2(float m11, float m12, float m21, float m22, float m31, float m32);
Mat3x2 invert() const;
float scaling_factor() const;
Mat3x2 operator *(const Mat3x2& rhs) const;
Mat3x2 operator +(const Mat3x2& rhs) const;
Mat3x2 operator -(const Mat3x2& rhs) const;
Mat3x2& operator *=(const Mat3x2& rhs);
bool operator==(const Mat3x2& rhs);
bool operator!=(const Mat3x2& rhs);
static const Mat3x2 identity;
static Mat3x2 create_translation(const Vec2& position);
static Mat3x2 create_translation(float x, float y);
static Mat3x2 create_scale(float scale);
static Mat3x2 create_scale(Vec2 scale);
static Mat3x2 create_scale(float x, float y);
static Mat3x2 create_scale(float scale, Vec2 centerPoint);
static Mat3x2 create_scale(Vec2 scale, Vec2 centerPoint);
static Mat3x2 create_scale(float scaleX, float scaleY, Vec2 centerPoint);
static Mat3x2 create_rotation(float radians);
static Mat3x2 create_transform(const Vec2& position, const Vec2& origin, const Vec2& scale, float rotation);
static Mat3x2 add(const Mat3x2& a, const Mat3x2& b);
static Mat3x2 subtract(const Mat3x2& a, const Mat3x2& b);
static Mat3x2 multiply(const Mat3x2& a, const Mat3x2& b);
};
}

109
public/blah/math/mat4x4.cpp Normal file
View File

@ -0,0 +1,109 @@
#include <blah/math/mat4x4.h>
using namespace Blah;
Mat4x4::Mat4x4() :
m11(0.0f), m12(0.0f), m13(0.0f), m14(0.0f),
m21(0.0f), m22(0.0f), m23(0.0f), m24(0.0f),
m31(0.0f), m32(0.0f), m33(0.0f), m34(0.0f),
m41(0.0f), m42(0.0f), m43(0.0f), m44(0.0f) {}
Mat4x4::Mat4x4(
float m11, float m12, float m13, float m14,
float m21, float m22, float m23, float m24,
float m31, float m32, float m33, float m34,
float m41, float m42, float m43, float m44) :
m11(m11), m12(m12), m13(m13), m14(m14),
m21(m21), m22(m22), m23(m23), m24(m24),
m31(m31), m32(m32), m33(m33), m34(m34),
m41(m41), m42(m42), m43(m43), m44(m44) {}
const Mat4x4 Mat4x4::Identity = Mat4x4(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
Mat4x4 Mat4x4::CreateOrthographic(float width, float height, float zNearPlane, float zFarPlane)
{
Mat4x4 result = Identity;
result.m11 = 2.0f / width;
result.m12 = result.m13 = result.m14 = 0.0f;
result.m22 = -2.0f / height;
result.m21 = result.m23 = result.m24 = 0.0f;
result.m33 = 1.0f / (zNearPlane - zFarPlane);
result.m31 = result.m32 = result.m34 = 0.0f;
result.m41 = result.m42 = 0.0f;
result.m43 = zNearPlane / (zNearPlane - zFarPlane);
result.m44 = 1.0f;
return result;
}
Mat4x4 Mat4x4::CreateOrthographicOffCenter(float left, float right, float bottom, float top, float zNearPlane, float zFarPlane)
{
Mat4x4 result = Identity;
result.m11 = 2.0f / (right - left);
result.m12 = result.m13 = result.m14 = 0.0f;
result.m22 = 2.0f / (top - bottom);
result.m21 = result.m23 = result.m24 = 0.0f;
result.m33 = 1.0f / (zNearPlane - zFarPlane);
result.m31 = result.m32 = result.m34 = 0.0f;
result.m41 = (left + right) / (left - right);
result.m42 = (top + bottom) / (bottom - top);
result.m43 = zNearPlane / (zNearPlane - zFarPlane);
result.m44 = 1.0f;
return result;
}
Mat4x4 Mat4x4::CreateTranslation(float xPosition, float yPosition, float zPosition)
{
Mat4x4 result = Identity;
result.m41 = xPosition;
result.m42 = yPosition;
result.m43 = zPosition;
return result;
}
Mat4x4 Mat4x4::CreateScale(float xScale, float yScale, float zScale)
{
Mat4x4 result = Identity;
result.m11 = xScale;
result.m22 = yScale;
result.m33 = zScale;
return result;
}
Mat4x4 Mat4x4::operator*(const Mat4x4& rhs)
{
Mat4x4 m;
m.m11 = m11 * rhs.m11 + m12 * rhs.m21 + m13 * rhs.m31 + m14 * rhs.m41;
m.m12 = m11 * rhs.m12 + m12 * rhs.m22 + m13 * rhs.m32 + m14 * rhs.m42;
m.m13 = m11 * rhs.m13 + m12 * rhs.m23 + m13 * rhs.m33 + m14 * rhs.m43;
m.m14 = m11 * rhs.m14 + m12 * rhs.m24 + m13 * rhs.m34 + m14 * rhs.m44;
m.m21 = m21 * rhs.m11 + m22 * rhs.m21 + m23 * rhs.m31 + m24 * rhs.m41;
m.m22 = m21 * rhs.m12 + m22 * rhs.m22 + m23 * rhs.m32 + m24 * rhs.m42;
m.m23 = m21 * rhs.m13 + m22 * rhs.m23 + m23 * rhs.m33 + m24 * rhs.m43;
m.m24 = m21 * rhs.m14 + m22 * rhs.m24 + m23 * rhs.m34 + m24 * rhs.m44;
m.m31 = m31 * rhs.m11 + m32 * rhs.m21 + m33 * rhs.m31 + m34 * rhs.m41;
m.m32 = m31 * rhs.m12 + m32 * rhs.m22 + m33 * rhs.m32 + m34 * rhs.m42;
m.m33 = m31 * rhs.m13 + m32 * rhs.m23 + m33 * rhs.m33 + m34 * rhs.m43;
m.m34 = m31 * rhs.m14 + m32 * rhs.m24 + m33 * rhs.m34 + m34 * rhs.m44;
m.m41 = m41 * rhs.m11 + m42 * rhs.m21 + m43 * rhs.m31 + m44 * rhs.m41;
m.m42 = m41 * rhs.m12 + m42 * rhs.m22 + m43 * rhs.m32 + m44 * rhs.m42;
m.m43 = m41 * rhs.m13 + m42 * rhs.m23 + m43 * rhs.m33 + m44 * rhs.m43;
m.m44 = m41 * rhs.m14 + m42 * rhs.m24 + m43 * rhs.m34 + m44 * rhs.m44;
return m;
}

44
public/blah/math/mat4x4.h Normal file
View File

@ -0,0 +1,44 @@
#pragma once
namespace Blah
{
struct Mat4x4
{
float m11;
float m12;
float m13;
float m14;
float m21;
float m22;
float m23;
float m24;
float m31;
float m32;
float m33;
float m34;
float m41;
float m42;
float m43;
float m44;
Mat4x4();
Mat4x4(
float m11, float m12, float m13, float m14,
float m21, float m22, float m23, float m24,
float m31, float m32, float m33, float m34,
float m41, float m42, float m43, float m44);
static const Mat4x4 Identity;
static Mat4x4 CreateOrthographic(float width, float height, float zNearPlane, float zFarPlane);
static Mat4x4 CreateOrthographicOffCenter(float left, float right, float bottom, float top, float zNearPlane, float zFarPlane);
static Mat4x4 CreateTranslation(float xPosition, float yPosition, float zPosition);
static Mat4x4 CreateScale(float xScale, float yScale, float zScale);
Mat4x4 operator* (const Mat4x4& rhs);
};
}

View File

@ -0,0 +1,51 @@
#include <blah/math/vec2.h>
#include <blah/math/mat3x2.h>
#include <math.h>
using namespace Blah;
Point::Point()
{
x = y = 0;
}
Point::Point(int px, int py)
{
x = px;
y = py;
}
Point Point::operator +(const Point rhs) const { return Point(x + rhs.x, y + rhs.y); }
Point Point::operator -(const Point rhs) const { return Point(x - rhs.x, y - rhs.y); }
Point Point::operator /(const int rhs) const { return Point(x / rhs, y / rhs); }
Point Point::operator *(const int rhs) const { return Point(x * rhs, y * rhs); }
Point Point::operator-() const { return Point(-x, -y); }
Point& Point::operator +=(const Point& rhs) { x += rhs.x; y += rhs.y; return *this; }
Point& Point::operator -=(const Point& rhs) { x -= rhs.x; y -= rhs.y; return *this; }
Point& Point::operator /=(const Point& rhs) { x /= rhs.x; y /= rhs.y; return *this; }
Point& Point::operator *=(const Point& rhs) { x *= rhs.x; y *= rhs.y; return *this; }
Point& Point::operator /=(int rhs) { x /= rhs; y /= rhs; return *this; }
Point& Point::operator *=(int rhs) { x *= rhs; y *= rhs; return *this; }
bool Point::operator ==(const Point& rhs) { return x == rhs.x && y == rhs.y; }
bool Point::operator !=(const Point& rhs) { return x != rhs.x || y != rhs.y; }
float Point::length() const
{
return sqrtf((float)(x * x + y * y));
}
int Point::length_squared() const
{
return x * x + y * y;
}
const Point Point::unitX = Point(1, 0);
const Point Point::unitY = Point(0, 1);
const Point Point::right = Point(1, 0);
const Point Point::up = Point(0, -1);
const Point Point::down = Point(0, 1);
const Point Point::left = Point(-1, 0);
const Point Point::zero = Point(0, 0);
const Point Point::one = Point(1, 1);

41
public/blah/math/point.h Normal file
View File

@ -0,0 +1,41 @@
#pragma once
namespace Blah
{
struct Point
{
int x;
int y;
Point();
Point(int px, int py);
Point operator +(const Point rhs) const;
Point operator -(const Point rhs) const;
Point operator /(const int rhs) const;
Point operator *(const int rhs) const;
Point operator -() const;
Point& operator +=(const Point& rhs);
Point& operator -=(const Point& rhs);
Point& operator /=(const Point& rhs);
Point& operator *=(const Point& rhs);
Point& operator /=(int rhs);
Point& operator *=(int rhs);
bool operator ==(const Point& rhs);
bool operator !=(const Point& rhs);
float length() const;
int length_squared() const;
static const Point unitX;
static const Point unitY;
static const Point right;
static const Point up;
static const Point down;
static const Point left;
static const Point zero;
static const Point one;
};
}

21
public/blah/math/quad.cpp Normal file
View File

@ -0,0 +1,21 @@
#include <blah/math/quad.h>
#include <blah/math/calc.h>
namespace Blah
{
void Quad::project(const Vec2& axis, float* min, float* max) const
{
float dot = Vec2::dot(a, axis);
*min = dot;
*max = dot;
dot = Vec2::dot(b, axis);
*min = Calc::min(dot, *min);
*max = Calc::max(dot, *max);
dot = Vec2::dot(c, axis);
*min = Calc::min(dot, *min);
*max = Calc::max(dot, *max);
dot = Vec2::dot(d, axis);
*min = Calc::min(dot, *min);
*max = Calc::max(dot, *max);
}
}

19
public/blah/math/quad.h Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <blah/math/vec2.h>
namespace Blah
{
struct Quad
{
Vec2 a;
Vec2 b;
Vec2 c;
Vec2 d;
Quad() {}
Quad(const Vec2& a, const Vec2& b, const Vec2& c, const Vec2& d)
: a(a), b(b), c(c), d(d) {}
void project(const Vec2& axis, float* min, float* max) const;
};
}

111
public/blah/math/rect.cpp Normal file
View File

@ -0,0 +1,111 @@
#include <blah/math/rect.h>
#include <blah/math/point.h>
#include <blah/math/rectI.h>
#include <blah/math/vec2.h>
#include <blah/math/calc.h>
#include <blah/math/mat3x2.h>
using namespace Blah;
Rect::Rect()
{
x = y = w = h = 0;
};
Rect::Rect(float rx, float ry, float rw, float rh)
{
x = rx;
y = ry;
w = rw;
h = rh;
}
Rect::Rect(Vec2 pos, Vec2 size)
{
x = pos.x;
y = pos.y;
w = size.x;
h = size.y;
}
Rect::Rect(RectI r)
{
x = (float)r.x;
y = (float)r.y;
w = (float)r.w;
h = (float)r.h;
}
Rect Rect::scale(float s)
{
x = (x * s);
y = (y * s);
w = (w * s);
h = (h * s);
return *this;
}
Rect Rect::scale(float sx, float sy)
{
x = (x * sx);
y = (y * sy);
w = (w * sx);
h = (h * sy);
return *this;
}
Line Rect::left_line() const { return Line(left(), top(), left(), bottom()); }
Line Rect::right_line() const { return Line(right(), top(), right(), bottom()); }
Line Rect::top_line() const { return Line(left(), top(), right(), top()); }
Line Rect::bottom_line() const { return Line(left(), bottom(), right(), bottom()); }
Rect Rect::operator+(const Vec2& rhs) const { return Rect(x + rhs.x, y + rhs.y, w, h); }
Rect Rect::operator-(const Vec2& rhs) const { return Rect(x - rhs.x, y - rhs.y, w, h); }
Rect& Rect::operator+=(const Vec2& rhs) { x += rhs.x; y += rhs.y; return *this; }
Rect& Rect::operator-=(const Vec2& rhs) { x -= rhs.x; y -= rhs.y; return *this; }
Rect Rect::transform(const Rect& rect, const Mat3x2& matrix)
{
return Rect(
(rect.x * matrix.m11) + (rect.y * matrix.m21) + matrix.m31,
(rect.x * matrix.m12) + (rect.y * matrix.m22) + matrix.m32,
(rect.w * matrix.m11) + (rect.h * matrix.m21),
(rect.w * matrix.m12) + (rect.h * matrix.m22));
}
Rect Rect::transform(float x, float y, float w, float h, const Mat3x2& matrix)
{
return Rect(
(x * matrix.m11) + (y * matrix.m21) + matrix.m31,
(x * matrix.m12) + (y * matrix.m22) + matrix.m32,
(w * matrix.m11) + (h * matrix.m21),
(w * matrix.m12) + (h * matrix.m22));
}
Rect Rect::from_points(Vec2& from, Vec2& to)
{
Vec2 min = Vec2(Calc::min(from.x, to.x), Calc::min(from.y, to.y));
Vec2 max = Vec2(Calc::max(from.x, to.x), Calc::max(from.y, to.y));
return Rect(min.x, min.y, max.x - min.x, max.y - min.y);
}
char Rect::get_sector(const Vec2& pt) const
{
char h;
if (pt.x < left())
h = 0b0001;
else if (pt.x >= right())
h = 0b0010;
else
h = 0;
char v;
if (pt.y < top())
v = 0b0100;
else if (pt.y >= bottom())
v = 0b1000;
else
v = 0;
return h | v;
}

101
public/blah/math/rect.h Normal file
View File

@ -0,0 +1,101 @@
#pragma once
#include <blah/math/point.h>
#include <blah/math/vec2.h>
#include <blah/math/rectI.h>
#include <blah/math/line.h>
namespace Blah
{
struct Mat3x2;
struct Rect
{
float x;
float y;
float w;
float h;
Rect();
Rect(float rx, float ry, float rw, float rh);
Rect(Vec2 pos, Vec2 size);
Rect(RectI r);
Rect scale(float s);
Rect scale(float sx, float sy);
float left() const { return x; }
float right() const { return x + w; }
float top() const { return y; }
float bottom() const { return y + h; }
Vec2 center() const { return Vec2(x + w / 2, y + h / 2); }
float center_x() const { return x + w / 2; }
float center_y() const { return y + h / 2; }
Vec2 top_left() const { return Vec2(x, y); }
Vec2 top_right() const { return Vec2(x + w, y); }
Vec2 bottom_right() const { return Vec2(x + w, y + h); }
Vec2 bottom_left() const { return Vec2(x, y + h); }
Vec2 center_left() const { return Vec2(x, y + h / 2); }
Vec2 center_right() const { return Vec2(x + w, y + h / 2); }
Vec2 middle_top() const { return Vec2(x + w / 2, y); }
Vec2 middle_bottom() const { return Vec2(x + w / 2, y + h); }
Line left_line() const;
Line right_line() const;
Line top_line() const;
Line bottom_line() const;
bool contains(const Point& pt) const { return pt.x >= x && pt.x < x + w && pt.y >= y && pt.y < y + h; }
bool contains(const Vec2& pt) const { return pt.x >= x && pt.x < x + w && pt.y >= y && pt.y < y + h; }
bool overlaps(const Rect& rect) const { return x + w >= rect.x && y + h >= rect.y && x < rect.x + rect.w && y < rect.y + rect.h; }
bool intersects(const Line& line) const { return line.intersects(*this); }
bool intersects(const Line& line, Vec2* out_intersection_point) const { return line.intersects(*this, out_intersection_point); }
bool intersects(const Vec2& line_from, const Vec2& line_to) const { return intersects(Line(line_from, line_to)); }
bool intersects(const Vec2& line_from, const Vec2& line_to, Vec2* out_intersection_point) const { return intersects(Line(line_from, line_to), out_intersection_point); }
Vec2 intersection_point(const Line& line) const
{
Vec2 ret;
if (line.intersects(*this, &ret))
return ret;
else
return Vec2::zero;
}
Vec2 intersection_point(const Vec2& line_from, const Vec2& line_to) const
{
Vec2 ret;
if (Line(line_from, line_to).intersects(*this, &ret))
return ret;
else
return Vec2::zero;
}
Rect inflate(float amount) { return Rect(x - amount, y - amount, w + amount * 2, h + amount * 2); }
/*
Rect Sectors:
0101 0100 0110
0001 0000 0010
1001 1000 1010
0000 = inside rectangle, all others refer to sectors relative to the rectangle
*/
char get_sector(const Vec2& pt) const;
bool operator==(const Rect& rhs) const { return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h; }
bool operator!=(const Rect& rhs) const { return !(*this == rhs); }
Rect operator+(const Vec2& rhs) const;
Rect operator-(const Vec2& rhs) const;
Rect& operator+=(const Vec2& rhs);
Rect& operator-=(const Vec2& rhs);
static Rect transform(const Rect& vec, const Mat3x2& matrix);
static Rect transform(float x, float y, float w, float h, const Mat3x2& matrix);
static Rect from_points(Vec2& from, Vec2& to);
};
}

View File

@ -0,0 +1,90 @@
#include <blah/math/rectI.h>
#include <blah/math/rect.h>
#include <blah/math/point.h>
#include <blah/math/vec2.h>
using namespace Blah;
RectI::RectI()
{
x = y = w = h = 0;
}
RectI::RectI(int rx, int ry, int rw, int rh)
{
x = rx;
y = ry;
w = rw;
h = rh;
}
RectI::RectI(Point pos, Point size)
{
x = pos.x;
y = pos.y;
w = size.x;
h = size.y;
}
Point RectI::center() const { return Point(centerX(), centerY()); }
Point RectI::top_left() const { return Point(left(), top()); }
Point RectI::top_right() const { return Point(right(), top()); }
Point RectI::bottom_left() const { return Point(left(), bottom()); }
Point RectI::bottom_right() const { return Point(right(), bottom()); }
bool RectI::contains(const Point& point) const
{
return point.x >= x && point.x < x + w && point.y >= y && point.y < y + h;
}
bool RectI::contains(const Vec2& point) const
{
return point.x >= x && point.x < x + w && point.y >= y && point.y < y + h;
}
char RectI::get_sector(const Point& pt) const
{
char h;
if (pt.x < left())
h = 0b0001;
else if (pt.x >= right())
h = 0b0010;
else
h = 0;
char v;
if (pt.y < top())
v = 0b0100;
else if (pt.y >= bottom())
v = 0b1000;
else
v = 0;
return h | v;
}
char RectI::get_sector(const Vec2& pt) const
{
char h;
if (pt.x < left())
h = 0b0001;
else if (pt.x >= right())
h = 0b0010;
else
h = 0;
char v;
if (pt.y < top())
v = 0b0100;
else if (pt.y >= bottom())
v = 0b1000;
else
v = 0;
return h | v;
}
RectI RectI::operator+(const Point& rhs) const { return RectI(x + rhs.x, y + rhs.y, w, h); }
RectI RectI::operator-(const Point& rhs) const { return RectI(x - rhs.x, y - rhs.y, w, h); }
RectI& RectI::operator+=(const Point& rhs) { x += rhs.x; y += rhs.y; return *this; }
RectI& RectI::operator-=(const Point& rhs) { x -= rhs.x; y -= rhs.y; return *this; }

64
public/blah/math/rectI.h Normal file
View File

@ -0,0 +1,64 @@
#pragma once
namespace Blah
{
struct Point;
struct Rect;
struct Vec2;
struct RectI
{
int x;
int y;
int w;
int h;
RectI();
RectI(int rx, int ry, int rw, int rh);
RectI(Point pos, Point size);
int left() const { return x; }
int right() const { return x + w; }
int top() const { return y; }
int bottom() const { return y + h; }
int centerX() const { return x + w / 2; }
int centerY() const { return y + h / 2; }
Point center() const;
Point top_left() const;
Point top_right() const;
Point bottom_left() const;
Point bottom_right() const;
bool overlaps(const RectI& other) const
{
return x < other.x + other.w
&& other.x < x + w
&& y < other.y + other.h
&& other.y < y + h;
}
bool contains(const Point& pt) const;
bool contains(const Vec2& pt) const;
/*
Rect Sectors:
0101 0100 0110
0001 0000 0010
1001 1000 1010
0000 = inside rectangle, all others refer to sectors relative to the rectangle
*/
char get_sector(const Point& pt) const;
char get_sector(const Vec2& pt) const;
bool operator==(const RectI& rhs) const { return x == rhs.x && y == rhs.y && w == rhs.w && h == rhs.h; }
bool operator!=(const RectI& rhs) const { return !(*this == rhs); }
RectI operator+(const Point& rhs) const;
RectI operator-(const Point& rhs) const;
RectI& operator+=(const Point& rhs);
RectI& operator-=(const Point& rhs);
RectI operator*(const int& rhs) const { return RectI(x * rhs, y * rhs, w * rhs, h * rhs); }
};
}

View File

@ -0,0 +1,26 @@
#include <blah/math/stopwatch.h>
#include <chrono>
using namespace std::chrono;
using namespace Blah;
Stopwatch::Stopwatch()
{
reset();
}
void Stopwatch::reset()
{
start_time = std::chrono::duration_cast<std::chrono::microseconds>(system_clock::now().time_since_epoch()).count();
}
uint64_t Stopwatch::milliseconds()
{
return microseconds() / 1000;
}
uint64_t Stopwatch::microseconds()
{
return std::chrono::duration_cast<std::chrono::microseconds>(system_clock::now().time_since_epoch()).count() - start_time;
}

View File

@ -0,0 +1,16 @@
#pragma once
#include <inttypes.h>
namespace Blah
{
class Stopwatch
{
public:
Stopwatch();
void reset();
uint64_t microseconds();
uint64_t milliseconds();
private:
uint64_t start_time;
};
}

122
public/blah/math/vec2.cpp Normal file
View File

@ -0,0 +1,122 @@
#include <blah/math/vec2.h>
#include <blah/math/mat3x2.h>
#include <blah/math/calc.h>
#include <math.h>
using namespace Blah;
Vec2::Vec2() { x = y = 0; }
Vec2::Vec2(float vx, float vy) { x = vx; y = vy; }
Vec2::Vec2(int vx, float vy) { x = (float)vx; y = vy; }
Vec2::Vec2(float vx, int vy) { x = vx; y = (float)vy; }
Vec2::Vec2(int vx, int vy) { x = (float)vx; y = (float)vy; }
Vec2::Vec2(Point p) { x = (float)p.x; y = (float)p.y; }
Vec2 Vec2::operator +(const Vec2 rhs) const { return Vec2(x + rhs.x, y + rhs.y); }
Vec2 Vec2::operator -(const Vec2 rhs) const { return Vec2(x - rhs.x, y - rhs.y); }
Vec2 Vec2::operator /(const float rhs) const { return Vec2(x / rhs, y / rhs); }
Vec2 Vec2::operator *(const float rhs) const { return Vec2(x * rhs, y * rhs); }
Vec2 Vec2::operator-() const { return Vec2(-x, -y); }
Vec2& Vec2::operator +=(const Vec2& rhs) { x += rhs.x; y += rhs.y; return *this; }
Vec2& Vec2::operator -=(const Vec2& rhs) { x -= rhs.x; y -= rhs.y; return *this; }
Vec2& Vec2::operator /=(const Vec2& rhs) { x /= rhs.x; y /= rhs.y; return *this; }
Vec2& Vec2::operator *=(const Vec2& rhs) { x *= rhs.x; y *= rhs.y; return *this; }
Vec2& Vec2::operator/=(float rhs) { x /= rhs; y /= rhs; return *this; }
Vec2& Vec2::operator*=(float rhs) { x *= rhs; y *= rhs; return *this; }
bool Vec2::operator ==(const Vec2& rhs) { return x == rhs.x && y == rhs.y; }
bool Vec2::operator !=(const Vec2& rhs) { return x != rhs.x || y != rhs.y; }
Vec2 Vec2::normal() const
{
if (x == 0 && y == 0)
return zero;
float length = this->length();
return Vec2(x / length, y / length);
}
float Vec2::length() const { return sqrtf(x * x + y * y); }
float Vec2::length_squared() const { return x * x + y * y; }
Vec2 Vec2::perpendicular() const
{
return Vec2(-y, x);
}
float Vec2::angle() const
{
return Calc::atan2(y, x);
}
float Vec2::dot(Vec2 a, Vec2 b) { return (a.x * b.x + a.y * b.y); }
float Vec2::dot(float x, float y, Vec2 b) { return (x * b.x + y * b.y); }
float Vec2::dot(float x1, float y1, float x2, float y2) { return (x1 * x2 + y1 * y2); }
Vec2 Vec2::transform(const Vec2& vec, const Mat3x2& matrix)
{
return Vec2(
(vec.x * matrix.m11) + (vec.y * matrix.m21) + matrix.m31,
(vec.x * matrix.m12) + (vec.y * matrix.m22) + matrix.m32);
}
Vec2 Vec2::transform(float x, float y, const Mat3x2& matrix)
{
return Vec2(
(x * matrix.m11) + (y * matrix.m21) + matrix.m31,
(x * matrix.m12) + (y * matrix.m22) + matrix.m32);
}
Vec2 Vec2::from_angle(float radians, float length)
{
return Vec2((float)cos(radians) * length, (float)sin(radians) * length);
}
Vec2 Vec2::from_angle(float radians)
{
return from_angle(radians, 1);
}
Vec2 Vec2::lerp(Vec2 a, Vec2 b, float t)
{
if (t == 0)
return a;
else if (t == 1)
return b;
else
return a + (b - a) * t;
}
Vec2 Vec2::bezier_lerp(Vec2 start, Vec2 b, Vec2 end, float t)
{
return lerp(lerp(start, b, t), lerp(b, end, t), t);
}
Vec2 Vec2::bezier_lerp(Vec2 start, Vec2 b, Vec2 c, Vec2 end, float t)
{
return bezier_lerp(lerp(start, b, t), lerp(b, c, t), lerp(c, end, t), t);
}
Vec2 Vec2::reflect(const Vec2& vector, const Vec2& normal)
{
float dot = vector.x * normal.x + vector.y * normal.y;
return Vec2(
vector.x - 2.0f * dot * normal.x,
vector.y - 2.0f * dot * normal.y);
}
const Vec2 Vec2::unit_x = Vec2(1, 0);
const Vec2 Vec2::unit_y = Vec2(0, 1);
const Vec2 Vec2::right = Vec2(1, 0);
const Vec2 Vec2::up = Vec2(0, -1);
const Vec2 Vec2::down = Vec2(0, 1);
const Vec2 Vec2::left = Vec2(-1, 0);
const Vec2 Vec2::zero = Vec2(0, 0);
const Vec2 Vec2::one = Vec2(1, 1);
#define DIAGONAL_UNIT 0.70710678118f
const Vec2 Vec2::up_right = Vec2(DIAGONAL_UNIT, -DIAGONAL_UNIT);
const Vec2 Vec2::up_left = Vec2(-DIAGONAL_UNIT, -DIAGONAL_UNIT);
const Vec2 Vec2::down_right = Vec2(DIAGONAL_UNIT, DIAGONAL_UNIT);
const Vec2 Vec2::down_left = Vec2(-DIAGONAL_UNIT, DIAGONAL_UNIT);
#undef DIAGONAL_UNIT

70
public/blah/math/vec2.h Normal file
View File

@ -0,0 +1,70 @@
#pragma once
#include <blah/math/point.h>
namespace Blah
{
struct Mat3x2;
struct Vec2
{
float x;
float y;
Vec2();
Vec2(float vx, float vy);
Vec2(int vx, float vy);
Vec2(float vx, int vy);
Vec2(int vx, int vy);
Vec2(Point p);
Vec2 operator +(const Vec2 rhs) const;
Vec2 operator -(const Vec2 rhs) const;
Vec2 operator /(const float rhs) const;
Vec2 operator *(const float rhs) const;
Vec2 operator -() const;
Vec2& operator +=(const Vec2& rhs);
Vec2& operator -=(const Vec2& rhs);
Vec2& operator /=(const Vec2& rhs);
Vec2& operator *=(const Vec2& rhs);
Vec2& operator /=(float rhs);
Vec2& operator *=(float rhs);
bool operator ==(const Vec2& rhs);
bool operator !=(const Vec2& rhs);
Vec2 normal() const;
float length() const;
float length_squared() const;
Vec2 perpendicular() const;
float angle() const;
static float dot(Vec2 a, Vec2 b);
static float dot(float x, float y, Vec2 b);
static float dot(float x1, float y1, float x2, float y2);
static Vec2 transform(const Vec2& vec, const Mat3x2& matrix);
static Vec2 transform(float x, float y, const Mat3x2& matrix);
static Vec2 from_angle(float radians, float length);
static Vec2 from_angle(float radians);
static Vec2 lerp(Vec2 start, Vec2 end, float t);
static Vec2 bezier_lerp(Vec2 start, Vec2 b, Vec2 end, float t);
static Vec2 bezier_lerp(Vec2 start, Vec2 b, Vec2 c, Vec2 end, float t);
static Vec2 reflect(const Vec2& vector, const Vec2& normal);
static const Vec2 unit_x;
static const Vec2 unit_y;
static const Vec2 right;
static const Vec2 up;
static const Vec2 down;
static const Vec2 left;
static const Vec2 zero;
static const Vec2 one;
static const Vec2 up_right;
static const Vec2 up_left;
static const Vec2 down_right;
static const Vec2 down_left;
};
}

15
public/blah/math/vec4.h Normal file
View File

@ -0,0 +1,15 @@
#pragma once
namespace Blah
{
struct Vec4
{
float x;
float y;
float z;
float w;
Vec4() : x(0), y(0), z(0), w(0) {}
Vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {}
};
}

View File

@ -0,0 +1,107 @@
#include <blah/streams/bufferstream.h>
#include <string.h>
using namespace Blah;
BufferStream::BufferStream()
: m_capacity(0), m_length(0), m_position(0) {}
BufferStream::BufferStream(int capacity)
: m_capacity(0), m_length(0), m_position(0)
{
if (capacity > 0)
{
m_buffer = new char[capacity];
m_capacity = capacity;
}
}
BufferStream::BufferStream(BufferStream&& src) noexcept
{
m_buffer = src.m_buffer;
m_length = src.m_length;
m_capacity = src.m_capacity;
m_position = src.m_position;
src.m_buffer = nullptr;
src.m_position = src.m_length = src.m_capacity = 0;
}
BufferStream& BufferStream::operator=(BufferStream&& src) noexcept
{
m_buffer = src.m_buffer;
m_length = src.m_length;
m_capacity = src.m_capacity;
m_position = src.m_position;
src.m_buffer = nullptr;
src.m_position = src.m_length = src.m_capacity = 0;
return *this;
}
BufferStream::~BufferStream()
{
delete[] m_buffer;
}
int64_t BufferStream::read_into(void* ptr, int64_t len)
{
if (m_buffer == nullptr || ptr == nullptr)
return 0;
if (len < 0)
return 0;
if (len > m_length - m_position)
len = m_length - m_position;
memcpy(ptr, m_buffer + m_position, (size_t)len);
m_position += len;
return len;
}
int64_t BufferStream::write_from(const void* ptr, int64_t len)
{
if (len < 0)
return 0;
// resize
if (m_position + len >= m_capacity)
{
auto last_capacity = m_capacity;
if (m_capacity <= 0)
m_capacity = 16;
while (m_position + len >= m_capacity)
m_capacity *= 2;
char* new_buffer = new char[m_capacity];
if (m_buffer != nullptr)
{
memcpy(new_buffer, m_buffer, last_capacity);
delete[] m_buffer;
}
m_buffer = new_buffer;
}
// copy data
if (ptr != nullptr)
memcpy(m_buffer + m_position, ptr, (size_t)len);
// increment position
m_position += len;
if (m_position > m_length)
m_length = m_position;
// return the amount we wrote
return len;
}
void BufferStream::close()
{
delete[] m_buffer;
m_buffer = nullptr;
m_position = 0;
m_length = 0;
m_capacity = 0;
}

View File

@ -0,0 +1,37 @@
#pragma once
#include <blah/streams/stream.h>
namespace Blah
{
class BufferStream : public Stream
{
public:
BufferStream();
BufferStream(int capacity);
BufferStream(BufferStream&& bs) noexcept;
BufferStream& operator=(BufferStream&& bs) noexcept;
~BufferStream();
virtual int64_t length() const override { return m_length; }
virtual int64_t position() const override { return m_position; }
virtual int64_t seek(int64_t seekTo) override { return m_position = (seekTo < 0 ? 0 : (seekTo > m_length ? m_length : seekTo)); }
virtual bool is_open() const override { return m_buffer != nullptr; }
virtual bool is_readable() const override { return true; }
virtual bool is_writable() const override { return true; }
virtual void close() override;
void clear() { m_length = m_position = 0; }
char* data() { return m_buffer; }
const char* data() const { return m_buffer; }
protected:
virtual int64_t read_into(void* ptr, int64_t length) override;
virtual int64_t write_from(const void* ptr, int64_t length) override;
private:
char* m_buffer;
int64_t m_capacity;
int64_t m_length;
int64_t m_position;
};
}

View File

@ -0,0 +1,96 @@
#include <blah/streams/filestream.h>
#include <blah/internal/platform.h>
#include <string.h>
using namespace Blah;
FileStream::FileStream()
{
m_handle = nullptr;
m_mode = FileMode::None;
}
FileStream::FileStream(const char* path, FileMode mode)
: m_mode(mode)
{
if (!Internal::Platform::file_open(path, &m_handle, mode))
m_handle = nullptr;
}
FileStream::FileStream(FileStream&& src) noexcept
{
m_handle = src.m_handle;
m_mode = src.m_mode;
src.m_handle = nullptr;
}
FileStream& FileStream::operator=(FileStream&& src) noexcept
{
m_handle = src.m_handle;
m_mode = src.m_mode;
src.m_handle = nullptr;
return *this;
}
FileStream::~FileStream()
{
if (m_handle != nullptr)
Internal::Platform::file_close(m_handle);
}
int64_t FileStream::length() const
{
if (m_handle == nullptr)
return 0;
return Internal::Platform::file_length(m_handle);
}
int64_t FileStream::position() const
{
if (m_handle == nullptr)
return 0;
return Internal::Platform::file_position(m_handle);
}
int64_t FileStream::seek(int64_t seek_to)
{
if (m_handle == nullptr)
return 0;
return Internal::Platform::file_seek(m_handle, seek_to);
}
int64_t FileStream::read_into(void* ptr, int64_t length)
{
if (m_handle == nullptr)
{
BLAH_ERROR("Unable to read from Stream");
return 0;
}
return Internal::Platform::file_read(m_handle, ptr, length);
}
int64_t FileStream::write_from(const void* ptr, int64_t length)
{
if (length <= 0)
return 0;
if (m_handle == nullptr)
{
BLAH_ERROR("Unable to write to Stream");
return 0;
}
return Internal::Platform::file_write(m_handle, ptr, length);
}
void FileStream::close()
{
if (m_handle != nullptr)
Internal::Platform::file_close(m_handle);
m_handle = nullptr;
m_mode = FileMode::None;
}

View File

@ -0,0 +1,32 @@
#pragma once
#include <blah/streams/stream.h>
#include <blah/filesystem.h>
namespace Blah
{
class FileStream : public Stream
{
public:
FileStream();
FileStream(const char* path, FileMode mode = FileMode::ReadWrite);
FileStream(FileStream&& fs) noexcept;
FileStream& operator=(FileStream&& fs) noexcept;
~FileStream();
virtual int64_t length() const override;
virtual int64_t position() const override;
virtual int64_t seek(int64_t seekTo) override;
virtual bool is_open() const override { return m_handle != nullptr; }
virtual bool is_readable() const override { return m_handle != nullptr && (m_mode == FileMode::ReadWrite || m_mode == FileMode::Read); }
virtual bool is_writable() const override { return m_handle != nullptr && (m_mode == FileMode::ReadWrite || m_mode == FileMode::Write); }
virtual void close() override;
protected:
virtual int64_t read_into(void* ptr, int64_t length) override;
virtual int64_t write_from(const void* ptr, int64_t length) override;
private:
FileMode m_mode;
void* m_handle;
};
}

View File

@ -0,0 +1,55 @@
#include <blah/streams/memorystream.h>
#include <string.h>
using namespace Blah;
MemoryStream::MemoryStream()
: m_data(nullptr), m_length(0), m_position(0) {}
MemoryStream::MemoryStream(char* data, int64_t length)
: m_data(data), m_length(length), m_position(0) {}
MemoryStream::MemoryStream(MemoryStream&& src) noexcept
{
m_data = src.m_data;
m_position = src.m_position;
m_length = src.m_length;
src.m_data = nullptr;
src.m_length = src.m_position = 0;
}
MemoryStream& MemoryStream::operator=(MemoryStream&& src) noexcept
{
m_data = src.m_data;
m_position = src.m_position;
m_length = src.m_length;
src.m_data = nullptr;
src.m_length = src.m_position = 0;
return *this;
}
int64_t MemoryStream::read_into(void* ptr, int64_t len)
{
if (len < 0 || ptr == nullptr)
return 0;
if (len > m_length - m_position)
len = m_length - m_position;
memcpy(ptr, m_data + m_position, (size_t)len);
m_position += len;
return len;
}
int64_t MemoryStream::write_from(const void* ptr, int64_t len)
{
if (len < 0 || ptr == nullptr)
return 0;
if (len > m_length - m_position)
len = m_length - m_position;
memcpy(m_data + m_position, ptr, (size_t)len);
m_position += len;
return len;
}

View File

@ -0,0 +1,35 @@
#pragma once
#include <blah/streams/stream.h>
namespace Blah
{
class MemoryStream : public Stream
{
public:
MemoryStream();
MemoryStream(char* data, int64_t length);
MemoryStream(MemoryStream&& ms) noexcept;
MemoryStream& operator=(MemoryStream&& ms) noexcept;
~MemoryStream() { m_data = nullptr; m_length = m_position = 0; }
virtual int64_t length() const override { return m_length; }
virtual int64_t position() const override { return m_position; }
virtual int64_t seek(int64_t seekTo) override { return m_position = (seekTo < 0 ? 0 : (seekTo > m_length ? m_length : seekTo)); }
virtual bool is_open() const override { return m_data != nullptr; }
virtual bool is_readable() const override { return true; }
virtual bool is_writable() const override { return true; }
virtual void close() override { m_data = nullptr; m_length = m_position = 0; }
char* data() { return m_data; }
const char* data() const { return m_data; }
protected:
virtual int64_t read_into(void* ptr, int64_t length) override;
virtual int64_t write_from(const void* ptr, int64_t length) override;
private:
char* m_data;
int64_t m_length;
int64_t m_position;
};
}

View File

@ -0,0 +1,122 @@
#include <blah/streams/stream.h>
#include <blah/containers/str.h>
#include <blah/internal/platform.h>
#include <string.h>
using namespace Blah;
Stream::Stream()
{
}
int64_t Stream::pipe(Stream& stream, int64_t length)
{
const int BUFFER_LENGTH = 4096;
int64_t result = 0;
char buffer[BUFFER_LENGTH];
while (length > 0)
{
auto step = length;
if (step > BUFFER_LENGTH)
step = BUFFER_LENGTH;
auto count = read(buffer, step);
auto wrote = stream.write(buffer, count);
result += wrote;
length -= step;
if (count < step || wrote < count)
break;
}
return result;
}
String Stream::read_line()
{
String string;
read_line(string);
return string;
}
int64_t Stream::read_line(String& writeTo)
{
const int bufferSize = 512;
char buffer[bufferSize];
int64_t pos = position();
int64_t length = 0;
int64_t count = 0;
bool hit = false;
// read chunk-by-chunk
do
{
count = (int)read(buffer, bufferSize);
pos += count;
// check for a newline
int64_t end = count;
for (int n = 0; n < count; n++)
if (buffer[n] == '\n' || buffer[n] == '\r')
{
hit = true;
end = n;
// skip to the end of the line for future reading
int64_t lineEnd = pos - count + end + 1;
// there might be a trailing '\n'
if (buffer[n] == '\r')
{
if (end < count && buffer[n + 1] == '\n')
{
lineEnd++;
}
// our buffer aligned perfectly ..... :/
else if (count == bufferSize && end == count)
{
char ch;
if (read(&ch, 1) != 0 && ch == '\n')
lineEnd++;
}
}
seek(lineEnd);
break;
}
// copy to string
writeTo.set_length((int)(length + end));
memcpy(writeTo.cstr() + length, buffer, (size_t)end);
*(writeTo.cstr() + length + end) = '\0';
// increment length
length += end;
} while (!hit && count >= bufferSize);
return length;
}
String Stream::read_string(int length)
{
if (length >= 0)
{
String str;
str.set_length(length);
read_into(str.cstr(), length);
str[length] = '\0';
return str;
}
else
{
String str;
char next;
while (read_into(&next, 1) && next != '\0')
str.append(next);
return str;
}
}

View File

@ -0,0 +1,108 @@
#pragma once
#include <inttypes.h>
#include <blah/containers/str.h>
#define BLAH_SWAP_ENDIAN(value, type) \
for (int i = 0; i < sizeof(type) / 2; i ++) { \
uint8_t* _ptr = (uint8_t*)&value;\
uint8_t _temp = *(_ptr + i); \
*(_ptr + i) = *(_ptr + sizeof(type) - i - 1); \
*(_ptr + sizeof(type) - i - 1) = _temp; \
}
#define BLAH_BIG_ENDIAN (*((short*)"AB") == 0x4243)
namespace Blah
{
enum class Endian
{
Little,
Big
};
class Stream
{
public:
Stream();
Stream(const Stream&) = delete;
Stream& operator=(const Stream&) = delete;
virtual ~Stream() = default;
// returns the length of the stream
virtual int64_t length() const = 0;
// returns the position of the stream
virtual int64_t position() const = 0;
// seeks the position of the stream
virtual int64_t seek(int64_t seek_to) = 0;
// returns true of the stream is open
virtual bool is_open() const = 0;
// returns true of the stream is readable
virtual bool is_readable() const = 0;
// returns true of the stream is writable
virtual bool is_writable() const = 0;
// closes the stream
virtual void close() = 0;
// pipes the contents of this tream to another stream
int64_t pipe(Stream& to, int64_t length);
// reads a single line from this stream (up until \r or \n)
String read_line();
// reada a single line from this stream, to the given string (up until \r or \n)
int64_t read_line(String& writeTo);
// reads a string of a given length, or until a null terminator if -1
String read_string(int length = -1);
// reads the amount of bytes into the given buffer, and returns the amount read
int64_t read(void* buffer, int64_t length) { return read_into(buffer, length); }
template<class T>
T read() { return read<T>(Endian::Little); }
template<class T>
T read(Endian endian)
{
T value;
read(&value, sizeof(T));
if ((endian == Endian::Little && BLAH_BIG_ENDIAN) || (endian == Endian::Big && !BLAH_BIG_ENDIAN))
BLAH_SWAP_ENDIAN(value, T);
return value;
}
int64_t write(const void* buffer, int64_t length) { return write_from(buffer, length); }
template<class T>
int64_t write(const T& value) { return write<T>(value, Endian::Little); }
template<class T>
int64_t write(const T& value, Endian endian)
{
T writing = value;
if ((endian == Endian::Little && BLAH_BIG_ENDIAN) || (endian == Endian::Big && !BLAH_BIG_ENDIAN))
BLAH_SWAP_ENDIAN(writing, T);
return write(&writing, sizeof(T));
}
protected:
// reads from the stream into the given buffer, and returns the number of bytes read
virtual int64_t read_into(void* buffer, int64_t length) = 0;
// writes from the stream from the given buffer, and returns the number of bytes written
virtual int64_t write_from(const void* buffer, int64_t length) = 0;
};
}
#undef BLAH_SWAP_ENDIAN
#undef BLAH_BIG_ENDIAN

54
public/blah/time.cpp Normal file
View File

@ -0,0 +1,54 @@
#include <blah/time.h>
#include <math.h>
using namespace Blah;
namespace
{
float modf(float x, float m)
{
return x - (int)(x / m) * m;
}
}
uint64_t Time::milliseconds = 0;
float Time::elapsed = 0;
float Time::delta = 0;
float Time::previous_elapsed = 0;
float Time::pause_timer = 0;
void Time::pause_for(float time)
{
if (time >= pause_timer)
pause_timer = time;
}
bool Time::on_interval(float time, float delta, float interval, float offset)
{
return floor((time - offset - delta) / interval) < floor((time - offset) / interval);
}
bool Time::on_interval(float delta, float interval, float offset)
{
return Time::on_interval(Time::elapsed, delta, interval, offset);
}
bool Time::on_interval(float interval, float offset)
{
return Time::on_interval(Time::elapsed, Time::delta, interval, offset);
}
bool Time::on_time(float time, float timestamp)
{
return time >= timestamp && time - Time::delta < timestamp;
}
bool Time::between_interval(float time, float interval, float offset)
{
return modf(time - offset, interval * 2) >= interval;
}
bool Time::between_interval(float interval, float offset)
{
return between_interval(Time::elapsed, interval, offset);
}

34
public/blah/time.h Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <inttypes.h>
namespace Blah
{
struct Time
{
// uptime, in milliseconds
static uint64_t milliseconds;
// uptime, in seconds
static float elapsed;
// previous frame uptime, in seconds
static float previous_elapsed;
// delta time from last frame
static float delta;
// time the application should pause for
static float pause_timer;
static void pause_for(float time);
static bool on_interval(float time, float delta, float interval, float offset = 0);
static bool on_interval(float delta, float interval, float offset = 0);
static bool on_interval(float interval, float offset = 0);
static bool on_time(float time, float timestamp);
static bool between_interval(float time, float interval, float offset = 0);
static bool between_interval(float interval, float offset = 0);
};
}