mirror of
https://github.com/NoelFB/blah.git
synced 2025-06-29 19:25:26 +08:00
restructured project to match a more standard cmake setup
This commit is contained in:
429
src/containers/str.cpp
Normal file
429
src/containers/str.cpp
Normal 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
296
src/core/app.cpp
Normal 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
136
src/core/filesystem.cpp
Normal 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
60
src/core/log.cpp
Normal 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
54
src/core/time.cpp
Normal 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
1080
src/drawing/batch.cpp
Normal file
File diff suppressed because it is too large
Load Diff
258
src/drawing/spritefont.cpp
Normal file
258
src/drawing/spritefont.cpp
Normal 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;
|
||||
}
|
64
src/drawing/subtexture.cpp
Normal file
64
src/drawing/subtexture.cpp
Normal 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
25
src/graphics/blend.cpp
Normal 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
|
||||
);
|
35
src/graphics/framebuffer.cpp
Normal file
35
src/graphics/framebuffer.cpp
Normal 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
355
src/graphics/material.cpp
Normal 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
40
src/graphics/mesh.cpp
Normal 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;
|
||||
}
|
94
src/graphics/renderpass.cpp
Normal file
94
src/graphics/renderpass.cpp
Normal 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
39
src/graphics/shader.cpp
Normal 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
61
src/graphics/texture.cpp
Normal 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
471
src/images/aseprite.cpp
Normal 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 = ⁢
|
||||
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
245
src/images/font.cpp
Normal 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
301
src/images/image.cpp
Normal 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
334
src/images/packer.cpp
Normal 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
403
src/input/input.cpp
Normal 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
166
src/input/virtual_axis.cpp
Normal 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;
|
||||
}
|
174
src/input/virtual_button.cpp
Normal file
174
src/input/virtual_button.cpp
Normal 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
191
src/input/virtual_stick.cpp
Normal 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;
|
||||
}
|
60
src/internal/graphics_backend.h
Normal file
60
src/internal/graphics_backend.h
Normal 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();
|
||||
}
|
||||
}
|
1496
src/internal/graphics_backend_d3d11.cpp
Normal file
1496
src/internal/graphics_backend_d3d11.cpp
Normal file
File diff suppressed because it is too large
Load Diff
235
src/internal/graphics_backend_dummy.cpp
Normal file
235
src/internal/graphics_backend_dummy.cpp
Normal 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))
|
1508
src/internal/graphics_backend_gl.cpp
Normal file
1508
src/internal/graphics_backend_gl.cpp
Normal file
File diff suppressed because it is too large
Load Diff
54
src/internal/input_backend.h
Normal file
54
src/internal/input_backend.h
Normal 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);
|
||||
}
|
||||
}
|
120
src/internal/platform_backend.h
Normal file
120
src/internal/platform_backend.h
Normal 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();
|
||||
}
|
||||
}
|
660
src/internal/platform_backend_sdl2.cpp
Normal file
660
src/internal/platform_backend_sdl2.cpp
Normal 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
156
src/math/calc.cpp
Normal 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
11
src/math/circle.cpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include <blah/math/circle.h>
|
||||
#include <blah/math/calc.h>
|
||||
|
||||
using namespace Blah;
|
||||
|
||||
|
||||
void Circle::project(const Vec2& axis, float* min, float* max) const
|
||||
{
|
||||
*min = Vec2::dot(center - axis * radius, axis);
|
||||
*max = Vec2::dot(center + axis * radius, axis);
|
||||
}
|
156
src/math/color.cpp
Normal file
156
src/math/color.cpp
Normal file
@ -0,0 +1,156 @@
|
||||
#include <blah/math/color.h>
|
||||
#include <blah/math/vec4.h>
|
||||
|
||||
using namespace Blah;
|
||||
|
||||
char const hex[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
|
||||
#define LT_HEX_VALUE(n) ((n >= '0' && n <= '9') ? (n - '0') : ((n >= 'A' && n <= 'F') ? (10 + n - 'A') : ((n >= 'a' && n <= 'f') ? (10 + n - 'a') : 0)))
|
||||
|
||||
Color::Color()
|
||||
: r(0), g(0), b(0), a(0) {}
|
||||
|
||||
Color::Color(int rgb) :
|
||||
r((uint8_t)((rgb & 0xFF0000) >> 16)),
|
||||
g((uint8_t)((rgb & 0x00FF00) >> 8)),
|
||||
b((uint8_t)(rgb & 0x0000FF)),
|
||||
a(255) {}
|
||||
|
||||
Color::Color(int rgb, float alpha) :
|
||||
r((int)(((uint8_t)((rgb & 0xFF0000) >> 16)) * alpha)),
|
||||
g((int)(((uint8_t)((rgb & 0x00FF00) >> 8)) * alpha)),
|
||||
b((int)(((uint8_t)(rgb & 0x0000FF)) * alpha)),
|
||||
a((int)(255 * alpha)) {}
|
||||
|
||||
Color::Color(uint8_t r, uint8_t g, uint8_t b)
|
||||
: r(r), g(g), b(b), a(255) {}
|
||||
|
||||
Color::Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
|
||||
: r(r), g(g), b(b), a(a) {}
|
||||
|
||||
Color::Color(const char* value) : r(0), g(0), b(0), a(255)
|
||||
{
|
||||
int length = 0;
|
||||
while (value[length] != '\0' && length < 10)
|
||||
length ++;
|
||||
|
||||
int offset = 0;
|
||||
if (length > 0 && value[0] == '#')
|
||||
offset = 1;
|
||||
else if (length >= 1 && value[0] == '0' && (value[1] == 'x' || value[1] == 'X'))
|
||||
offset = 2;
|
||||
|
||||
if (length - offset >= 8)
|
||||
a = (LT_HEX_VALUE(value[offset + 6]) << 4) + LT_HEX_VALUE(value[offset + 7]);
|
||||
|
||||
if (length - offset >= 6)
|
||||
{
|
||||
r = (LT_HEX_VALUE(value[offset + 0]) << 4) + LT_HEX_VALUE(value[offset + 1]);
|
||||
g = (LT_HEX_VALUE(value[offset + 2]) << 4) + LT_HEX_VALUE(value[offset + 3]);
|
||||
b = (LT_HEX_VALUE(value[offset + 4]) << 4) + LT_HEX_VALUE(value[offset + 5]);
|
||||
}
|
||||
}
|
||||
|
||||
void Color::premultiply()
|
||||
{
|
||||
r = r * a / 255;
|
||||
g = g * a / 255;
|
||||
b = b * a / 255;
|
||||
}
|
||||
|
||||
uint32_t Color::to_rgba() const
|
||||
{
|
||||
return
|
||||
((uint32_t)r << 24) |
|
||||
((uint32_t)g << 16) |
|
||||
((uint32_t)b << 8) |
|
||||
(uint32_t)a;
|
||||
}
|
||||
|
||||
Vec4 Color::to_vec4() const
|
||||
{
|
||||
return Vec4(r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f);
|
||||
}
|
||||
|
||||
void Color::to_hex_rgba(char* buffer) const
|
||||
{
|
||||
buffer[0] = hex[(r & 0xF0) >> 4];
|
||||
buffer[1] = hex[(r & 0x0F) >> 0];
|
||||
buffer[2] = hex[(g & 0xF0) >> 4];
|
||||
buffer[3] = hex[(g & 0x0F) >> 0];
|
||||
buffer[4] = hex[(b & 0xF0) >> 4];
|
||||
buffer[5] = hex[(b & 0x0F) >> 0];
|
||||
buffer[6] = hex[(a & 0xF0) >> 4];
|
||||
buffer[7] = hex[(a & 0x0F) >> 0];
|
||||
}
|
||||
|
||||
void Color::to_hex_rgb(char* buffer) const
|
||||
{
|
||||
buffer[0] = hex[(r & 0xF0) >> 4];
|
||||
buffer[1] = hex[(r & 0x0F) >> 0];
|
||||
buffer[2] = hex[(g & 0xF0) >> 4];
|
||||
buffer[3] = hex[(g & 0x0F) >> 0];
|
||||
buffer[4] = hex[(b & 0xF0) >> 4];
|
||||
buffer[5] = hex[(b & 0x0F) >> 0];
|
||||
}
|
||||
|
||||
Color Color::from_rgba(uint32_t value)
|
||||
{
|
||||
return
|
||||
{
|
||||
(uint8_t)((value & 0xFF000000) >> 24),
|
||||
(uint8_t)((value & 0x00FF0000) >> 16),
|
||||
(uint8_t)((value & 0x0000FF00) >> 8),
|
||||
(uint8_t)((value & 0x000000FF))
|
||||
};
|
||||
}
|
||||
|
||||
Color Color::from_rgb(uint32_t value)
|
||||
{
|
||||
return
|
||||
{
|
||||
(uint8_t)((value & 0xFF0000) >> 16),
|
||||
(uint8_t)((value & 0x00FF00) >> 8),
|
||||
(uint8_t)((value & 0x0000FF))
|
||||
};
|
||||
}
|
||||
|
||||
Color Color::lerp(Color a, Color b, float amount)
|
||||
{
|
||||
if (amount < 0) amount = 0;
|
||||
if (amount > 1) amount = 1;
|
||||
|
||||
return Color(
|
||||
(uint8_t)(a.r + (b.r - a.r) * amount),
|
||||
(uint8_t)(a.g + (b.g - a.g) * amount),
|
||||
(uint8_t)(a.b + (b.b - a.b) * amount),
|
||||
(uint8_t)(a.a + (b.a - a.a) * amount)
|
||||
);
|
||||
}
|
||||
|
||||
Color Color::operator*(float multiply) const
|
||||
{
|
||||
return Color(
|
||||
(int)(r * multiply),
|
||||
(int)(g * multiply),
|
||||
(int)(b * multiply),
|
||||
(int)(a * multiply));
|
||||
}
|
||||
|
||||
Color& Color::operator= (const int rgb)
|
||||
{
|
||||
r = (uint8_t)((rgb & 0xFF0000) >> 16);
|
||||
g = (uint8_t)((rgb & 0x00FF00) >> 8);
|
||||
b = (uint8_t)(rgb & 0x0000FF);
|
||||
a = 255;
|
||||
return *this;
|
||||
}
|
||||
|
||||
const Color Color::transparent = Color(0, 0, 0, 0);
|
||||
const Color Color::white = Color(255, 255, 255, 255);
|
||||
const Color Color::black = Color(0, 0, 0, 255);
|
||||
const Color Color::red = Color(255, 0, 0, 255);
|
||||
const Color Color::green = Color(0, 255, 0, 255);
|
||||
const Color Color::blue = Color(0, 0, 255, 255);
|
||||
const Color Color::yellow = Color(255, 255, 0, 255);
|
||||
const Color Color::purple = Color(255, 0, 255, 255);
|
||||
const Color Color::teal = Color(0, 255, 255, 255);
|
157
src/math/line.cpp
Normal file
157
src/math/line.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
#include <blah/math/line.h>
|
||||
#include <blah/math/rect.h>
|
||||
#include <blah/math/vec2.h>
|
||||
#include <blah/math/calc.h>
|
||||
|
||||
using namespace Blah;
|
||||
|
||||
Line::Line(float x0, float y0, float x1, float y1)
|
||||
{
|
||||
a.x = x0;
|
||||
a.y = y0;
|
||||
b.x = x1;
|
||||
b.y = y1;
|
||||
}
|
||||
|
||||
Line::Line(const Vec2& start, const Vec2& end)
|
||||
: a(start), b(end) {}
|
||||
|
||||
Rect Line::bounds() const
|
||||
{
|
||||
Vec2 pos = Vec2(Calc::min(a.x, b.x), Calc::min(a.y, b.y));
|
||||
|
||||
return Rect(
|
||||
pos,
|
||||
Vec2(Calc::max(a.x, b.x) - pos.x, Calc::max(a.y, b.y) - pos.y)
|
||||
);
|
||||
}
|
||||
|
||||
Vec2 Line::closest_point(const Vec2& pt) const
|
||||
{
|
||||
Vec2 v = b - a;
|
||||
Vec2 w = pt - a;
|
||||
float t = Vec2::dot(w, v) / Vec2::dot(v, v);
|
||||
t = Calc::clamp(t, 0, 1);
|
||||
|
||||
return v * t + a;
|
||||
}
|
||||
|
||||
bool Line::intersects(const Rect& rect) const
|
||||
{
|
||||
char ca = rect.get_sector(a);
|
||||
char cb = rect.get_sector(b);
|
||||
|
||||
if (ca == cb || (ca & cb) != 0)
|
||||
return false;
|
||||
|
||||
char both = ca | cb;
|
||||
|
||||
// top
|
||||
if ((both & 0b0100) != 0 && intersects(rect.top_line()))
|
||||
return true;
|
||||
|
||||
// bottom
|
||||
if ((both & 0b1000) != 0 && intersects(rect.bottom_line()))
|
||||
return true;
|
||||
|
||||
// left
|
||||
if ((both & 0b0001) != 0 && intersects(rect.left_line()))
|
||||
return true;
|
||||
|
||||
// right
|
||||
if ((both & 0b0010) != 0 && intersects(rect.right_line()))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Line::intersects(const Rect& rect, Vec2* out_intersection_point) const
|
||||
{
|
||||
char ca = rect.get_sector(a);
|
||||
char cb = rect.get_sector(b);
|
||||
|
||||
if (ca == cb || (ca & cb) != 0)
|
||||
return false;
|
||||
|
||||
char both = ca | cb;
|
||||
|
||||
// top
|
||||
if ((both & 0b0100) != 0 && intersects(rect.top_line(), out_intersection_point))
|
||||
return true;
|
||||
|
||||
// bottom
|
||||
if ((both & 0b1000) != 0 && intersects(rect.bottom_line(), out_intersection_point))
|
||||
return true;
|
||||
|
||||
// left
|
||||
if ((both & 0b0001) != 0 && intersects(rect.left_line(), out_intersection_point))
|
||||
return true;
|
||||
|
||||
// right
|
||||
if ((both & 0b0010) != 0 && intersects(rect.right_line(), out_intersection_point))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Line::intersects(const Line& line) const
|
||||
{
|
||||
Vec2 e = b - a;
|
||||
Vec2 d = line.b - line.a;
|
||||
float e_dot_d_perp = e.x * d.y - e.y * d.x;
|
||||
|
||||
// if e dot d == 0, it means the lines are parallel
|
||||
// so have infinite intersection points
|
||||
if (e_dot_d_perp < 0.0001 && e_dot_d_perp > -0.0001)
|
||||
return false;
|
||||
|
||||
Vec2 c = line.a - a;
|
||||
float t = (c.x * d.y - c.y * d.x) / e_dot_d_perp;
|
||||
if (t < 0 || t > 1)
|
||||
return false;
|
||||
|
||||
float u = (c.x * e.y - c.y * e.x) / e_dot_d_perp;
|
||||
if (u < 0 || u > 1)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Line::intersects(const Line& line, Vec2* intersection_point) const
|
||||
{
|
||||
Vec2 e = b - a;
|
||||
Vec2 d = line.b - line.a;
|
||||
float e_dot_d_perp = e.x * d.y - e.y * d.x;
|
||||
|
||||
// if e dot d == 0, it means the lines are parallel
|
||||
// so have infinite intersection points
|
||||
if (e_dot_d_perp < 0.0001 && e_dot_d_perp > -0.0001)
|
||||
return false;
|
||||
|
||||
Vec2 c = line.a - a;
|
||||
float t = (c.x * d.y - c.y * d.x) / e_dot_d_perp;
|
||||
if (t < 0 || t > 1)
|
||||
return false;
|
||||
|
||||
float u = (c.x * e.y - c.y * e.x) / e_dot_d_perp;
|
||||
if (u < 0 || u > 1)
|
||||
return false;
|
||||
|
||||
Vec2 i = (e * t) + a;
|
||||
intersection_point->x = i.x;
|
||||
intersection_point->y = i.y;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Line::project(const Vec2& axis, float* min, float* max) const
|
||||
{
|
||||
float dot = a.x * axis.x + a.y * axis.y;
|
||||
*min = dot;
|
||||
*max = dot;
|
||||
dot = b.x * axis.x + b.y * axis.y;
|
||||
*min = Calc::min(dot, *min);
|
||||
*max = Calc::max(dot, *max);
|
||||
}
|
||||
|
||||
Line Line::operator +(const Vec2& rhs) const { return Line(a + rhs, b + rhs); }
|
||||
Line Line::operator -(const Vec2& rhs) const { return Line(a - rhs, b - rhs); }
|
187
src/math/mat3x2.cpp
Normal file
187
src/math/mat3x2.cpp
Normal file
@ -0,0 +1,187 @@
|
||||
#include <blah/math/mat3x2.h>
|
||||
#include <blah/math/vec2.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
using namespace Blah;
|
||||
|
||||
Mat3x2::Mat3x2()
|
||||
: m11(0), m12(0), m21(0), m22(0), m31(0), m32(0) {}
|
||||
|
||||
Mat3x2::Mat3x2(float m11, float m12, float m21, float m22, float m31, float m32)
|
||||
: m11(m11), m12(m12), m21(m21), m22(m22), m31(m31), m32(m32) {}
|
||||
|
||||
Mat3x2 Mat3x2::invert() const
|
||||
{
|
||||
auto det = (m11 * m22) - (m21 * m12);
|
||||
auto invDet = 1.0f / det;
|
||||
|
||||
return Mat3x2
|
||||
{
|
||||
m22 * invDet,
|
||||
-m12 * invDet,
|
||||
-m21 * invDet,
|
||||
m11 * invDet,
|
||||
(m21 * m32 - m31 * m22) * invDet,
|
||||
(m31 * m12 - m11 * m32) * invDet
|
||||
};
|
||||
}
|
||||
|
||||
float Mat3x2::scaling_factor() const
|
||||
{
|
||||
return (float)sqrt((double)m11 * m11 + (double)m12 * m12);
|
||||
}
|
||||
|
||||
const Mat3x2 Mat3x2::identity = Mat3x2(1, 0, 0, 1, 0, 0);
|
||||
|
||||
Mat3x2 Mat3x2::operator *(const Mat3x2& rhs) const { return multiply(*this, rhs); }
|
||||
Mat3x2 Mat3x2::operator +(const Mat3x2& rhs) const { return add(*this, rhs); }
|
||||
Mat3x2 Mat3x2::operator -(const Mat3x2& rhs) const { return subtract(*this, rhs); }
|
||||
|
||||
Mat3x2& Mat3x2::operator*=(const Mat3x2& rhs)
|
||||
{
|
||||
*this = multiply(*this, rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool Mat3x2::operator ==(const Mat3x2& rhs)
|
||||
{
|
||||
return memcmp(this, &rhs, sizeof(Mat3x2));
|
||||
}
|
||||
|
||||
bool Mat3x2::operator !=(const Mat3x2& rhs)
|
||||
{
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
Mat3x2 Mat3x2::create_translation(const Vec2& Vec2)
|
||||
{
|
||||
return create_translation(Vec2.x, Vec2.y);
|
||||
}
|
||||
|
||||
Mat3x2 Mat3x2::create_translation(float x, float y)
|
||||
{
|
||||
return Mat3x2(1, 0, 0, 1, x, y);
|
||||
}
|
||||
|
||||
Mat3x2 Mat3x2::create_scale(float scale)
|
||||
{
|
||||
return create_scale(scale, scale);
|
||||
}
|
||||
|
||||
Mat3x2 Mat3x2::create_scale(Vec2 Vec2)
|
||||
{
|
||||
return create_scale(Vec2.x, Vec2.y);
|
||||
}
|
||||
|
||||
Mat3x2 Mat3x2::create_scale(float x, float y)
|
||||
{
|
||||
return Mat3x2(x, 0, 0, y, 0, 0);
|
||||
}
|
||||
|
||||
Mat3x2 Mat3x2::create_scale(float scale, Vec2 centerPoint)
|
||||
{
|
||||
Mat3x2 result;
|
||||
|
||||
float tx = centerPoint.x * (1 - scale);
|
||||
float ty = centerPoint.y * (1 - scale);
|
||||
|
||||
result.m11 = scale;
|
||||
result.m12 = 0.0f;
|
||||
result.m21 = 0.0f;
|
||||
result.m22 = scale;
|
||||
result.m31 = tx;
|
||||
result.m32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Mat3x2 Mat3x2::create_scale(Vec2 scale, Vec2 centerPoint)
|
||||
{
|
||||
Mat3x2 result;
|
||||
|
||||
float tx = centerPoint.x * (1 - scale.x);
|
||||
float ty = centerPoint.y * (1 - scale.y);
|
||||
|
||||
result.m11 = scale.x;
|
||||
result.m12 = 0.0f;
|
||||
result.m21 = 0.0f;
|
||||
result.m22 = scale.y;
|
||||
result.m31 = tx;
|
||||
result.m32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Mat3x2 Mat3x2::create_scale(float scaleX, float scaleY, Vec2 centerPoint)
|
||||
{
|
||||
Mat3x2 result;
|
||||
|
||||
float tx = centerPoint.x * (1 - scaleX);
|
||||
float ty = centerPoint.y * (1 - scaleY);
|
||||
|
||||
result.m11 = scaleX;
|
||||
result.m12 = 0.0f;
|
||||
result.m21 = 0.0f;
|
||||
result.m22 = scaleY;
|
||||
result.m31 = tx;
|
||||
result.m32 = ty;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Mat3x2 Mat3x2::create_rotation(float radians)
|
||||
{
|
||||
float c = cosf(radians);
|
||||
float s = sinf(radians);
|
||||
|
||||
return Mat3x2(c, s, -s, c, 0, 0);
|
||||
}
|
||||
|
||||
Mat3x2 Mat3x2::create_transform(const Vec2& position, const Vec2& origin, const Vec2& scale, float rotation)
|
||||
{
|
||||
Mat3x2 matrix = identity;
|
||||
|
||||
if (origin.x != 0 || origin.y != 0)
|
||||
matrix = create_translation(-origin.x, -origin.y);
|
||||
if (scale.x != 1 || scale.y != 1)
|
||||
matrix = matrix * create_scale(scale);
|
||||
if (rotation != 0)
|
||||
matrix = matrix * create_rotation(rotation);
|
||||
if (position.x != 0 || position.y != 0)
|
||||
matrix = matrix * create_translation(position);
|
||||
|
||||
return matrix;
|
||||
}
|
||||
|
||||
Mat3x2 Mat3x2::add(const Mat3x2& a, const Mat3x2& b)
|
||||
{
|
||||
return Mat3x2(
|
||||
a.m11 + b.m11,
|
||||
a.m12 + b.m12,
|
||||
a.m21 + b.m21,
|
||||
a.m22 + b.m22,
|
||||
a.m31 + b.m31,
|
||||
a.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
109
src/math/mat4x4.cpp
Normal file
@ -0,0 +1,109 @@
|
||||
#include <blah/math/mat4x4.h>
|
||||
|
||||
using namespace Blah;
|
||||
|
||||
Mat4x4::Mat4x4() :
|
||||
m11(0.0f), m12(0.0f), m13(0.0f), m14(0.0f),
|
||||
m21(0.0f), m22(0.0f), m23(0.0f), m24(0.0f),
|
||||
m31(0.0f), m32(0.0f), m33(0.0f), m34(0.0f),
|
||||
m41(0.0f), m42(0.0f), m43(0.0f), m44(0.0f) {}
|
||||
|
||||
Mat4x4::Mat4x4(
|
||||
float m11, float m12, float m13, float m14,
|
||||
float m21, float m22, float m23, float m24,
|
||||
float m31, float m32, float m33, float m34,
|
||||
float m41, float m42, float m43, float m44) :
|
||||
m11(m11), m12(m12), m13(m13), m14(m14),
|
||||
m21(m21), m22(m22), m23(m23), m24(m24),
|
||||
m31(m31), m32(m32), m33(m33), m34(m34),
|
||||
m41(m41), m42(m42), m43(m43), m44(m44) {}
|
||||
|
||||
const Mat4x4 Mat4x4::identity = Mat4x4(
|
||||
1.0f, 0.0f, 0.0f, 0.0f,
|
||||
0.0f, 1.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f, 0.0f,
|
||||
0.0f, 0.0f, 0.0f, 1.0f);
|
||||
|
||||
Mat4x4 Mat4x4::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
51
src/math/point.cpp
Normal file
@ -0,0 +1,51 @@
|
||||
#include <blah/math/vec2.h>
|
||||
#include <blah/math/mat3x2.h>
|
||||
#include <math.h>
|
||||
|
||||
using namespace Blah;
|
||||
|
||||
Point::Point()
|
||||
{
|
||||
x = y = 0;
|
||||
}
|
||||
|
||||
Point::Point(int px, int py)
|
||||
{
|
||||
x = px;
|
||||
y = py;
|
||||
}
|
||||
|
||||
Point Point::operator +(const Point rhs) const { return Point(x + rhs.x, y + rhs.y); }
|
||||
Point Point::operator -(const Point rhs) const { return Point(x - rhs.x, y - rhs.y); }
|
||||
Point Point::operator /(const int rhs) const { return Point(x / rhs, y / rhs); }
|
||||
Point Point::operator *(const int rhs) const { return Point(x * rhs, y * rhs); }
|
||||
Point Point::operator-() const { return Point(-x, -y); }
|
||||
|
||||
Point& Point::operator +=(const Point& rhs) { x += rhs.x; y += rhs.y; return *this; }
|
||||
Point& Point::operator -=(const Point& rhs) { x -= rhs.x; y -= rhs.y; return *this; }
|
||||
Point& Point::operator /=(const Point& rhs) { x /= rhs.x; y /= rhs.y; return *this; }
|
||||
Point& Point::operator *=(const Point& rhs) { x *= rhs.x; y *= rhs.y; return *this; }
|
||||
Point& Point::operator /=(int rhs) { x /= rhs; y /= rhs; return *this; }
|
||||
Point& Point::operator *=(int rhs) { x *= rhs; y *= rhs; return *this; }
|
||||
|
||||
bool Point::operator ==(const Point& rhs) { return x == rhs.x && y == rhs.y; }
|
||||
bool Point::operator !=(const Point& rhs) { return x != rhs.x || y != rhs.y; }
|
||||
|
||||
float Point::length() const
|
||||
{
|
||||
return sqrtf((float)(x * x + y * y));
|
||||
}
|
||||
|
||||
int Point::length_squared() const
|
||||
{
|
||||
return x * x + y * y;
|
||||
}
|
||||
|
||||
const Point Point::unitX = Point(1, 0);
|
||||
const Point Point::unitY = Point(0, 1);
|
||||
const Point Point::right = Point(1, 0);
|
||||
const Point Point::up = Point(0, -1);
|
||||
const Point Point::down = Point(0, 1);
|
||||
const Point Point::left = Point(-1, 0);
|
||||
const Point Point::zero = Point(0, 0);
|
||||
const Point Point::one = Point(1, 1);
|
21
src/math/quad.cpp
Normal file
21
src/math/quad.cpp
Normal file
@ -0,0 +1,21 @@
|
||||
#include <blah/math/quad.h>
|
||||
#include <blah/math/calc.h>
|
||||
|
||||
namespace Blah
|
||||
{
|
||||
void Quad::project(const Vec2& axis, float* min, float* max) const
|
||||
{
|
||||
float dot = Vec2::dot(a, axis);
|
||||
*min = dot;
|
||||
*max = dot;
|
||||
dot = Vec2::dot(b, axis);
|
||||
*min = Calc::min(dot, *min);
|
||||
*max = Calc::max(dot, *max);
|
||||
dot = Vec2::dot(c, axis);
|
||||
*min = Calc::min(dot, *min);
|
||||
*max = Calc::max(dot, *max);
|
||||
dot = Vec2::dot(d, axis);
|
||||
*min = Calc::min(dot, *min);
|
||||
*max = Calc::max(dot, *max);
|
||||
}
|
||||
}
|
275
src/math/rect.cpp
Normal file
275
src/math/rect.cpp
Normal 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
90
src/math/rectI.cpp
Normal file
@ -0,0 +1,90 @@
|
||||
#include <blah/math/rectI.h>
|
||||
#include <blah/math/rect.h>
|
||||
#include <blah/math/point.h>
|
||||
#include <blah/math/vec2.h>
|
||||
|
||||
using namespace Blah;
|
||||
|
||||
RectI::RectI()
|
||||
{
|
||||
x = y = w = h = 0;
|
||||
}
|
||||
|
||||
RectI::RectI(int rx, int ry, int rw, int rh)
|
||||
{
|
||||
x = rx;
|
||||
y = ry;
|
||||
w = rw;
|
||||
h = rh;
|
||||
}
|
||||
|
||||
RectI::RectI(Point pos, Point size)
|
||||
{
|
||||
x = pos.x;
|
||||
y = pos.y;
|
||||
w = size.x;
|
||||
h = size.y;
|
||||
}
|
||||
|
||||
Point RectI::center() const { return Point(centerX(), centerY()); }
|
||||
Point RectI::top_left() const { return Point(left(), top()); }
|
||||
Point RectI::top_right() const { return Point(right(), top()); }
|
||||
Point RectI::bottom_left() const { return Point(left(), bottom()); }
|
||||
Point RectI::bottom_right() const { return Point(right(), bottom()); }
|
||||
|
||||
bool RectI::contains(const Point& point) const
|
||||
{
|
||||
return point.x >= x && point.x < x + w && point.y >= y && point.y < y + h;
|
||||
}
|
||||
|
||||
bool RectI::contains(const Vec2& point) const
|
||||
{
|
||||
return point.x >= x && point.x < x + w && point.y >= y && point.y < y + h;
|
||||
}
|
||||
|
||||
char RectI::get_sector(const Point& pt) const
|
||||
{
|
||||
char h;
|
||||
if (pt.x < left())
|
||||
h = 0b0001;
|
||||
else if (pt.x >= right())
|
||||
h = 0b0010;
|
||||
else
|
||||
h = 0;
|
||||
|
||||
char v;
|
||||
if (pt.y < top())
|
||||
v = 0b0100;
|
||||
else if (pt.y >= bottom())
|
||||
v = 0b1000;
|
||||
else
|
||||
v = 0;
|
||||
|
||||
return h | v;
|
||||
}
|
||||
|
||||
char RectI::get_sector(const Vec2& pt) const
|
||||
{
|
||||
char h;
|
||||
if (pt.x < left())
|
||||
h = 0b0001;
|
||||
else if (pt.x >= right())
|
||||
h = 0b0010;
|
||||
else
|
||||
h = 0;
|
||||
|
||||
char v;
|
||||
if (pt.y < top())
|
||||
v = 0b0100;
|
||||
else if (pt.y >= bottom())
|
||||
v = 0b1000;
|
||||
else
|
||||
v = 0;
|
||||
|
||||
return h | v;
|
||||
}
|
||||
|
||||
RectI RectI::operator+(const Point& rhs) const { return RectI(x + rhs.x, y + rhs.y, w, h); }
|
||||
RectI RectI::operator-(const Point& rhs) const { return RectI(x - rhs.x, y - rhs.y, w, h); }
|
||||
RectI& RectI::operator+=(const Point& rhs) { x += rhs.x; y += rhs.y; return *this; }
|
||||
RectI& RectI::operator-=(const Point& rhs) { x -= rhs.x; y -= rhs.y; return *this; }
|
26
src/math/stopwatch.cpp
Normal file
26
src/math/stopwatch.cpp
Normal file
@ -0,0 +1,26 @@
|
||||
#include <blah/math/stopwatch.h>
|
||||
#include <chrono>
|
||||
|
||||
using namespace std::chrono;
|
||||
using namespace Blah;
|
||||
|
||||
Stopwatch::Stopwatch()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void Stopwatch::reset()
|
||||
{
|
||||
start_time = std::chrono::duration_cast<std::chrono::microseconds>(system_clock::now().time_since_epoch()).count();
|
||||
}
|
||||
|
||||
uint64_t Stopwatch::milliseconds()
|
||||
{
|
||||
return microseconds() / 1000;
|
||||
}
|
||||
|
||||
|
||||
uint64_t Stopwatch::microseconds()
|
||||
{
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(system_clock::now().time_since_epoch()).count() - start_time;
|
||||
}
|
133
src/math/vec2.cpp
Normal file
133
src/math/vec2.cpp
Normal 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
|
107
src/streams/bufferstream.cpp
Normal file
107
src/streams/bufferstream.cpp
Normal 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;
|
||||
}
|
97
src/streams/filestream.cpp
Normal file
97
src/streams/filestream.cpp
Normal 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;
|
||||
}
|
55
src/streams/memorystream.cpp
Normal file
55
src/streams/memorystream.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include <blah/streams/memorystream.h>
|
||||
#include <string.h>
|
||||
|
||||
using namespace Blah;
|
||||
|
||||
MemoryStream::MemoryStream()
|
||||
: m_data(nullptr), m_length(0), m_position(0) {}
|
||||
|
||||
MemoryStream::MemoryStream(char* data, int64_t length)
|
||||
: m_data(data), m_length(length), m_position(0) {}
|
||||
|
||||
MemoryStream::MemoryStream(MemoryStream&& src) noexcept
|
||||
{
|
||||
m_data = src.m_data;
|
||||
m_position = src.m_position;
|
||||
m_length = src.m_length;
|
||||
src.m_data = nullptr;
|
||||
src.m_length = src.m_position = 0;
|
||||
}
|
||||
|
||||
MemoryStream& MemoryStream::operator=(MemoryStream&& src) noexcept
|
||||
{
|
||||
m_data = src.m_data;
|
||||
m_position = src.m_position;
|
||||
m_length = src.m_length;
|
||||
src.m_data = nullptr;
|
||||
src.m_length = src.m_position = 0;
|
||||
return *this;
|
||||
}
|
||||
|
||||
int64_t MemoryStream::read_into(void* ptr, int64_t len)
|
||||
{
|
||||
if (len < 0 || ptr == nullptr)
|
||||
return 0;
|
||||
|
||||
if (len > m_length - m_position)
|
||||
len = m_length - m_position;
|
||||
|
||||
memcpy(ptr, m_data + m_position, (size_t)len);
|
||||
m_position += len;
|
||||
return len;
|
||||
}
|
||||
|
||||
int64_t MemoryStream::write_from(const void* ptr, int64_t len)
|
||||
{
|
||||
if (len < 0 || ptr == nullptr)
|
||||
return 0;
|
||||
|
||||
if (len > m_length - m_position)
|
||||
len = m_length - m_position;
|
||||
|
||||
memcpy(m_data + m_position, ptr, (size_t)len);
|
||||
m_position += len;
|
||||
return len;
|
||||
}
|
29
src/streams/stream.cpp
Normal file
29
src/streams/stream.cpp
Normal 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
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
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
5011
src/third_party/stb_truetype.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user