restructured project to match a more standard cmake setup

This commit is contained in:
Noel Berry
2020-12-31 13:43:23 -08:00
parent c841bd82a1
commit 241d863ac4
97 changed files with 233 additions and 264 deletions

429
src/containers/str.cpp Normal file
View File

@ -0,0 +1,429 @@
#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;
}
String Str::substr(int start) const
{
if (start < 0) start = 0;
if (start > m_length) start = m_length;
return String(data() + start);
}
String 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);
}
Vector<String> Str::split(char ch) const
{
Vector<String> result;
const char* ptr = data();
int last = 0;
int index = 1;
while (index < m_length)
{
if (ptr[index] == ch)
{
result.push_back(substr(last, index));
last = index + 1;
}
index++;
}
if (last < index)
result.push_back(substr(last, index));
return result;
}
Str& Str::replace(const Str& os, const Str& ns)
{
for (int i = 0; i < m_length - os.m_length + 1; i++)
{
if (strcmp(data() + i, os.data()) == 0)
{
if (ns.m_length > os.m_length)
reserve(ns.m_length - os.m_length);
memcpy(data() + i + os.m_length, data() + i + ns.m_length, m_length - i - os.m_length);
memcpy(data() + i, ns.cstr(), ns.m_length);
set_length(m_length + ns.m_length - os.m_length);
i += os.m_length - 1;
}
}
return *this;
}
Str& Str::replace(char c, char r)
{
char* ptr = data();
for (int n = 0; n < m_length; n++)
if (ptr[n] == c)
ptr[n] = r;
return *this;
}
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';
}

296
src/core/app.cpp Normal file
View File

@ -0,0 +1,296 @@
#include <blah/core/app.h>
#include <blah/core/log.h>
#include <blah/core/time.h>
#include <blah/math/point.h>
#include <blah/graphics/framebuffer.h>
#include "../internal/platform_backend.h"
#include "../internal/graphics_backend.h"
#include "../internal/input_backend.h"
using namespace Blah;
namespace
{
Config app_config;
bool app_is_running = false;
bool app_is_exiting = false;
}
Config::Config()
{
name = nullptr;
width = 0;
height = 0;
target_framerate = 60;
max_updates = 5;
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 ...");
// initialize the system
if (!PlatformBackend::init(&app_config))
{
Log::error("Failed to initialize Platform module");
return false;
}
// initialize graphics
if (!GraphicsBackend::init())
{
Log::error("Failed to initialize Graphics module");
return false;
}
// input
InputBackend::init();
// startup
if (app_config.on_startup != nullptr)
app_config.on_startup();
uint64_t time_last = PlatformBackend::time();
uint64_t time_accumulator = 0;
// display window
PlatformBackend::ready();
while (!app_is_exiting)
{
// poll system events
PlatformBackend::frame();
// update at a fixed timerate
// TODO: allow a non-fixed step update?
{
uint64_t time_target = (uint64_t)((1.0f / app_config.target_framerate) * 1000);
uint64_t time_curr = PlatformBackend::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)
{
PlatformBackend::sleep((int)(time_target - time_accumulator));
time_curr = PlatformBackend::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::delta = (1.0f / app_config.target_framerate);
if (Time::pause_timer > 0)
{
Time::pause_timer -= Time::delta;
if (Time::pause_timer <= -0.0001f)
Time::delta = -Time::pause_timer;
else
continue;
}
Time::milliseconds += time_target;
Time::previous_elapsed = Time::elapsed;
Time::elapsed += Time::delta;
InputBackend::frame();
GraphicsBackend::frame();
if (app_config.on_update != nullptr)
app_config.on_update();
}
}
// render
{
GraphicsBackend::before_render();
if (app_config.on_render != nullptr)
app_config.on_render();
GraphicsBackend::after_render();
PlatformBackend::present();
}
}
Log::print("Shutting down ...");
// shutdown
if (app_config.on_shutdown != nullptr)
app_config.on_shutdown();
GraphicsBackend::shutdown();
PlatformBackend::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 PlatformBackend::app_path();
}
const char* App::user_path()
{
return PlatformBackend::user_path();
}
int App::width()
{
int w, h;
PlatformBackend::get_size(&w, &h);
return w;
}
int App::height()
{
int w, h;
PlatformBackend::get_size(&w, &h);
return h;
}
int App::draw_width()
{
int w, h;
PlatformBackend::get_draw_size(&w, &h);
return w;
}
int App::draw_height()
{
int w, h;
PlatformBackend::get_draw_size(&w, &h);
return h;
}
float App::content_scale()
{
return PlatformBackend::get_content_scale();
}
void App::fullscreen(bool enabled)
{
PlatformBackend::set_fullscreen(enabled);
}
Renderer App::renderer()
{
return GraphicsBackend::renderer();
}
const RendererFeatures& Blah::App::renderer_features()
{
return GraphicsBackend::features();
}
namespace
{
// A dummy Frame Buffer that represents the Back Buffer
// it doesn't actually contain any textures or details.
class BackBuffer final : public FrameBuffer
{
Attachments empty_attachments;
TextureRef empty_texture;
virtual Attachments& attachments() override
{
BLAH_ASSERT(false, "Backbuffer doesn't have any attachments");
return empty_attachments;
}
virtual const Attachments& attachments() const override
{
BLAH_ASSERT(false, "Backbuffer doesn't have any attachments");
return empty_attachments;
}
virtual TextureRef& attachment(int index) override
{
BLAH_ASSERT(false, "Backbuffer doesn't have any attachments");
return empty_texture;
}
virtual const TextureRef& attachment(int index) const override
{
BLAH_ASSERT(false, "Backbuffer doesn't have any attachments");
return empty_texture;
}
virtual int width() const override
{
return App::draw_width();
}
virtual int height() const override
{
return App::draw_height();
}
virtual void clear(Color color) override
{
GraphicsBackend::clear_backbuffer(color);
}
};
}
extern const FrameBufferRef App::backbuffer = FrameBufferRef(new BackBuffer());

136
src/core/filesystem.cpp Normal file
View File

@ -0,0 +1,136 @@
#include <blah/core/filesystem.h>
#include "../internal/platform_backend.h"
using namespace Blah;
bool File::exists(const FilePath& path)
{
return PlatformBackend::file_exists(path.cstr());
}
bool File::remove(const FilePath& path)
{
return PlatformBackend::file_delete(path.cstr());
}
bool Directory::create(const FilePath& path)
{
return PlatformBackend::dir_create(path.cstr());
}
bool Directory::exists(const FilePath& path)
{
return PlatformBackend::dir_exists(path.cstr());
}
bool Directory::remove(const FilePath& path)
{
return PlatformBackend::dir_delete(path.cstr());
}
Vector<FilePath> Directory::enumerate(const FilePath& path, bool recursive)
{
Vector<FilePath> list;
// get files
PlatformBackend::dir_enumerate(list, path.cstr(), recursive);
// normalize path names
for (auto& it : list)
it.replace('\\', '/');
return list;
}
void Directory::explore(const FilePath& path)
{
PlatformBackend::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;
}

60
src/core/log.cpp Normal file
View File

@ -0,0 +1,60 @@
#include <blah/core/log.h>
#include <blah/core/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_MESSAGE];
va_list ap;
va_start(ap, format);
vsnprintf(msg, sizeof(char) * BLAH_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_MESSAGE];
va_list ap;
va_start(ap, format);
vsnprintf(msg, sizeof(char) * BLAH_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_MESSAGE];
va_list ap;
va_start(ap, format);
vsnprintf(msg, sizeof(char) * BLAH_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);
}
}

54
src/core/time.cpp Normal file
View File

@ -0,0 +1,54 @@
#include <blah/core/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);
}

1080
src/drawing/batch.cpp Normal file

File diff suppressed because it is too large Load Diff

258
src/drawing/spritefont.cpp Normal file
View File

@ -0,0 +1,258 @@
#include <blah/drawing/spritefont.h>
#include <blah/images/font.h>
#include <blah/images/packer.h>
#include <blah/core/log.h>
using namespace Blah;
SpriteFont::SpriteFont()
{
size = 0;
ascent = 0;
descent = 0;
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
{
name = src.name;
size = src.size;
ascent = src.ascent;
descent = src.descent;
line_gap = src.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.clear();
m_characters.clear();
m_kerning.clear();
name.dispose();
}
SpriteFont& SpriteFont::operator=(SpriteFont && src) noexcept
{
name = src.name;
size = src.size;
ascent = src.ascent;
descent = src.descent;
line_gap = src.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 - line_gap;
}
void SpriteFont::build(const char* file, float sz, const uint32_t* charset)
{
dispose();
Font font(file);
if (font.is_valid())
build(font, sz, charset);
}
void SpriteFont::build(const Font& font, float size, const uint32_t* charset)
{
dispose();
float scale = font.get_scale(size);
name = font.family_name();
ascent = font.ascent() * scale;
descent = font.descent() * scale;
line_gap = font.line_gap() * scale;
this->size = size;
Packer packer;
packer.spacing = 0;
packer.padding = 1;
packer.max_size = 8192;
packer.power_of_two = true;
std::unordered_map<uint32_t, int> glyphs;
Vector<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.get_glyph(i);
if (glyph <= 0)
continue;
glyphs[i] = glyph;
// add character
Font::Char ch = font.get_character(glyph, scale);
m_characters[i].advance = ch.advance;
m_characters[i].offset = Vec2(ch.offset_x, ch.offset_y);
// pack glyph
if (ch.has_glyph)
{
if (buffer.size() < ch.width * ch.height)
buffer.resize(ch.width * ch.height);
if (font.get_image(ch, buffer.data()))
packer.add(i, ch.width, ch.height, buffer.data());
}
}
ranges += 2;
}
buffer.clear();
packer.pack();
for (auto& it : packer.pages)
m_atlas.push_back(Texture::create(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++)
{
auto kern = font.get_kerning(a->second, b->second, scale);
if (kern != 0)
set_kerning(a->first, b->first, kern);
}
}
float SpriteFont::get_kerning(uint32_t codepoint0, uint32_t codepoint1) const
{
uint64_t index = ((uint64_t)codepoint0 << 32) | 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)
{
uint64_t index = ((uint64_t)codepoint0 << 32) | codepoint1;
if (value == 0)
{
m_kerning.erase(index);
}
else
{
m_kerning[index] = value;
}
}
const SpriteFont::Character& SpriteFont::get_character(uint32_t codepoint) const
{
static const Character empty;
auto it = m_characters.find(codepoint);
if (it != m_characters.end())
return it->second;
return empty;
}
const SpriteFont::Character& SpriteFont::operator[](uint32_t codepoint) const
{
static const Character empty;
auto it = m_characters.find(codepoint);
if (it != m_characters.end())
return it->second;
return empty;
}

View File

@ -0,0 +1,64 @@
#include <blah/drawing/subtexture.h>
#include <blah/math/calc.h>
using namespace Blah;
Subtexture::Subtexture() {}
Subtexture::Subtexture(const TextureRef& texture)
: Subtexture(texture, Rect(0, 0, (float)texture->width(), (float)texture->height())) {}
Subtexture::Subtexture(const TextureRef& texture, Rect source)
: Subtexture(texture, source, Rect(0, 0, source.w, source.h)) {}
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)
{
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;
}
}
void Subtexture::crop_info(const Rect& clip, Rect* dest_source, Rect* dest_frame) const
{
*dest_source = (clip + source.top_left() + frame.top_left()).overlap_rect(source);
dest_frame->x = Calc::min(0.0f, frame.x + clip.x);
dest_frame->y = Calc::min(0.0f, frame.y + clip.y);
dest_frame->w = clip.w;
dest_frame->h = clip.h;
}
Subtexture Subtexture::crop(const Rect& clip) const
{
Subtexture dst;
dst.texture = texture;
crop_info(clip, &dst.source, &dst.frame);
dst.update();
return dst;
}

25
src/graphics/blend.cpp Normal file
View File

@ -0,0 +1,25 @@
#include <blah/graphics/blend.h>
using namespace Blah;
const BlendMode BlendMode::Normal = BlendMode(
BlendOp::Add,
BlendFactor::One,
BlendFactor::OneMinusSrcAlpha,
BlendOp::Add,
BlendFactor::One,
BlendFactor::OneMinusSrcAlpha,
BlendMask::RGBA,
0xffffffff
);
const BlendMode BlendMode::Subtract = BlendMode(
BlendOp::ReverseSubtract,
BlendFactor::One,
BlendFactor::One,
BlendOp::Add,
BlendFactor::One,
BlendFactor::One,
BlendMask::RGBA,
0xffffffff
);

View File

@ -0,0 +1,35 @@
#include <blah/graphics/framebuffer.h>
#include "../internal/graphics_backend.h"
using namespace Blah;
FrameBufferRef FrameBuffer::create(int width, int height)
{
static const TextureFormat attachment = TextureFormat::RGBA;
return create(width, height, &attachment, 1);
}
FrameBufferRef FrameBuffer::create(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_ATTACHMENTS, "Exceeded maximum attachment count");
BLAH_ASSERT(attachment_count > 0, "At least one attachment must be provided");
int color_count = 0;
int depth_count = 0;
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");
if (attachments[i] == TextureFormat::DepthStencil)
depth_count++;
else
color_count++;
}
BLAH_ASSERT(depth_count <= 1, "FrameBuffer can only have 1 Depth/Stencil Texture");
BLAH_ASSERT(color_count <= BLAH_ATTACHMENTS - 1, "Exceeded maximum Color attachment count");
return GraphicsBackend::create_framebuffer(width, height, attachments, attachment_count);
}

355
src/graphics/material.cpp Normal file
View File

@ -0,0 +1,355 @@
#include <blah/graphics/material.h>
#include <blah/core/log.h>
using namespace Blah;
namespace
{
int calc_uniform_size(const UniformInfo& uniform)
{
int 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;
}
}
MaterialRef Material::create(const ShaderRef& shader)
{
BLAH_ASSERT(shader, "The provided shader is invalid");
if (shader)
return MaterialRef(new Material(shader));
return MaterialRef();
}
Material::Material(const ShaderRef& shader)
{
BLAH_ASSERT(shader, "Material is being created with an invalid shader");
m_shader = shader;
auto& uniforms = shader->uniforms();
int float_size = 0;
for (auto& uniform : uniforms)
{
if (uniform.type == UniformType::None)
continue;
if (uniform.type == UniformType::Texture2D)
{
for (int i = 0; i < uniform.array_length; i ++)
m_textures.push_back(TextureRef());
continue;
}
if (uniform.type == UniformType::Sampler2D)
{
for (int i = 0; i < uniform.array_length; i++)
m_samplers.push_back(TextureSampler());
continue;
}
float_size += calc_uniform_size(uniform);
}
m_data.expand(float_size);
}
const ShaderRef Material::shader() const
{
return m_shader;
}
void Material::set_texture(const char* name, const TextureRef& texture, int index)
{
BLAH_ASSERT(m_shader, "Material Shader is invalid");
int offset = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type != UniformType::Texture2D)
continue;
if (strcmp(uniform.name, name) == 0)
{
m_textures[offset + index] = texture;
return;
}
offset += uniform.array_length;
if (offset + index >= m_textures.size())
break;
}
Log::warn("No Texture Uniform '%s' at index [%i] exists", name, index);
}
void Material::set_texture(int slot, const TextureRef& texture, int index)
{
BLAH_ASSERT(m_shader, "Material Shader is invalid");
int s = 0;
int offset = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type != UniformType::Texture2D)
continue;
if (s == slot)
{
if (index > uniform.array_length)
break;
m_textures[offset + index] = texture;
break;
}
offset += uniform.array_length;
s++;
}
}
TextureRef Material::get_texture(const char* name, int index) const
{
BLAH_ASSERT(m_shader, "Material Shader is invalid");
int offset = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type != UniformType::Texture2D)
continue;
if (strcmp(uniform.name, name) == 0)
return m_textures[offset + index];
offset += uniform.array_length;
if (offset + index >= m_textures.size())
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_shader, "Material Shader is invalid");
int s = 0;
int offset = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type != UniformType::Texture2D)
continue;
if (s == slot)
{
if (index > uniform.array_length)
break;
return m_textures[offset + index];
}
offset += uniform.array_length;
if (offset + index >= m_textures.size())
break;
s++;
}
Log::warn("No Texture Uniform ['%i'] at index [%i] exists", slot, index);
return TextureRef();
}
void Material::set_sampler(const char* name, const TextureSampler& sampler, int index)
{
BLAH_ASSERT(m_shader, "Material Shader is invalid");
int offset = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type != UniformType::Sampler2D)
continue;
if (strcmp(uniform.name, name) == 0)
{
m_samplers[offset + index] = sampler;
return;
}
offset += uniform.array_length;
if (offset + index >= m_samplers.size())
break;
}
Log::warn("No Sampler Uniform '%s' at index [%i] exists", name, index);
}
void Material::set_sampler(int slot, const TextureSampler& sampler, int index)
{
BLAH_ASSERT(m_shader, "Material Shader is invalid");
int s = 0;
int offset = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type != UniformType::Sampler2D)
continue;
if (s == slot)
{
if (index > uniform.array_length)
break;
m_samplers[offset + index] = sampler;
break;
}
offset += uniform.array_length;
s++;
}
}
TextureSampler Material::get_sampler(const char* name, int index) const
{
BLAH_ASSERT(m_shader, "Material Shader is invalid");
int offset = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type != UniformType::Sampler2D)
continue;
if (strcmp(uniform.name, name) == 0)
return m_samplers[offset + index];
offset += uniform.array_length;
if (offset + index >= m_samplers.size())
break;
}
Log::warn("No Sampler Uniform '%s' at index [%i] exists", name, index);
return TextureSampler();
}
TextureSampler Material::get_sampler(int slot, int index) const
{
BLAH_ASSERT(m_shader, "Material Shader is invalid");
int s = 0;
int offset = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type != UniformType::Sampler2D)
continue;
if (s == slot)
{
if (index > uniform.array_length)
break;
return m_samplers[offset + index];
}
offset += uniform.array_length;
if (offset + index >= m_samplers.size())
break;
s++;
}
Log::warn("No Sampler Uniform ['%i'] at index [%i] exists", slot, index);
return TextureSampler();
}
void Material::set_value(const char* name, const float* value, int64_t length)
{
BLAH_ASSERT(m_shader, "Material Shader is invalid");
BLAH_ASSERT(length >= 0, "Length must be >= 0");
int index = 0;
int offset = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type == UniformType::Texture2D ||
uniform.type == UniformType::Sampler2D ||
uniform.type == UniformType::None)
continue;
if (strcmp(uniform.name, name) == 0)
{
auto max = calc_uniform_size(uniform);
if (length > max)
{
Log::warn("Exceeding length of Uniform '%s' (%i / %i)", name, length, max);
length = max;
}
memcpy(m_data.begin() + offset, value, sizeof(float) * length);
return;
}
offset += calc_uniform_size(uniform);
index++;
}
Log::warn("No Uniform '%s' exists", name);
}
const float* Material::get_value(const char* name, int64_t* length) const
{
BLAH_ASSERT(m_shader, "Material Shader is invalid");
int index = 0;
int offset = 0;
for (auto& uniform : m_shader->uniforms())
{
if (uniform.type == UniformType::Texture2D ||
uniform.type == UniformType::Sampler2D ||
uniform.type == UniformType::None)
continue;
if (strcmp(uniform.name, name) == 0)
{
if (length != nullptr)
*length = calc_uniform_size(uniform);
return m_data.begin() + offset;
}
index++;
offset += calc_uniform_size(uniform);
}
*length = 0;
return nullptr;
Log::warn("No Uniform '%s' exists", name);
}
const Vector<TextureRef>& Material::textures() const
{
return m_textures;
}
const Vector<TextureSampler>& Material::samplers() const
{
return m_samplers;
}
const float* Material::data() const
{
return m_data.begin();
}

40
src/graphics/mesh.cpp Normal file
View File

@ -0,0 +1,40 @@
#include <blah/graphics/mesh.h>
#include "../internal/graphics_backend.h"
using namespace Blah;
MeshRef Mesh::create()
{
return GraphicsBackend::create_mesh();
}
VertexFormat::VertexFormat(std::initializer_list<VertexAttribute> attributes, int stride)
{
for (auto& it : attributes)
this->attributes.push_back(it);
if (stride <= 0)
{
stride = 0;
for (auto& it : attributes)
{
switch (it.type)
{
case VertexType::Float: stride += 4; break;
case VertexType::Float2: stride += 8; break;
case VertexType::Float3: stride += 12; break;
case VertexType::Float4: stride += 16; break;
case VertexType::Byte4: stride += 4; break;
case VertexType::UByte4: stride += 4; break;
case VertexType::Short2: stride += 4; break;
case VertexType::UShort2: stride += 4; break;
case VertexType::Short4: stride += 8; break;
case VertexType::UShort4: stride += 8; break;
}
}
}
this->stride = stride;
}

View File

@ -0,0 +1,94 @@
#include <blah/graphics/renderpass.h>
#include <blah/core/log.h>
#include "../internal/graphics_backend.h"
using namespace Blah;
RenderPass::RenderPass()
{
blend = BlendMode::Normal;
target = App::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 RenderPass::perform()
{
BLAH_ASSERT(material, "Trying to draw with an invalid Material");
BLAH_ASSERT(material->shader(), "Trying to draw with an invalid Shader");
BLAH_ASSERT(mesh, "Trying to draw with an invalid Mesh");
// copy call
RenderPass pass = *this;
// Validate Backbuffer
if (!pass.target)
{
pass.target = App::backbuffer;
Log::warn("Trying to draw with an invalid Target; falling back to Back Buffer");
}
// Validate Index Count
int64_t index_count = pass.mesh->index_count();
if (pass.index_start + pass.index_count > index_count)
{
Log::warn(
"Trying to draw more indices than exist in the index buffer (%i-%i / %i); trimming extra indices",
pass.index_start,
pass.index_start + pass.index_count,
index_count);
if (pass.index_start > pass.index_count)
return;
pass.index_count = pass.index_count - pass.index_start;
}
// Validate Instance Count
int64_t instance_count = pass.mesh->instance_count();
if (pass.instance_count > instance_count)
{
Log::warn(
"Trying to draw more instances than exist in the index buffer (%i / %i); trimming extra instances",
pass.instance_count,
instance_count);
pass.instance_count = instance_count;
}
// get the total drawable size
Vec2 draw_size;
if (!pass.target)
draw_size = Vec2(App::draw_width(), App::draw_height());
else
draw_size = Vec2(pass.target->width(), pass.target->height());
// Validate Viewport
if (!pass.has_viewport)
{
pass.viewport.x = 0;
pass.viewport.y = 0;
pass.viewport.w = draw_size.x;
pass.viewport.h = draw_size.y;
}
else
{
pass.viewport = pass.viewport.overlap_rect(Rect(0, 0, draw_size.x, draw_size.y));
}
// Validate Scissor
if (pass.has_scissor)
pass.scissor = pass.scissor.overlap_rect(Rect(0, 0, draw_size.x, draw_size.y));
// perform render
GraphicsBackend::render(pass);
}

39
src/graphics/shader.cpp Normal file
View File

@ -0,0 +1,39 @@
#include <blah/graphics/shader.h>
#include <blah/core/app.h>
#include "../internal/graphics_backend.h"
using namespace Blah;
ShaderRef Shader::create(const ShaderData& data)
{
BLAH_ASSERT(data.vertex.length() > 0, "Must provide a Vertex Shader");
BLAH_ASSERT(data.fragment.length() > 0, "Must provide a Fragment Shader");
BLAH_ASSERT(data.hlsl_attributes.size() > 0 || App::renderer() != Renderer::D3D11, "D3D11 Shaders must have hlsl_attributes assigned");
auto shader = GraphicsBackend::create_shader(&data);
// validate the shader
if (shader)
{
auto& uniforms = shader->uniforms();
// make sure its uniforms are valid
for (auto& it : uniforms)
if (it.type == UniformType::None)
{
BLAH_ERROR_FMT("Uniform '%s' has an invalid type!\n\tOnly Float/Float2/Float3/Float4/Mat3x2/Mat4x4/Texture are allowed!", it.name.cstr());
return ShaderRef();
}
// make sure uniform names don't overlap
for (int i = 0; i < uniforms.size(); i ++)
for (int j = i + 1; j < uniforms.size(); j ++)
if (uniforms[i].name == uniforms[j].name)
{
BLAH_ERROR_FMT("Shader Uniform names '%s' overlap! All Names must be unique.", uniforms[0].name.cstr());
return ShaderRef();
}
}
return shader;
}

61
src/graphics/texture.cpp Normal file
View File

@ -0,0 +1,61 @@
#include <blah/graphics/texture.h>
#include <blah/images/image.h>
#include <blah/streams/stream.h>
#include <blah/core/log.h>
#include "../internal/graphics_backend.h"
using namespace Blah;
TextureRef Texture::create(const Image& image)
{
auto tex = create(image.width, image.height, TextureFormat::RGBA);
if (tex)
tex->set_data((unsigned char*)image.pixels);
return tex;
}
TextureRef Texture::create(int width, int height, unsigned char* rgba)
{
auto tex = create(width, height, TextureFormat::RGBA);
if (tex)
tex->set_data(rgba);
return tex;
}
TextureRef Texture::create(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");
return GraphicsBackend::create_texture(width, height, format);
}
TextureRef Texture::create(Stream& stream)
{
Image img = Image(stream);
if (img.pixels && img.width > 0 && img.height > 0)
{
auto tex = create(img.width, img.height, TextureFormat::RGBA);
if (tex)
tex->set_data((unsigned char*)img.pixels);
return tex;
}
return TextureRef();
}
TextureRef Texture::create(const char* file)
{
Image img = Image(file);
if (img.pixels)
{
auto tex = create(img.width, img.height, TextureFormat::RGBA);
if (tex)
tex->set_data((unsigned char*)img.pixels);
return tex;
}
return TextureRef();
}

471
src/images/aseprite.cpp Normal file
View File

@ -0,0 +1,471 @@
#include <blah/images/aseprite.h>
#include <blah/streams/filestream.h>
#include <blah/core/filesystem.h>
#include <blah/core/log.h>
#define STBI_NO_STDIO
#define STBI_ONLY_ZLIB
#include "../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(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=(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::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 frame_count = 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
frame_count = 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.resize(frame_count);
// frames
for (int i = 0; i < frame_count; 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: parse_layer(stream, i); break;
case Chunks::Cel: parse_cel(stream, i, chunkEnd); break;
case Chunks::Palette: parse_palette(stream, i); break;
case Chunks::UserData: parse_user_data(stream, i); break;
case Chunks::FrameTags: parse_tag(stream, i); break;
case Chunks::Slice: parse_slice(stream, i); break;
default: break;
}
stream.seek(chunkEnd);
}
stream.seek(frameEnd);
}
}
void Aseprite::parse_layer(Stream& stream, int frame)
{
layers.emplace_back();
auto& layer = layers.back();
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 = "";
m_last_userdata = &(layer.userdata);
}
void Aseprite::parse_cel(Stream& stream, int frameIndex, size_t maxPosition)
{
Frame& frame = frames[frameIndex];
frame.cels.emplace_back();
auto& cel = frame.cels.back();
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;
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;
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);
}
// draw to frame if visible
if ((int)layers[cel.layer_index].flag & (int)LayerFlags::Visible)
{
render_cel(&cel, &frame);
}
cel.userdata.color = 0xffffff;
cel.userdata.text = "";
m_last_userdata = &(cel.userdata);
}
void Aseprite::parse_palette(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.resize(palette.size() + (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::parse_user_data(Stream& stream, int frame)
{
if (m_last_userdata != nullptr)
{
auto flags = stream.read<uint32_t>(Endian::Little);
// has text
if (flags & (1 << 0))
{
m_last_userdata->text.set_length(stream.read<uint16_t>(Endian::Little));
stream.read(m_last_userdata->text.cstr(), m_last_userdata->text.length());
}
// has color
if (flags & (1 << 1))
m_last_userdata->color = stream.read<Color>(Endian::Little);
}
}
void Aseprite::parse_tag(Stream& stream, int frame)
{
auto count = stream.read<uint16_t>(Endian::Little);
stream.seek(stream.position() + 8);
for (int t = 0; t < count; t++)
{
Tag tag;
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());
tags.push_back(tag);
}
}
void Aseprite::parse_slice(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++)
{
slices.emplace_back();
auto& slice = slices.back();
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 = "";
m_last_userdata = &(slice.userdata);
}
}
void Aseprite::render_cel(Cel* cel, Frame* frame)
{
Layer& layer = layers[cel->layer_index];
while (cel->linked_frame_index >= 0)
{
auto& frame = frames[cel->linked_frame_index];
for (auto& it : frame.cels)
if (it.layer_index == cel->layer_index)
{
cel = &it;
break;
}
}
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");
}
}

245
src/images/font.cpp Normal file
View File

@ -0,0 +1,245 @@
#include <blah/images/font.h>
#include <blah/streams/filestream.h>
#include <blah/math/calc.h>
#include <blah/core/log.h>
using namespace Blah;
#define STBTT_STATIC
#define STB_TRUETYPE_IMPLEMENTATION
#include "../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()
{
m_font = nullptr;
m_data = nullptr;
m_ascent = 0;
m_descent = 0;
m_line_gap = 0;
m_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
{
m_font = src.m_font;
m_data = src.m_data;
m_family_name = src.m_family_name;
m_style_name = src.m_style_name;
m_ascent = src.m_ascent;
m_descent = src.m_descent;
m_line_gap = src.m_line_gap;
m_valid = src.m_valid;
src.m_family_name.clear();
src.m_style_name.clear();
src.m_valid = false;
src.m_font = nullptr;
src.m_data = nullptr;
}
Font& Font::operator=(Font&& src) noexcept
{
m_font = src.m_font;
m_data = src.m_data;
m_family_name = src.m_family_name;
m_style_name = src.m_style_name;
m_ascent = src.m_ascent;
m_descent = src.m_descent;
m_line_gap = src.m_line_gap;
m_valid = src.m_valid;
src.m_family_name.clear();
src.m_style_name.clear();
src.m_valid = false;
src.m_font = nullptr;
src.m_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();
m_data = new unsigned char[size];
stream.read(m_data, size);
// init font
m_font = new stbtt_fontinfo();
auto fn = (stbtt_fontinfo*)m_font;
stbtt_InitFont(fn, m_data, 0);
m_family_name = GetName(fn, 1);
m_style_name = GetName(fn, 2);
// properties
stbtt_GetFontVMetrics(fn, &m_ascent, &m_descent, &m_line_gap);
m_valid = true;
}
void Font::dispose()
{
delete (stbtt_fontinfo*)m_font;
delete[] m_data;
m_font = nullptr;
m_data = nullptr;
m_family_name.dispose();
m_style_name.dispose();
}
const char* Font::family_name() const
{
return m_family_name.cstr();
}
const char* Font::style_name() const
{
return m_style_name.cstr();
}
int Font::ascent() const
{
return m_ascent;
}
int Font::descent() const
{
return m_descent;
}
int Font::line_gap() const
{
return m_line_gap;
}
int Font::height() const
{
return m_ascent - m_descent;
}
int Font::line_height() const
{
return m_ascent - m_descent + m_line_gap;
}
int Font::get_glyph(Codepoint codepoint) const
{
if (!m_font)
return 0;
return stbtt_FindGlyphIndex((stbtt_fontinfo*)m_font, codepoint);
}
float Font::get_scale(float size) const
{
if (!m_font)
return 0;
return stbtt_ScaleForMappingEmToPixels((stbtt_fontinfo*)m_font, size);
}
float Font::get_kerning(int glyph1, int glyph2, float scale) const
{
if (!m_font)
return 0;
return stbtt_GetGlyphKernAdvance((stbtt_fontinfo*)m_font, glyph1, glyph2) * scale;
}
Font::Char Font::get_character(int glyph, float scale) const
{
Char ch;
if (!m_font)
return ch;
int advance, offsetX, x0, y0, x1, y1;
stbtt_GetGlyphHMetrics((stbtt_fontinfo*)m_font, glyph, &advance, &offsetX);
stbtt_GetGlyphBitmapBox((stbtt_fontinfo*)m_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.offset_x = offsetX * scale;
ch.offset_y = (float)y0;
ch.scale = scale;
ch.has_glyph = (w > 0 && h > 0 && stbtt_IsGlyphEmpty((stbtt_fontinfo*)m_font, glyph) == 0);
return ch;
}
bool Font::get_image(const Font::Char& ch, Color* pixels) const
{
if (ch.has_glyph)
{
// 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*)m_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::is_valid() const
{
return m_valid;
}

301
src/images/image.cpp Normal file
View File

@ -0,0 +1,301 @@
#include <blah/images/image.h>
#include <blah/streams/stream.h>
#include <blah/streams/filestream.h>
#include <blah/core/log.h>
using namespace Blah;
#define STB_IMAGE_IMPLEMENTATION
#define STBI_ONLY_JPEG
#define STBI_ONLY_PNG
#define STBI_ONLY_BMP
#include "../third_party/stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "../third_party/stb_image_write.h"
namespace
{
int Blah_STBI_Read(void* user, char* data, int size)
{
int64_t read = ((Stream*)user)->read(data, size);
return (int)read;
}
void Blah_STBI_Skip(void* user, int n)
{
((Stream*)user)->seek(((Stream*)user)->position() + n);
}
int Blah_STBI_Eof(void* user)
{
int64_t position = ((Stream*)user)->position();
int64_t length = ((Stream*)user)->length();
if (position >= length)
return 1;
return 0;
}
void Blah_STBI_Write(void* context, void* data, int size)
{
((Stream*)context)->write((char*)data, size);
}
}
Image::Image()
{
width = height = 0;
pixels = nullptr;
m_stbi_ownership = false;
}
Image::Image(Stream& stream)
{
width = height = 0;
pixels = nullptr;
m_stbi_ownership = false;
from_stream(stream);
}
Image::Image(const char* file)
{
width = height = 0;
pixels = nullptr;
m_stbi_ownership = false;
FileStream fs(file, FileMode::Read);
if (fs.is_readable())
from_stream(fs);
}
Image::Image(int w, int h)
{
BLAH_ASSERT(w >= 0 && h >= 0, "Image width and height must be larger than 0");
width = w;
height = h;
pixels = new Color[width * height];
m_stbi_ownership = false;
memset(pixels, 0, (size_t)width * (size_t)height * sizeof(Color));
}
Image::Image(const Image& src)
{
width = src.width;
height = src.height;
m_stbi_ownership = src.m_stbi_ownership;
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;
m_stbi_ownership = src.m_stbi_ownership;
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;
m_stbi_ownership = src.m_stbi_ownership;
src.width = src.height = 0;
src.pixels = nullptr;
src.m_stbi_ownership = false;
}
Image& Image::operator=(Image&& src) noexcept
{
width = src.width;
height = src.height;
pixels = src.pixels;
m_stbi_ownership = src.m_stbi_ownership;
src.width = src.height = 0;
src.pixels = nullptr;
src.m_stbi_ownership = false;
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 = Blah_STBI_Eof;
callbacks.read = Blah_STBI_Read;
callbacks.skip = Blah_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;
}
m_stbi_ownership = true;
pixels = (Color*)data;
width = x;
height = y;
}
void Image::dispose()
{
if (m_stbi_ownership)
stbi_image_free(pixels);
else
delete[] pixels;
pixels = nullptr;
width = height = 0;
m_stbi_ownership = false;
}
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(Blah_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(Blah_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;
}

334
src/images/packer.cpp Normal file
View File

@ -0,0 +1,334 @@
#include <blah/images/packer.h>
#include <blah/core/log.h>
#include <algorithm>
#include <cstring>
using namespace Blah;
Packer::Packer()
: max_size(8192), power_of_two(true), spacing(1), padding(1), m_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), m_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;
m_dirty = src.m_dirty;
pages = std::move(src.pages);
entries = std::move(src.entries);
m_buffer = std::move(src.m_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;
m_dirty = src.m_dirty;
pages = std::move(src.pages);
entries = std::move(src.entries);
m_buffer = std::move(src.m_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)
{
m_dirty = true;
Entry entry(id, RectI(0, 0, w, h));
// 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.memory_index = m_buffer.position();
// copy pixels over
if (entry.packed.w == w && entry.packed.h == h)
{
m_buffer.write((char*)pixels, sizeof(Color) * w * h);
}
else
{
for (int i = 0; i < entry.packed.h; i++)
m_buffer.write((char*)(pixels + left + (top + i) * entry.frame.w), sizeof(Color) * entry.packed.w);
}
}
entries.push_back(entry);
}
void Packer::pack()
{
if (!m_dirty)
return;
m_dirty = false;
pages.clear();
// only if we have stuff to pack
auto count = entries.size();
if (count > 0)
{
// get all the sources sorted largest -> smallest
Vector<Entry*> sources;
{
sources.resize(count);
int index = 0;
for (int i = 0; i < entries.size(); 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
Vector<Node> nodes;
nodes.resize(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.emplace_back(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*)(m_buffer.data() + sources[i]->memory_index);
// 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();
m_dirty = false;
}
void Packer::dispose()
{
pages.clear();
entries.clear();
m_buffer.close();
max_size = 0;
power_of_two = 0;
spacing = 0;
m_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;
}

403
src/input/input.cpp Normal file
View File

@ -0,0 +1,403 @@
#include <blah/input/input.h>
#include <blah/core/app.h>
#include <blah/core/time.h>
#include <blah/core/log.h>
#include <blah/math/point.h>
#include "../internal/input_backend.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 InputBackend::init()
{
g_empty_controller.name = "Disconnected";
for (int i = 0; i < Blah::Input::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 InputBackend::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::Input::max_keyboard_keys; i++)
{
g_next_state.keyboard.pressed[i] = false;
g_next_state.keyboard.released[i] = false;
}
for (int i = 0; i < Blah::Input::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::Input::max_text_input; i++)
g_next_state.keyboard.text[i] = 0;
for (int i = 0; i < Blah::Input::max_controllers; i++)
{
ControllerState* controller = &(g_next_state.controllers[i]);
if (!controller->is_connected)
controller->name = nullptr;
for (int j = 0; j < Blah::Input::max_controller_buttons; j++)
{
controller->pressed[j] = false;
controller->released[j] = false;
}
}
}
}
void InputBackend::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 InputBackend::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 InputBackend::on_mouse_down(MouseButton button)
{
int i = (int)button;
if (i >= 0 && i < Blah::Input::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 InputBackend::on_mouse_up(MouseButton button)
{
int i = (int)button;
if (i >= 0 && i < Blah::Input::max_mouse_buttons)
{
g_next_state.mouse.down[i] = false;
g_next_state.mouse.released[i] = true;
}
}
void InputBackend::on_key_down(Key key)
{
int i = (int)key;
if (i >= 0 && i < Blah::Input::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 InputBackend::on_mouse_wheel(Point wheel)
{
g_next_state.mouse.wheel = wheel;
}
void InputBackend::on_key_up(Key key)
{
int i = (int)key;
if (i >= 0 && i < Blah::Input::max_keyboard_keys)
{
g_next_state.keyboard.down[i] = false;
g_next_state.keyboard.released[i] = true;
}
}
void InputBackend::on_text_utf8(const char* text)
{
strncat(g_next_state.keyboard.text, text, Blah::Input::max_text_input);
}
void InputBackend::on_controller_connect(int index, const char* name, int is_gamepad, int button_count, int axis_count, uint16_t vendor, uint16_t product, uint16_t version)
{
if (index < Blah::Input::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;
controller->vendor = vendor;
controller->product = product;
controller->version = version;
}
}
void InputBackend::on_controller_disconnect(int index)
{
if (index < Blah::Input::max_controllers)
g_next_state.controllers[index] = g_empty_controller;
}
void InputBackend::on_button_down(int index, int button)
{
if (index < Blah::Input::max_controllers &&
button < Blah::Input::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 InputBackend::on_button_up(int index, int button)
{
if (index < Blah::Input::max_controllers &&
button < Blah::Input::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 InputBackend::on_axis_move(int index, int axis, float value)
{
if (index < Blah::Input::max_controllers &&
axis < Blah::Input::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()
{
return g_curr_state.mouse.position;
}
Vec2 Input::mouse_draw()
{
return Vec2(g_curr_state.mouse.draw_position);
}
Vec2 Input::mouse_screen()
{
return Vec2(g_curr_state.mouse.screen_position);
}
bool Input::pressed(MouseButton button)
{
int i = (int)button;
return i >= 0 && i < Blah::Input::max_mouse_buttons&& g_curr_state.mouse.pressed[i];
}
bool Input::down(MouseButton button)
{
int i = (int)button;
return i >= 0 && i < Blah::Input::max_mouse_buttons&& g_curr_state.mouse.down[i];
}
bool Input::released(MouseButton button)
{
int i = (int)button;
return i >= 0 && i < Blah::Input::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::Input::max_keyboard_keys&& g_curr_state.keyboard.pressed[i];
}
bool Input::down(Key key)
{
int i = (int)key;
return i > 0 && i < Blah::Input::max_keyboard_keys&& g_curr_state.keyboard.down[i];
}
bool Input::released(Key key)
{
int i = (int)key;
return i > 0 && i < Blah::Input::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::Input::max_controllers)
{
Log::warn("Trying to access a out-of-range controller at %i", 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::Input::max_controllers && i >= 0 && i < Blah::Input::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::Input::max_controllers && i >= 0 && i < Blah::Input::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::Input::max_controllers && i >= 0 && i < Blah::Input::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::Input::max_controllers && i >= 0 && i < Blah::Input::max_controller_axis)
return g_curr_state.controllers[controllerIndex].axis[i];
return 0;
}
int Input::axis_check(int fallback, 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 fallback;
else if (pos)
return 1;
else if (neg)
return -1;
else
return 0;
}
}
int Input::axis_check(int fallback, 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 fallback;
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";
}
const char* Input::name_of(Button button)
{
switch (button)
{
#define DEFINE_BTN(name, value) case Button::name: return #name;
BLAH_BUTTON_DEFINITIONS
#undef DEFINE_BTN
}
return "Unknown";
}

166
src/input/virtual_axis.cpp Normal file
View File

@ -0,0 +1,166 @@
#include <blah/input/virtual_axis.h>
#include <blah/core/time.h>
#include <blah/core/log.h>
using namespace Blah;
VirtualAxis& VirtualAxis::add_keys(Key negative, Key positive)
{
if (m_axes_len >= Input::max_virtual_nodes)
BLAH_ERROR("VirtualAxis Keys out of bounds!");
else
{
m_keys[m_keys_len].init(negative, positive);
m_keys_len++;
}
return *this;
}
VirtualAxis& VirtualAxis::add_buttons(int gamepad_id, Button negative, Button positive)
{
if (m_axes_len >= Input::max_virtual_nodes)
BLAH_ERROR("VirtualAxis Buttons out of bounds!");
else
{
m_buttons[m_buttons_len].init(gamepad_id, negative, positive);
m_buttons_len++;
}
return *this;
}
VirtualAxis& VirtualAxis::add_axis(int gamepad_id, Axis axis, float deadzone)
{
if (m_axes_len >= Input::max_virtual_nodes)
BLAH_ERROR("VirtualAxis Axes out of bounds!");
else
{
m_axes[m_axes_len].init(gamepad_id, axis, deadzone);
m_axes_len++;
}
return *this;
}
VirtualAxis& VirtualAxis::repeat(float m_repeat_delay, float m_repeat_interval)
{
this->m_repeat_delay = m_repeat_delay;
this->m_repeat_interval = m_repeat_interval;
return *this;
}
VirtualAxis& VirtualAxis::press_buffer(float duration)
{
this->m_press_buffer = duration;
return *this;
}
VirtualAxis& VirtualAxis::release_buffer(float duration)
{
this->m_release_buffer = duration;
return *this;
}
void VirtualAxis::update()
{
m_last_value = m_value;
m_value = 0;
for (int i = 0; i < m_keys_len; i++)
{
m_keys[i].update();
if (m_value == 0)
m_value = (float)m_keys[i].value;
}
for (int i = 0; i < m_buttons_len; i++)
{
m_buttons[i].update();
if (m_value == 0)
m_value = (float)m_buttons[i].value;
}
for (int i = 0; i < m_axes_len; i++)
{
m_axes[i].update();
if (m_value == 0)
m_value = m_axes[i].value;
}
//Valuei
m_last_value_i = m_value_i;
if (m_value > 0)
m_value_i = 1;
else if (m_value < 0)
m_value_i = -1;
else
m_value_i = 0;
//pressed?
m_pressed = false;
if (m_value_i != 0 && m_last_value_i != m_value_i)
{
m_pressed = true;
m_last_press_time = m_repeat_press_time = Time::elapsed;
}
else if (m_value_i == m_last_value_i && m_value_i != 0)
{
if (Time::elapsed - m_last_press_time <= m_press_buffer)
m_pressed = true;
else if (m_repeat_interval > 0 && Time::elapsed >= m_repeat_press_time + m_repeat_delay)
{
int prev = (int)((Time::previous_elapsed - m_repeat_press_time - m_repeat_delay) / m_repeat_interval);
int cur = (int)((Time::elapsed - m_repeat_press_time - m_repeat_delay) / m_repeat_interval);
m_pressed = prev < cur;
}
}
//released?
if (m_last_value_i != 0 && m_value_i != m_last_value_i)
{
m_released = true;
m_last_release_time = Time::elapsed;
}
else if (Time::elapsed - m_last_release_time <= m_release_buffer)
m_released = true;
else
m_released = false;
}
void VirtualAxis::KeysNode::init(Key negative, Key positive)
{
this->negative = negative;
this->positive = positive;
}
void VirtualAxis::KeysNode::update()
{
value = Input::axis_check(value, negative, positive);
}
void VirtualAxis::ButtonsNode::init(int gamepad_id, Button negative, Button positive)
{
this->gamepad_id = gamepad_id;
this->negative = negative;
this->positive = positive;
}
void VirtualAxis::ButtonsNode::update()
{
value = Input::axis_check(value, gamepad_id, negative, positive);
}
void VirtualAxis::AxisNode::init(int gamepad_id, Axis axis, float deadzone)
{
this->gamepad_id = gamepad_id;
this->axis = axis;
this->deadzone = deadzone;
}
void VirtualAxis::AxisNode::update()
{
value = Input::axis_check(gamepad_id, axis);
if (value < deadzone && value > -deadzone)
value = 0;
}

View File

@ -0,0 +1,174 @@
#include <blah/input/virtual_button.h>
#include <blah/core/time.h>
#include <blah/core/log.h>
using namespace Blah;
VirtualButton& VirtualButton::add_key(Key key)
{
if (m_keys_len >= Input::max_virtual_nodes)
BLAH_ERROR("VirtualButton Keys out of bounds!");
else
{
m_keys[m_keys_len].init(key);
m_keys_len++;
}
return *this;
}
VirtualButton& VirtualButton::add_button(int gamepad_id, Button button)
{
if (m_buttons_len >= Input::max_virtual_nodes)
BLAH_ERROR("VirtualButton Buttons out of bounds!");
else
{
m_buttons[m_buttons_len].init(gamepad_id, button);
m_buttons_len++;
}
return *this;
}
VirtualButton& VirtualButton::add_axis(int gamepad_id, Axis axis, float threshold, bool greater_than)
{
if (m_axes_len >= Input::max_virtual_nodes)
BLAH_ERROR("VirtualButton Axes out of bounds!");
else
{
m_axes[m_axes_len].init(gamepad_id, axis, threshold, greater_than);
m_axes_len++;
}
return *this;
}
VirtualButton& VirtualButton::repeat(float m_repeat_delay, float m_repeat_interval)
{
this->m_repeat_delay = m_repeat_delay;
this->m_repeat_interval = m_repeat_interval;
return *this;
}
VirtualButton& VirtualButton::press_buffer(float duration)
{
this->m_press_buffer = duration;
return *this;
}
VirtualButton& VirtualButton::release_buffer(float duration)
{
this->m_release_buffer = duration;
return *this;
}
void VirtualButton::update()
{
m_down = false;
m_pressed = false;
m_released = false;
//Keys
for (int i = 0; i < m_keys_len; i++)
{
m_keys[i].update();
m_down = m_down || m_keys[i].down;
m_pressed = m_pressed || m_keys[i].pressed;
m_released = m_released || m_keys[i].released;
}
//Buttons
for (int i = 0; i < m_buttons_len; i++)
{
m_buttons[i].update();
m_down = m_down || m_buttons[i].down;
m_pressed = m_pressed || m_buttons[i].pressed;
m_released = m_released || m_buttons[i].released;
}
//Axes
for (int i = 0; i < m_axes_len; i++)
{
m_axes[i].update();
m_down = m_down || m_axes[i].down;
m_pressed = m_pressed || m_axes[i].pressed;
m_released = m_released || m_axes[i].released;
}
//pressed?
if (m_pressed)
{
m_repeat_press_time = m_last_press_time = Time::elapsed;
}
else if (Time::elapsed - m_last_press_time <= m_press_buffer)
{
m_pressed = true;
}
else if (m_down && m_repeat_interval > 0 && Time::elapsed >= m_repeat_press_time + m_repeat_delay)
{
int prev = (int)((Time::previous_elapsed - m_repeat_press_time - m_repeat_delay) / m_repeat_interval);
int cur = (int)((Time::elapsed - m_repeat_press_time - m_repeat_delay) / m_repeat_interval);
m_pressed = prev < cur;
}
//released?
if (m_released)
m_last_release_time = Time::elapsed;
else
m_released = Time::elapsed - m_last_release_time <= m_release_buffer;
}
void VirtualButton::KeyNode::init(Key key)
{
this->key = key;
}
void VirtualButton::KeyNode::update()
{
down = Input::down(key);
pressed = Input::pressed(key);
released = Input::released(key);
}
void VirtualButton::ButtonNode::init(int gamepad_id, Button button)
{
this->gamepad_id = gamepad_id;
this->button = button;
}
void VirtualButton::ButtonNode::update()
{
down = Input::down(gamepad_id, button);
pressed = Input::pressed(gamepad_id, button);
released = Input::released(gamepad_id, button);
}
void VirtualButton::AxisNode::init(int gamepad_id, Axis axis, float threshold, bool greater_than)
{
this->gamepad_id = gamepad_id;
this->axis = axis;
this->threshold = threshold;
this->greater_than = greater_than;
}
void VirtualButton::AxisNode::update()
{
float curr = Input::state()->controllers[gamepad_id].axis[(int)axis];
float prev = Input::last_state()->controllers[gamepad_id].axis[(int)axis];
if (greater_than)
{
down = curr >= threshold;
pressed = down && prev < threshold;
released = !down && prev >= threshold;
}
else
{
down = curr <= threshold;
pressed = down && prev > threshold;
released = !down && prev <= threshold;
}
}

191
src/input/virtual_stick.cpp Normal file
View File

@ -0,0 +1,191 @@
#include <blah/input/virtual_stick.h>
#include <blah/core/time.h>
#include <blah/core/log.h>
using namespace Blah;
VirtualStick::VirtualStick()
{
this->m_i_deadzone = 0;
}
VirtualStick::VirtualStick(float iDeadzone)
{
this->m_i_deadzone = iDeadzone;
}
VirtualStick& VirtualStick::add_keys(Key left, Key right, Key up, Key down)
{
if (m_keys_len >= Input::max_virtual_nodes)
BLAH_ERROR("VirtualStick Keys out of bounds!");
else
{
m_keys[m_keys_len].init(left, right, up, down);
m_keys_len++;
}
return *this;
}
VirtualStick& VirtualStick::add_buttons(int gamepad_id, Button left, Button right, Button up, Button down)
{
if (m_buttons_len >= Input::max_virtual_nodes)
BLAH_ERROR("VirtualStick Buttons out of bounds!");
else
{
m_buttons[m_buttons_len].init(gamepad_id, left, right, up, down);
m_buttons_len++;
}
return *this;
}
VirtualStick& VirtualStick::add_axes(int gamepad_id, Axis horizontal, Axis vertical, float deadzone)
{
if (m_axes_len >= Input::max_virtual_nodes)
BLAH_ERROR("VirtualStick Axes out of bounds!");
else
{
m_axes[m_axes_len].init(gamepad_id, horizontal, vertical, deadzone);
m_axes_len++;
}
return *this;
}
VirtualStick& VirtualStick::repeat(float repeat_delay, float repeat_interval)
{
this->m_repeat_delay = repeat_delay;
this->m_repeat_interval = repeat_interval;
return *this;
}
VirtualStick& VirtualStick::press_buffer(float duration)
{
m_press_buffer = duration;
return *this;
}
VirtualStick& VirtualStick::release_buffer(float duration)
{
m_release_buffer = duration;
return *this;
}
void VirtualStick::update()
{
m_last_value = m_value;
m_value = Vec2::zero;
for (int i = 0; i < m_keys_len; i++)
{
m_keys[i].update();
if (m_value == Vec2::zero)
m_value = m_keys[i].value;
}
for (int i = 0; i < m_buttons_len; i++)
{
m_buttons[i].update();
if (m_value == Vec2::zero)
m_value = m_buttons[i].value;
}
for (int i = 0; i < m_axes_len; i++)
{
m_axes[i].update();
if (m_value == Vec2::zero)
m_value = m_axes[i].value;
}
//Valuei
m_last_value_i = m_value_i;
if (m_value.x > m_i_deadzone)
m_value_i.x = 1;
else if (m_value.x < -m_i_deadzone)
m_value_i.x = -1;
else
m_value_i.x = 0;
if (m_value.y > m_i_deadzone)
m_value_i.y = 1;
else if (m_value.y < -m_i_deadzone)
m_value_i.y = -1;
else
m_value_i.y = 0;
//pressed?
m_pressed = false;
if (m_value_i != Point::zero && m_last_value_i != m_value_i)
{
m_pressed = true;
m_last_press_time = m_repeat_press_time = Time::elapsed;
}
else if (m_value_i == m_last_value_i && m_value_i != Point::zero)
{
if (Time::elapsed - m_last_press_time <= m_press_buffer)
m_pressed = true;
else if (m_repeat_interval > 0 && Time::elapsed >= m_repeat_press_time + m_repeat_delay)
{
int prev = (int)((Time::previous_elapsed - m_repeat_press_time - m_repeat_delay) / m_repeat_interval);
int cur = (int)((Time::elapsed - m_repeat_press_time - m_repeat_delay) / m_repeat_interval);
m_pressed = prev < cur;
}
}
//released?
if (m_last_value_i != Point::zero && m_value_i != m_last_value_i)
{
m_released = true;
m_last_release_time = Time::elapsed;
}
else if (Time::elapsed - m_last_release_time <= m_release_buffer)
m_released = true;
else
m_released = false;
}
void VirtualStick::KeysNode::init(Key left, Key right, Key up, Key down)
{
this->left = left;
this->right = right;
this->up = up;
this->down = down;
}
void VirtualStick::KeysNode::update()
{
value.x = Input::axis_check(value.x, left, right);
value.y = Input::axis_check(value.y, up, down);
}
void VirtualStick::ButtonsNode::init(int gamepad_id, Button left, Button right, Button up, Button down)
{
this->gamepad_id = gamepad_id;
this->left = left;
this->right = right;
this->up = up;
this->down = down;
}
void VirtualStick::ButtonsNode::update()
{
value.x = Input::axis_check(value.x, gamepad_id, left, right);
value.y = Input::axis_check(value.y, gamepad_id, up, down);
}
void VirtualStick::AxesNode::init(int gamepad_id, Axis horizontal, Axis vertical, float deadzone)
{
this->gamepad_id = gamepad_id;
this->horizontal = horizontal;
this->vertical = vertical;
this->deadzone = deadzone;
}
void VirtualStick::AxesNode::update()
{
value.x = Input::axis_check(gamepad_id, horizontal);
value.y = Input::axis_check(gamepad_id, vertical);
if (value.length() < deadzone)
value = Vec2::zero;
}

View File

@ -0,0 +1,60 @@
#pragma once
#include <blah/core/app.h>
#include <blah/graphics/renderpass.h>
#include <blah/graphics/texture.h>
#include <blah/graphics/framebuffer.h>
#include <blah/graphics/shader.h>
#include <blah/graphics/mesh.h>
#include <blah/graphics/material.h>
#include <blah/math/color.h>
namespace Blah
{
// Graphics backend API used for rendering.
// All rendering ends up going through here.
namespace GraphicsBackend
{
// Initializes the graphics backend
bool init();
// Shuts down the graphics backend
void shutdown();
// Returns info about the renderer
const RendererFeatures& features();
// Returns the renderer type
Renderer renderer();
// Called once per frame
void frame();
// Called before rendering begins
void before_render();
// Called after renderings ends
void after_render();
// Performs a draw call
void render(const RenderPass& pass);
// Clears the backbuffer
void clear_backbuffer(Color color);
// Creates a new Texture.
// if the Texture is invalid, this should return an empty reference.
TextureRef create_texture(int width, int height, TextureFormat format);
// Creates a new FrameBuffer.
// if the FrameBuffer is invalid, this should return an empty reference.
FrameBufferRef create_framebuffer(int width, int height, const TextureFormat* attachments, int attachment_count);
// Creates a new Shader.
// if the Shader is invalid, this should return an empty reference.
ShaderRef create_shader(const ShaderData* data);
// Creates a new Mesh.
// if the Mesh is invalid, this should return an empty reference.
MeshRef create_mesh();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,235 @@
#if !(defined(BLAH_USE_OPENGL) || defined(BLAH_USE_D3D11))
#include "../internal/graphics_backend.h"
#include "../internal/platform_backend.h"
#include <blah/core/log.h>
namespace Blah
{
class Dummy_Texture : public Texture
{
private:
int m_width;
int m_height;
TextureFormat m_format;
bool m_framebuffer;
public:
Dummy_Texture(int width, int height, TextureFormat format, bool framebuffer)
{
m_width = width;
m_height = height;
m_format = format;
m_framebuffer = framebuffer;
}
virtual int width() const override
{
return m_width;
}
virtual int height() const override
{
return m_height;
}
virtual TextureFormat format() const override
{
return m_format;
}
virtual void set_data(unsigned char* data) override
{
}
virtual void get_data(unsigned char* data) override
{
}
virtual bool is_framebuffer() const override
{
return m_framebuffer;
}
};
class Dummy_FrameBuffer : public FrameBuffer
{
private:
Attachments m_attachments;
public:
Dummy_FrameBuffer(int width, int height, const TextureFormat* attachments, int attachmentCount)
{
for (int i = 0; i < attachmentCount; i++)
{
m_attachments.push_back(
TextureRef(new Dummy_Texture(width, height, attachments[i], true))
);
}
}
virtual Attachments& attachments() override
{
return m_attachments;
}
virtual const Attachments& attachments() const override
{
return m_attachments;
}
virtual TextureRef& attachment(int index) override
{
return m_attachments[index];
}
virtual const TextureRef& attachment(int index) const override
{
return m_attachments[index];
}
virtual int width() const override
{
return m_attachments[0]->width();
}
virtual int height() const override
{
return m_attachments[0]->height();
}
virtual void clear(Color color) override
{
}
};
class Dummy_Shader : public Shader
{
private:
Vector<UniformInfo> m_uniforms;
public:
Dummy_Shader(const ShaderData* data)
{
}
virtual Vector<UniformInfo>& uniforms() override
{
return m_uniforms;
}
virtual const Vector<UniformInfo>& uniforms() const override
{
return m_uniforms;
}
};
class Dummy_Mesh : public Mesh
{
private:
int64_t m_index_count = 0;
int64_t m_vertex_count = 0;
int64_t m_instance_count = 0;
public:
Dummy_Mesh()
{
}
virtual void index_data(IndexFormat format, const void* indices, int64_t count) override
{
m_index_count = count;
}
virtual void vertex_data(const VertexFormat& format, const void* vertices, int64_t count) override
{
m_vertex_count = count;
}
virtual void instance_data(const VertexFormat& format, const void* instances, int64_t count) override
{
m_instance_count = count;
}
virtual int64_t index_count() const override
{
return m_index_count;
}
virtual int64_t vertex_count() const override
{
return m_vertex_count;
}
virtual int64_t instance_count() const override
{
return m_instance_count;
}
};
bool GraphicsBackend::init()
{
Log::print("Dummy Renderer");
return true;
}
Renderer GraphicsBackend::renderer()
{
return Renderer::None;
}
void GraphicsBackend::shutdown()
{
}
const RendererFeatures& GraphicsBackend::features()
{
static const RendererFeatures features { false, true, 4096 };
return features;
}
void GraphicsBackend::frame() {}
void GraphicsBackend::before_render() {}
void GraphicsBackend::after_render() {}
TextureRef GraphicsBackend::create_texture(int width, int height, TextureFormat format)
{
return TextureRef(new Dummy_Texture(width, height, format, false));
}
FrameBufferRef GraphicsBackend::create_framebuffer(int width, int height, const TextureFormat* attachments, int attachmentCount)
{
return FrameBufferRef(new Dummy_FrameBuffer(width, height, attachments, attachmentCount));
}
ShaderRef GraphicsBackend::create_shader(const ShaderData* data)
{
return ShaderRef(new Dummy_Shader(data));
}
MeshRef GraphicsBackend::create_mesh()
{
return MeshRef(new Dummy_Mesh());
}
void GraphicsBackend::render(const RenderPass& pass)
{
}
void GraphicsBackend::clear_backbuffer(Color color)
{
}
}
#endif // !(defined(BLAH_USE_OPENGL) || defined(BLAH_USE_D3D11))

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,54 @@
#pragma once
#include <blah/input/input.h>
namespace Blah
{
namespace InputBackend
{
// 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, uint16_t vendor, uint16_t product, uint16_t version);
// Call this when a controller is disconnected
void on_controller_disconnect(int index);
// Call this when a controller button is pressed
void on_button_down(int index, int button);
// Call this when a controller button is released
void on_button_up(int index, int button);
/// Call this when a controller axis is moved
void on_axis_move(int index, int axis, float value);
}
}

View File

@ -0,0 +1,120 @@
#pragma once
#include <inttypes.h>
#include <blah/core/filesystem.h>
#include <blah/containers/vector.h>
namespace Blah
{
struct Config;
enum class FileMode;
namespace PlatformBackend
{
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(Vector<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);
// D3D11 Methods
void* d3d11_get_hwnd();
}
}

View File

@ -0,0 +1,660 @@
#ifdef BLAH_USE_SDL2
#include "../internal/platform_backend.h"
#include "../internal/input_backend.h"
#include "../internal/graphics_backend.h"
#include <blah/input/input.h>
#include <blah/core/app.h>
#include <blah/core/filesystem.h>
#include <blah/core/log.h>
#include <SDL.h>
#include <SDL_vulkan.h>
#include <SDL_syswm.h>
#if _WIN32
// on Windows we're using the C++ <filesystem> API for now
#define WIN32_LEAN_AND_MEAN
#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;
namespace
{
SDL_Window* window = nullptr;
SDL_Joystick* joysticks[Blah::Input::max_controllers];
SDL_GameController* gamepads[Blah::Input::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 PlatformBackend::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;
}
int flags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN | SDL_WINDOW_RESIZABLE;
// enable OpenGL
if (App::renderer() == Renderer::OpenGL)
{
flags |= SDL_WINDOW_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);
}
// enable DirectX
else if (App::renderer() == Renderer::D3D11)
{
}
// 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 PlatformBackend::ready()
{
// enable V-Sync
if (App::renderer() == Renderer::OpenGL)
SDL_GL_SetSwapInterval(1);
}
void PlatformBackend::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 PlatformBackend::time()
{
return (uint64_t)SDL_GetTicks();
}
void PlatformBackend::frame()
{
// update the mouse every frame
{
int winX, winY, x, y;
SDL_GetWindowPosition(window, &winX, &winY);
SDL_GetGlobalMouseState(&x, &y);
InputBackend::on_mouse_move((float)(x - winX), (float)(y - winY));
InputBackend::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;
InputBackend::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;
InputBackend::on_mouse_up(btn);
}
else if (event.type == SDL_MOUSEWHEEL)
{
InputBackend::on_mouse_wheel(Point(event.wheel.x, event.wheel.y));
}
// Keyboard
else if (event.type == SDL_KEYDOWN)
{
if (event.key.repeat == 0)
InputBackend::on_key_down((Key)event.key.keysym.scancode);
}
else if (event.type == SDL_KEYUP)
{
if (event.key.repeat == 0)
InputBackend::on_key_up((Key)event.key.keysym.scancode);
}
else if (event.type == SDL_TEXTINPUT)
{
InputBackend::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);
uint16_t vendor = SDL_JoystickGetVendor(ptr);
uint16_t product = SDL_JoystickGetProduct(ptr);
uint16_t version = SDL_JoystickGetProductVersion(ptr);
InputBackend::on_controller_connect(index, name, 0, button_count, axis_count, vendor, product, version);
}
}
else if (event.type == SDL_JOYDEVICEREMOVED)
{
Sint32 index = event.jdevice.which;
if (SDL_IsGameController(index) == SDL_FALSE)
{
InputBackend::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)
InputBackend::on_button_down(index, event.jbutton.button);
}
else if (event.type == SDL_JOYBUTTONUP)
{
Sint32 index = event.jdevice.which;
if (SDL_IsGameController(index) == SDL_FALSE)
InputBackend::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;
InputBackend::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);
uint16_t vendor = SDL_GameControllerGetVendor(ptr);
uint16_t product = SDL_GameControllerGetProduct(ptr);
uint16_t version = SDL_GameControllerGetProductVersion(ptr);
InputBackend::on_controller_connect(index, name, 1, 15, 6, vendor, product, version);
}
else if (event.type == SDL_CONTROLLERDEVICEREMOVED)
{
Sint32 index = event.cdevice.which;
InputBackend::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!
InputBackend::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!
InputBackend::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;
InputBackend::on_axis_move(index, axis, value);
}
}
}
void PlatformBackend::sleep(int milliseconds)
{
if (milliseconds >= 0)
SDL_Delay((uint32_t)milliseconds);
}
void PlatformBackend::present()
{
if (App::renderer() == Renderer::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* PlatformBackend::get_title()
{
return nullptr;
}
void PlatformBackend::set_title(const char* title)
{
SDL_SetWindowTitle(window, title);
}
void PlatformBackend::get_position(int* x, int* y)
{
SDL_GetWindowPosition(window, x, y);
}
void PlatformBackend::set_position(int x, int y)
{
SDL_SetWindowPosition(window, x, y);
}
void PlatformBackend::set_fullscreen(bool enabled)
{
if (enabled)
SDL_SetWindowFullscreen(window, SDL_WINDOW_FULLSCREEN_DESKTOP);
else
SDL_SetWindowFullscreen(window, 0);
}
void PlatformBackend::get_size(int* width, int* height)
{
SDL_GetWindowSize(window, width, height);
}
void PlatformBackend::set_size(int width, int height)
{
SDL_SetWindowSize(window, width, height);
}
void PlatformBackend::get_draw_size(int* width, int* height)
{
if (App::renderer() == Renderer::OpenGL)
{
SDL_GL_GetDrawableSize(window, width, height);
}
else
{
SDL_GetWindowSize(window, width, height);
}
}
float PlatformBackend::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* PlatformBackend::app_path()
{
if (basePath == nullptr)
basePath = SDL_GetBasePath();
return basePath;
}
const char* PlatformBackend::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 PlatformBackend::file_exists(const char* path)
{
return fs::is_regular_file(path);
}
bool PlatformBackend::file_delete(const char* path)
{
return fs::remove(path);
}
bool PlatformBackend::dir_create(const char* path)
{
std::error_code error;
return fs::create_directories(path, error);
}
bool PlatformBackend::dir_exists(const char* path)
{
return fs::is_directory(path);
}
bool PlatformBackend::dir_delete(const char* path)
{
BLAH_ERROR("not implemented");
return false;
}
void PlatformBackend::dir_enumerate(Vector<FilePath>& list, const char* path, bool recursive)
{
if (fs::is_directory(path))
{
if (recursive)
{
for (auto& p : fs::recursive_directory_iterator(path))
list.emplace_back(p.path().string().c_str());
}
else
{
for (auto& p : fs::directory_iterator(path))
list.emplace_back(p.path().string().c_str());
}
}
}
void PlatformBackend::dir_explore(const char* path)
{
ShellExecute(NULL, "open", path, NULL, NULL, SW_SHOWDEFAULT);
}
// Non-Windows File System Methods
#else
bool PlatformBackend::file_exists(const char* path)
{
struct stat buffer;
return (stat(path, &buffer) == 0) && S_ISREG(buffer.st_mode);
}
bool PlatformBackend::file_delete(const char* path)
{
BLAH_ERROR("not implemented");
return false;
}
bool PlatformBackend::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 PlatformBackend::dir_exists(const char* path)
{
struct stat buffer;
return (stat(path, &buffer) == 0) && S_ISDIR(buffer.st_mode);
}
bool PlatformBackend::dir_delete(const char* path)
{
BLAH_ERROR("not implemented");
return false;
}
void PlatformBackend::dir_enumerate(Vector<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.push_back(subpath);
if (recursive && dp->d_type == DT_DIR)
dir_enumerate(list, subpath + "/", true);
}
closedir(dirp);
}
}
void PlatformBackend::dir_explore(const char* path)
{
BLAH_ERROR("'dir_explore' Not Implemented");
}
#endif
bool PlatformBackend::file_open(const char* path, PlatformBackend::FileHandle* handle, FileMode mode)
{
const char* sdlMode = "rb";
if (mode == FileMode::Write)
sdlMode = "wb";
auto ptr = SDL_RWFromFile(path, sdlMode);
*handle = (PlatformBackend::FileHandle)ptr;
return ptr != nullptr;
}
int64_t PlatformBackend::file_length(PlatformBackend::FileHandle stream)
{
return SDL_RWsize((SDL_RWops*)stream);
}
int64_t PlatformBackend::file_position(PlatformBackend::FileHandle stream)
{
return SDL_RWtell((SDL_RWops*)stream);
}
int64_t PlatformBackend::file_seek(PlatformBackend::FileHandle stream, int64_t seekTo)
{
return SDL_RWseek((SDL_RWops*)stream, seekTo, RW_SEEK_SET);
}
int64_t PlatformBackend::file_read(PlatformBackend::FileHandle stream, void* ptr, int64_t length)
{
return SDL_RWread((SDL_RWops*)stream, ptr, sizeof(char), length);
}
int64_t PlatformBackend::file_write(PlatformBackend::FileHandle stream, const void* ptr, int64_t length)
{
return SDL_RWwrite((SDL_RWops*)stream, ptr, sizeof(char), length);
}
void PlatformBackend::file_close(PlatformBackend::FileHandle stream)
{
if (stream != nullptr)
SDL_RWclose((SDL_RWops*)stream);
}
void* PlatformBackend::gl_get_func(const char* name)
{
return SDL_GL_GetProcAddress(name);
}
void* PlatformBackend::gl_context_create()
{
void* pointer = SDL_GL_CreateContext(window);
if (pointer == nullptr)
Log::error("SDL_GL_CreateContext failed: %s", SDL_GetError());
return pointer;
}
void PlatformBackend::gl_context_make_current(void* context)
{
SDL_GL_MakeCurrent(window, context);
}
void PlatformBackend::gl_context_destroy(void* context)
{
SDL_GL_DeleteContext(context);
}
void* PlatformBackend::d3d11_get_hwnd()
{
SDL_SysWMinfo info;
SDL_VERSION(&info.version);
SDL_GetWindowWMInfo(window, &info);
return info.info.win.window;
}
#endif // BLAH_USE_SDL2

156
src/math/calc.cpp Normal file
View File

@ -0,0 +1,156 @@
#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);
}
float Calc::map(float t, float old_min, float old_max, float new_min, float new_max)
{
return new_min + ((t - old_min) / (old_max - old_min)) * (new_max - new_min);
}
float Calc::clamped_map(float t, float old_min, float old_max, float new_min, float new_max)
{
return map(Calc::clamp(t, old_min, old_max), old_min, old_max, new_min, new_max);
}
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::round(float x)
{
return roundf(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::angle_lerp(float radians_a, float radians_b, float p)
{
const auto shortest_angle = mod(mod(radians_b - radians_a, TAU) + (TAU + PI), TAU) - PI;
return radians_a + mod(shortest_angle * p, TAU);
}
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);
}

11
src/math/circle.cpp Normal file
View File

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

156
src/math/color.cpp Normal file
View File

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

157
src/math/line.cpp Normal file
View File

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

187
src/math/mat3x2.cpp Normal file
View File

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

109
src/math/mat4x4.cpp Normal file
View File

@ -0,0 +1,109 @@
#include <blah/math/mat4x4.h>
using namespace Blah;
Mat4x4::Mat4x4() :
m11(0.0f), m12(0.0f), m13(0.0f), m14(0.0f),
m21(0.0f), m22(0.0f), m23(0.0f), m24(0.0f),
m31(0.0f), m32(0.0f), m33(0.0f), m34(0.0f),
m41(0.0f), m42(0.0f), m43(0.0f), m44(0.0f) {}
Mat4x4::Mat4x4(
float m11, float m12, float m13, float m14,
float m21, float m22, float m23, float m24,
float m31, float m32, float m33, float m34,
float m41, float m42, float m43, float m44) :
m11(m11), m12(m12), m13(m13), m14(m14),
m21(m21), m22(m22), m23(m23), m24(m24),
m31(m31), m32(m32), m33(m33), m34(m34),
m41(m41), m42(m42), m43(m43), m44(m44) {}
const Mat4x4 Mat4x4::identity = Mat4x4(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f);
Mat4x4 Mat4x4::create_ortho(float width, float height, float z_near_plane, float z_far_plane)
{
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 / (z_near_plane - z_far_plane);
result.m31 = result.m32 = result.m34 = 0.0f;
result.m41 = result.m42 = 0.0f;
result.m43 = z_near_plane / (z_near_plane - z_far_plane);
result.m44 = 1.0f;
return result;
}
Mat4x4 Mat4x4::create_ortho_offcenter(float left, float right, float bottom, float top, float z_near_plane, float z_far_plane)
{
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 / (z_near_plane - z_far_plane);
result.m31 = result.m32 = result.m34 = 0.0f;
result.m41 = (left + right) / (left - right);
result.m42 = (top + bottom) / (bottom - top);
result.m43 = z_near_plane / (z_near_plane - z_far_plane);
result.m44 = 1.0f;
return result;
}
Mat4x4 Mat4x4::create_translation(float x, float y, float z)
{
Mat4x4 result = identity;
result.m41 = x;
result.m42 = y;
result.m43 = z;
return result;
}
Mat4x4 Mat4x4::create_scale(float x, float y, float z)
{
Mat4x4 result = identity;
result.m11 = x;
result.m22 = y;
result.m33 = z;
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;
}

51
src/math/point.cpp Normal file
View File

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

21
src/math/quad.cpp Normal file
View File

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

275
src/math/rect.cpp Normal file
View File

@ -0,0 +1,275 @@
#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;
}
float Rect::left() const
{
return x;
}
float Rect::right() const
{
return x + w;
}
float Rect::top() const
{
return y;
}
float Rect::bottom() const
{
return y + h;
}
Vec2 Rect::center() const
{
return Vec2(x + w / 2, y + h / 2);
}
float Rect::center_x() const
{
return x + w / 2;
}
float Rect::center_y() const
{
return y + h / 2;
}
Vec2 Rect::top_left() const
{
return Vec2(x, y);
}
Vec2 Rect::top_right() const
{
return Vec2(x + w, y);
}
Vec2 Rect::bottom_right() const
{
return Vec2(x + w, y + h);
}
Vec2 Rect::bottom_left() const
{
return Vec2(x, y + h);
}
Vec2 Rect::center_left() const
{
return Vec2(x, y + h / 2);
}
Vec2 Rect::center_right() const
{
return Vec2(x + w, y + h / 2);
}
Vec2 Rect::middle_top() const
{
return Vec2(x + w / 2, y);
}
Vec2 Rect::middle_bottom() const
{
return Vec2(x + w / 2, y + h);
}
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());
}
bool Rect::contains(const Point& pt) const
{
return pt.x >= x && pt.x < x + w && pt.y >= y && pt.y < y + h;
}
bool Rect::contains(const Vec2& pt) const
{
return pt.x >= x && pt.x < x + w && pt.y >= y && pt.y < y + h;
}
bool Rect::overlaps(const Rect& rect) const
{
return x + w >= rect.x && y + h >= rect.y && x < rect.x + rect.w && y < rect.y + rect.h;
}
Rect Rect::overlap_rect(const Rect& against) const
{
Rect result(0, 0, 0, 0);
if (x + w >= against.x && x < against.x + against.w)
{
result.x = Calc::max(x, against.x);
result.w = Calc::min(x + w, against.x + against.w) - result.x;
}
if (y + h >= against.y && y < against.y + against.h)
{
result.y = Calc::max(y, against.y);
result.h = Calc::min(y + h, against.y + against.h) - result.y;
}
return result;
}
bool Rect::intersects(const Line& line) const
{
return line.intersects(*this);
}
bool Rect::intersects(const Line& line, Vec2* out_intersection_point) const
{
return line.intersects(*this, out_intersection_point);
}
bool Rect::intersects(const Vec2& line_from, const Vec2& line_to) const
{
return intersects(Line(line_from, line_to));
}
bool Rect::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 Rect::intersection_point(const Line& line) const
{
Vec2 ret;
if (line.intersects(*this, &ret))
return ret;
else
return Vec2::zero;
}
Vec2 Rect::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 Rect::inflate(float amount) const
{
return Rect(x - amount, y - amount, w + amount * 2, h + amount * 2);
}
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;
}

90
src/math/rectI.cpp Normal file
View File

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

26
src/math/stopwatch.cpp Normal file
View File

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

133
src/math/vec2.cpp Normal file
View File

@ -0,0 +1,133 @@
#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);
}
Vec2 Vec2::turn_right() const
{
return Vec2(y, -x);
}
Vec2 Vec2::turn_left() const
{
return Vec2(-y, x);
}
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

View File

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

View File

@ -0,0 +1,97 @@
#include <blah/streams/filestream.h>
#include <blah/core/log.h>
#include "../internal/platform_backend.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 (!PlatformBackend::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)
PlatformBackend::file_close(m_handle);
}
int64_t FileStream::length() const
{
if (m_handle == nullptr)
return 0;
return PlatformBackend::file_length(m_handle);
}
int64_t FileStream::position() const
{
if (m_handle == nullptr)
return 0;
return PlatformBackend::file_position(m_handle);
}
int64_t FileStream::seek(int64_t seek_to)
{
if (m_handle == nullptr)
return 0;
return PlatformBackend::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 PlatformBackend::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 PlatformBackend::file_write(m_handle, ptr, length);
}
void FileStream::close()
{
if (m_handle != nullptr)
PlatformBackend::file_close(m_handle);
m_handle = nullptr;
m_mode = FileMode::None;
}

View File

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

29
src/streams/stream.cpp Normal file
View File

@ -0,0 +1,29 @@
#include <blah/streams/stream.h>
#include <blah/containers/str.h>
#include <string.h>
using namespace Blah;
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;
}

7656
src/third_party/stb_image.h vendored Normal file

File diff suppressed because it is too large Load Diff

1666
src/third_party/stb_image_write.h vendored Normal file

File diff suppressed because it is too large Load Diff

5011
src/third_party/stb_truetype.h vendored Normal file

File diff suppressed because it is too large Load Diff