mirror of
https://github.com/NoelFB/blah.git
synced 2024-11-25 16:18:57 +08:00
first true commit
This commit is contained in:
parent
c20900d690
commit
8d7e5e4224
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
build/
|
||||
out/
|
||||
temp/
|
||||
.vs/
|
||||
.vscode/
|
||||
CMakeSettings.json
|
128
CMakeLists.txt
Normal file
128
CMakeLists.txt
Normal 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()
|
67
private/blah/internal/graphics.h
Normal file
67
private/blah/internal/graphics.h
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
1630
private/blah/internal/graphics_opengl.cpp
Normal file
1630
private/blah/internal/graphics_opengl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
57
private/blah/internal/input.h
Normal file
57
private/blah/internal/input.h
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
120
private/blah/internal/platform.h
Normal file
120
private/blah/internal/platform.h
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
652
private/blah/internal/platform_sdl2.cpp
Normal file
652
private/blah/internal/platform_sdl2.cpp
Normal 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
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
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
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
52
public/blah.h
Normal 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
238
public/blah/app.cpp
Normal 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
73
public/blah/app.h
Normal 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);
|
||||
}
|
||||
}
|
314
public/blah/containers/list.h
Normal file
314
public/blah/containers/list.h
Normal 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;
|
||||
}
|
||||
|
||||
}
|
212
public/blah/containers/stacklist.h
Normal file
212
public/blah/containers/stacklist.h
Normal 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];
|
||||
}
|
||||
|
||||
}
|
385
public/blah/containers/str.cpp
Normal file
385
public/blah/containers/str.cpp
Normal 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';
|
||||
}
|
185
public/blah/containers/str.h
Normal file
185
public/blah/containers/str.h
Normal 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>;
|
||||
}
|
832
public/blah/drawing/batch.cpp
Normal file
832
public/blah/drawing/batch.cpp
Normal 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
169
public/blah/drawing/batch.h
Normal 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);
|
||||
};
|
||||
}
|
250
public/blah/drawing/spritefont.cpp
Normal file
250
public/blah/drawing/spritefont.cpp
Normal 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;
|
||||
}
|
93
public/blah/drawing/spritefont.h
Normal file
93
public/blah/drawing/spritefont.h
Normal 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;
|
||||
};
|
||||
}
|
41
public/blah/drawing/subtexture.cpp
Normal file
41
public/blah/drawing/subtexture.cpp
Normal 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;
|
||||
}
|
||||
}
|
24
public/blah/drawing/subtexture.h
Normal file
24
public/blah/drawing/subtexture.h
Normal 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
136
public/blah/filesystem.cpp
Normal 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
41
public/blah/filesystem.h
Normal 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);
|
||||
}
|
||||
}
|
36
public/blah/graphics/framebuffer.h
Normal file
36
public/blah/graphics/framebuffer.h
Normal 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;
|
||||
}
|
340
public/blah/graphics/graphics.cpp
Normal file
340
public/blah/graphics/graphics.cpp
Normal 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);
|
||||
}
|
349
public/blah/graphics/graphics.h
Normal file
349
public/blah/graphics/graphics.h
Normal 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);
|
||||
}
|
||||
}
|
252
public/blah/graphics/material.cpp
Normal file
252
public/blah/graphics/material.cpp
Normal 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();
|
||||
}
|
66
public/blah/graphics/material.h
Normal file
66
public/blah/graphics/material.h
Normal 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;
|
||||
}
|
47
public/blah/graphics/mesh.h
Normal file
47
public/blah/graphics/mesh.h
Normal 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;
|
||||
}
|
36
public/blah/graphics/shader.h
Normal file
36
public/blah/graphics/shader.h
Normal 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;
|
||||
}
|
57
public/blah/graphics/texture.h
Normal file
57
public/blah/graphics/texture.h
Normal 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;
|
||||
}
|
466
public/blah/images/aseprite.cpp
Normal file
466
public/blah/images/aseprite.cpp
Normal 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");
|
||||
}
|
||||
}
|
152
public/blah/images/aseprite.h
Normal file
152
public/blah/images/aseprite.h
Normal 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
244
public/blah/images/font.cpp
Normal 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
62
public/blah/images/font.h
Normal 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;
|
||||
};
|
||||
}
|
284
public/blah/images/image.cpp
Normal file
284
public/blah/images/image.cpp
Normal 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;
|
||||
}
|
38
public/blah/images/image.h
Normal file
38
public/blah/images/image.h
Normal 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);
|
||||
};
|
||||
}
|
336
public/blah/images/packer.cpp
Normal file
336
public/blah/images/packer.cpp
Normal 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;
|
||||
}
|
71
public/blah/images/packer.h
Normal file
71
public/blah/images/packer.h
Normal 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
378
public/blah/input/input.cpp
Normal 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
431
public/blah/input/input.h
Normal 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);
|
||||
}
|
||||
}
|
4
public/blah/input/virtual_axis.cpp
Normal file
4
public/blah/input/virtual_axis.cpp
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
#include <blah/input/virtual_axis.h>
|
||||
|
||||
using namespace Blah;
|
84
public/blah/input/virtual_axis.h
Normal file
84
public/blah/input/virtual_axis.h
Normal 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; }
|
||||
};
|
||||
}
|
4
public/blah/input/virtual_button.cpp
Normal file
4
public/blah/input/virtual_button.cpp
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
#include <blah/input/virtual_button.h>
|
||||
|
||||
using namespace Blah;
|
83
public/blah/input/virtual_button.h
Normal file
83
public/blah/input/virtual_button.h
Normal 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; }
|
||||
};
|
||||
}
|
4
public/blah/input/virtual_stick.cpp
Normal file
4
public/blah/input/virtual_stick.cpp
Normal file
|
@ -0,0 +1,4 @@
|
|||
#pragma once
|
||||
#include <blah/input/virtual_stick.h>
|
||||
|
||||
using namespace Blah;
|
95
public/blah/input/virtual_stick.h
Normal file
95
public/blah/input/virtual_stick.h
Normal 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
60
public/blah/log.cpp
Normal 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
34
public/blah/log.h
Normal 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
135
public/blah/math/calc.cpp
Normal 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
71
public/blah/math/calc.h
Normal 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();
|
||||
};
|
||||
}
|
11
public/blah/math/circle.cpp
Normal file
11
public/blah/math/circle.cpp
Normal 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
19
public/blah/math/circle.h
Normal 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
156
public/blah/math/color.cpp
Normal 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
64
public/blah/math/color.h
Normal 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
336
public/blah/math/ease.h
Normal 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
157
public/blah/math/line.cpp
Normal 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
30
public/blah/math/line.h
Normal 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
187
public/blah/math/mat3x2.cpp
Normal 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
47
public/blah/math/mat3x2.h
Normal 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
109
public/blah/math/mat4x4.cpp
Normal 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
44
public/blah/math/mat4x4.h
Normal 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);
|
||||
};
|
||||
}
|
51
public/blah/math/point.cpp
Normal file
51
public/blah/math/point.cpp
Normal 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
41
public/blah/math/point.h
Normal 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
21
public/blah/math/quad.cpp
Normal 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
19
public/blah/math/quad.h
Normal 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
111
public/blah/math/rect.cpp
Normal 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
101
public/blah/math/rect.h
Normal 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);
|
||||
};
|
||||
}
|
90
public/blah/math/rectI.cpp
Normal file
90
public/blah/math/rectI.cpp
Normal 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
64
public/blah/math/rectI.h
Normal 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); }
|
||||
};
|
||||
}
|
26
public/blah/math/stopwatch.cpp
Normal file
26
public/blah/math/stopwatch.cpp
Normal 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;
|
||||
}
|
16
public/blah/math/stopwatch.h
Normal file
16
public/blah/math/stopwatch.h
Normal 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
122
public/blah/math/vec2.cpp
Normal 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
70
public/blah/math/vec2.h
Normal 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
15
public/blah/math/vec4.h
Normal 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) {}
|
||||
};
|
||||
}
|
107
public/blah/streams/bufferstream.cpp
Normal file
107
public/blah/streams/bufferstream.cpp
Normal 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;
|
||||
}
|
37
public/blah/streams/bufferstream.h
Normal file
37
public/blah/streams/bufferstream.h
Normal 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;
|
||||
};
|
||||
}
|
96
public/blah/streams/filestream.cpp
Normal file
96
public/blah/streams/filestream.cpp
Normal 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;
|
||||
}
|
32
public/blah/streams/filestream.h
Normal file
32
public/blah/streams/filestream.h
Normal 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;
|
||||
};
|
||||
}
|
55
public/blah/streams/memorystream.cpp
Normal file
55
public/blah/streams/memorystream.cpp
Normal 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;
|
||||
}
|
35
public/blah/streams/memorystream.h
Normal file
35
public/blah/streams/memorystream.h
Normal 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;
|
||||
};
|
||||
}
|
122
public/blah/streams/stream.cpp
Normal file
122
public/blah/streams/stream.cpp
Normal 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;
|
||||
}
|
||||
}
|
108
public/blah/streams/stream.h
Normal file
108
public/blah/streams/stream.h
Normal 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
54
public/blah/time.cpp
Normal 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
34
public/blah/time.h
Normal 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);
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user