summaryrefslogtreecommitdiffstats
path: root/src/core
diff options
context:
space:
mode:
authoruntodesu <kirill@untode.su>2025-06-29 22:24:42 +0500
committeruntodesu <kirill@untode.su>2025-06-29 22:24:42 +0500
commit6cd00aacfa22fed6a54a9b812f6b069ad16feec0 (patch)
treeb77f4e665da3dd235cdb01e7e6ea78c1c02ecf2e /src/core
parentf440914e1ae453768d09383f332bc7844e0a700e (diff)
downloadvoxelius-6cd00aacfa22fed6a54a9b812f6b069ad16feec0.tar.bz2
voxelius-6cd00aacfa22fed6a54a9b812f6b069ad16feec0.zip
Move game sources into src subdirectory
Diffstat (limited to 'src/core')
-rw-r--r--src/core/CMakeLists.txt44
-rw-r--r--src/core/aabb.cc59
-rw-r--r--src/core/aabb.hh31
-rw-r--r--src/core/angles.hh107
-rw-r--r--src/core/binfile.cc70
-rw-r--r--src/core/binfile.hh10
-rw-r--r--src/core/buffer.cc207
-rw-r--r--src/core/buffer.hh255
-rw-r--r--src/core/cmdline.cc81
-rw-r--r--src/core/cmdline.hh13
-rw-r--r--src/core/concepts.hh15
-rw-r--r--src/core/config.cc191
-rw-r--r--src/core/config.hh182
-rw-r--r--src/core/constexpr.hh191
-rw-r--r--src/core/crc64.cc286
-rw-r--r--src/core/crc64.hh12
-rw-r--r--src/core/epoch.cc39
-rw-r--r--src/core/epoch.hh19
-rw-r--r--src/core/floathacks.hh56
-rw-r--r--src/core/image.cc112
-rw-r--r--src/core/image.hh13
-rw-r--r--src/core/macros.hh19
-rw-r--r--src/core/pch.hh50
-rw-r--r--src/core/randomizer.hh57
-rw-r--r--src/core/resource.hh18
-rw-r--r--src/core/strtools.cc54
-rw-r--r--src/core/strtools.hh21
-rw-r--r--src/core/vectors.hh47
-rw-r--r--src/core/version.cc12
-rw-r--r--src/core/version.cc.in12
-rw-r--r--src/core/version.hh12
31 files changed, 2295 insertions, 0 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
new file mode 100644
index 0000000..13284cb
--- /dev/null
+++ b/src/core/CMakeLists.txt
@@ -0,0 +1,44 @@
+add_library(core STATIC
+ "${CMAKE_CURRENT_LIST_DIR}/aabb.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/aabb.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/binfile.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/binfile.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/buffer.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/buffer.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/cmdline.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/cmdline.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/concepts.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/config.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/config.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/constexpr.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/crc64.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/crc64.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/epoch.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/epoch.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/floathacks.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/image.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/image.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/macros.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/pch.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/resource.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/strtools.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/strtools.hh"
+ "${CMAKE_CURRENT_LIST_DIR}/version.cc"
+ "${CMAKE_CURRENT_LIST_DIR}/version.hh")
+target_compile_features(core PUBLIC cxx_std_20)
+target_include_directories(core PUBLIC "${DEPS_INCLUDE_DIR}")
+target_include_directories(core PUBLIC "${PROJECT_SOURCE_DIR}/src")
+target_precompile_headers(core PRIVATE "${CMAKE_CURRENT_LIST_DIR}/pch.hh")
+target_link_libraries(core PUBLIC enet emhash glm physfs spdlog stb)
+
+if(WIN32)
+ target_compile_definitions(core PUBLIC _CRT_SECURE_NO_WARNINGS)
+ target_compile_definitions(core PUBLIC _USE_MATH_DEFINES)
+ target_compile_definitions(core PUBLIC NOMINMAX)
+endif()
+
+if(MSVC)
+ target_compile_options(core PUBLIC /utf-8)
+endif()
+
+configure_file("${CMAKE_CURRENT_LIST_DIR}/version.cc.in" "${CMAKE_CURRENT_LIST_DIR}/version.cc")
diff --git a/src/core/aabb.cc b/src/core/aabb.cc
new file mode 100644
index 0000000..3661143
--- /dev/null
+++ b/src/core/aabb.cc
@@ -0,0 +1,59 @@
+#include "core/pch.hh"
+
+#include "core/aabb.hh"
+
+AABB::AABB(const glm::fvec3& min, const glm::fvec3& max)
+{
+ set_bounds(min, max);
+}
+
+void AABB::set_bounds(const glm::fvec3& min, const glm::fvec3& max)
+{
+ this->min = min;
+ this->max = max;
+}
+
+void AABB::set_offset(const glm::fvec3& base, const glm::fvec3& size)
+{
+ this->min = base;
+ this->max = base + size;
+}
+
+bool AABB::contains(const glm::fvec3& point) const
+{
+ auto result = true;
+ result = result && (point.x >= min.x) && (point.x <= max.x);
+ result = result && (point.y >= min.y) && (point.y <= max.y);
+ result = result && (point.z >= min.z) && (point.z <= max.z);
+ return result;
+}
+
+bool AABB::intersect(const AABB& other_box) const
+{
+ auto result = true;
+ result = result && (min.x < other_box.max.x) && (max.x > other_box.min.x);
+ result = result && (min.y < other_box.max.y) && (max.y > other_box.min.y);
+ result = result && (min.z < other_box.max.z) && (max.z > other_box.min.z);
+ return result;
+}
+
+AABB AABB::combine_with(const AABB& other_box) const
+{
+ AABB result;
+ result.set_bounds(min, other_box.max);
+ return result;
+}
+
+AABB AABB::multiply_with(const AABB& other_box) const
+{
+ AABB result;
+ result.set_bounds(other_box.min, max);
+ return result;
+}
+
+AABB AABB::push(const glm::fvec3& vector) const
+{
+ AABB result;
+ result.set_bounds(min + vector, max + vector);
+ return result;
+}
diff --git a/src/core/aabb.hh b/src/core/aabb.hh
new file mode 100644
index 0000000..fe07060
--- /dev/null
+++ b/src/core/aabb.hh
@@ -0,0 +1,31 @@
+#ifndef CORE_AABB_HH
+#define CORE_AABB_HH 1
+#pragma once
+
+#include "core/macros.hh"
+
+class AABB final {
+public:
+ DECLARE_DEFAULT_CONSTRUCTOR(AABB);
+ explicit AABB(const glm::fvec3& min, const glm::fvec3& max);
+ virtual ~AABB(void) = default;
+
+ void set_bounds(const glm::fvec3& min, const glm::fvec3& max);
+ void set_offset(const glm::fvec3& base, const glm::fvec3& size);
+
+ const glm::fvec3& get_min(void) const;
+ const glm::fvec3& get_max(void) const;
+
+ bool contains(const glm::fvec3& point) const;
+ bool intersect(const AABB& other_box) const;
+
+ AABB combine_with(const AABB& other_box) const;
+ AABB multiply_with(const AABB& other_box) const;
+ AABB push(const glm::fvec3& vector) const;
+
+public:
+ glm::fvec3 min;
+ glm::fvec3 max;
+};
+
+#endif /* CORE_AABB_HH */
diff --git a/src/core/angles.hh b/src/core/angles.hh
new file mode 100644
index 0000000..a2a3d55
--- /dev/null
+++ b/src/core/angles.hh
@@ -0,0 +1,107 @@
+#ifndef CORE_ANGLES_HH
+#define CORE_ANGLES_HH 1
+#pragma once
+
+#include "core/constexpr.hh"
+
+constexpr float A180 = vx::radians(180.0f);
+constexpr float A360 = vx::radians(360.0f);
+
+namespace cxangles
+{
+float wrap_180(float angle);
+float wrap_360(float angle);
+} // namespace cxangles
+
+namespace cxangles
+{
+glm::fvec3 wrap_180(const glm::fvec3& angles);
+glm::fvec3 wrap_360(const glm::fvec3& angles);
+} // namespace cxangles
+
+namespace cxangles
+{
+void vectors(const glm::fvec3& angles, glm::fvec3& forward);
+void vectors(const glm::fvec3& angles, glm::fvec3* forward, glm::fvec3* right, glm::fvec3* up);
+} // namespace cxangles
+
+inline float cxangles::wrap_180(float angle)
+{
+ const auto result = std::fmod(angle + A180, A360);
+
+ if(result < 0.0f) {
+ return result + A180;
+ }
+
+ return result - A180;
+}
+
+inline float cxangles::wrap_360(float angle)
+{
+ return std::fmod(std::fmod(angle, A360) + A360, A360);
+}
+
+inline glm::fvec3 cxangles::wrap_180(const glm::fvec3& angles)
+{
+ return glm::fvec3 {
+ cxangles::wrap_180(angles.x),
+ cxangles::wrap_180(angles.y),
+ cxangles::wrap_180(angles.z),
+ };
+}
+
+inline glm::fvec3 cxangles::wrap_360(const glm::fvec3& angles)
+{
+ return glm::fvec3 {
+ cxangles::wrap_360(angles.x),
+ cxangles::wrap_360(angles.y),
+ cxangles::wrap_360(angles.z),
+ };
+}
+
+inline void cxangles::vectors(const glm::fvec3& angles, glm::fvec3& forward)
+{
+ const float cosp = std::cos(angles.x);
+ const float cosy = std::cos(angles.y);
+ const float sinp = std::sin(angles.x);
+ const float siny = std::sin(angles.y);
+
+ forward.x = cosp * siny * (-1.0f);
+ forward.y = sinp;
+ forward.z = cosp * cosy * (-1.0f);
+}
+
+inline void cxangles::vectors(const glm::fvec3& angles, glm::fvec3* forward, glm::fvec3* right, glm::fvec3* up)
+{
+ if(!forward && !right && !up) {
+ // There's no point in figuring out
+ // direction vectors if nothing is passed
+ // in the function to store that stuff in
+ return;
+ }
+
+ const auto pcv = glm::cos(angles);
+ const auto psv = glm::sin(angles);
+ const auto ncv = pcv * (-1.0f);
+ const auto nsv = psv * (-1.0f);
+
+ if(forward) {
+ forward->x = pcv.x * nsv.y;
+ forward->y = psv.x;
+ forward->z = pcv.x * ncv.y;
+ }
+
+ if(right) {
+ right->x = pcv.z * pcv.y;
+ right->y = psv.z * pcv.y;
+ right->z = nsv.y;
+ }
+
+ if(up) {
+ up->x = psv.x * psv.y * pcv.z + ncv.y * psv.z;
+ up->y = pcv.x * pcv.z;
+ up->z = nsv.x * ncv.y * pcv.z + psv.y * psv.z;
+ }
+}
+
+#endif /* CORE_ANGLES_HH */
diff --git a/src/core/binfile.cc b/src/core/binfile.cc
new file mode 100644
index 0000000..aa39039
--- /dev/null
+++ b/src/core/binfile.cc
@@ -0,0 +1,70 @@
+#include "core/pch.hh"
+
+#include "core/binfile.hh"
+
+#include "core/resource.hh"
+
+static emhash8::HashMap<std::string, resource_ptr<BinFile>> resource_map;
+
+template<>
+resource_ptr<BinFile> resource::load<BinFile>(const char* name, unsigned int flags)
+{
+ auto it = resource_map.find(name);
+
+ if(it != resource_map.cend()) {
+ // Return an existing resource
+ return it->second;
+ }
+
+ auto file = PHYSFS_openRead(name);
+
+ if(file == nullptr) {
+ spdlog::warn("resource: {}: {}", name, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return nullptr;
+ }
+
+ auto new_resource = std::make_shared<BinFile>();
+ new_resource->size = PHYSFS_fileLength(file);
+ new_resource->buffer = new std::byte[new_resource->size];
+
+ PHYSFS_readBytes(file, new_resource->buffer, new_resource->size);
+ PHYSFS_close(file);
+
+ return resource_map.insert_or_assign(name, new_resource).first->second;
+}
+
+template<>
+void resource::hard_cleanup<BinFile>(void)
+{
+ for(const auto& it : resource_map) {
+ if(it.second.use_count() > 1L) {
+ spdlog::warn("resource: zombie resource [BinFile] {} [use_count={}]", it.first, it.second.use_count());
+ } else {
+ spdlog::debug("resource: releasing [BinFile] {}", it.first);
+ }
+
+ delete[] it.second->buffer;
+ }
+
+ resource_map.clear();
+}
+
+template<>
+void resource::soft_cleanup<BinFile>(void)
+{
+ auto iter = resource_map.cbegin();
+
+ while(iter != resource_map.cend()) {
+ if(iter->second.use_count() == 1L) {
+ spdlog::debug("resource: releasing [BinFile] {}", iter->first);
+
+ delete[] iter->second->buffer;
+
+ iter = resource_map.erase(iter);
+
+ continue;
+ }
+
+ iter = std::next(iter);
+ }
+}
diff --git a/src/core/binfile.hh b/src/core/binfile.hh
new file mode 100644
index 0000000..21dab40
--- /dev/null
+++ b/src/core/binfile.hh
@@ -0,0 +1,10 @@
+#ifndef CORE_BINFILE_HH
+#define CORE_BINFILE_HH 1
+#pragma once
+
+struct BinFile final {
+ std::byte* buffer;
+ std::size_t size;
+};
+
+#endif /* CORE_BINFILE_HH */
diff --git a/src/core/buffer.cc b/src/core/buffer.cc
new file mode 100644
index 0000000..0e18f4f
--- /dev/null
+++ b/src/core/buffer.cc
@@ -0,0 +1,207 @@
+#include "core/pch.hh"
+
+#include "core/buffer.hh"
+
+#include "core/constexpr.hh"
+
+ReadBuffer::ReadBuffer(const void* data, std::size_t size)
+{
+ reset(data, size);
+}
+
+ReadBuffer::ReadBuffer(const ENetPacket* packet)
+{
+ reset(packet);
+}
+
+ReadBuffer::ReadBuffer(PHYSFS_File* file)
+{
+ reset(file);
+}
+
+std::size_t ReadBuffer::size(void) const
+{
+ return m_vector.size();
+}
+
+const std::byte* ReadBuffer::data(void) const
+{
+ return m_vector.data();
+}
+
+void ReadBuffer::reset(const void* data, std::size_t size)
+{
+ auto bytes = reinterpret_cast<const std::byte*>(data);
+ m_vector.assign(bytes, bytes + size);
+ m_position = 0U;
+}
+
+void ReadBuffer::reset(const ENetPacket* packet)
+{
+ auto bytes_ptr = reinterpret_cast<const std::byte*>(packet->data);
+ m_vector.assign(bytes_ptr, bytes_ptr + packet->dataLength);
+ m_position = 0;
+}
+
+void ReadBuffer::reset(PHYSFS_File* file)
+{
+ m_vector.resize(PHYSFS_fileLength(file));
+ m_position = 0;
+
+ PHYSFS_seek(file, 0);
+ PHYSFS_readBytes(file, m_vector.data(), m_vector.size());
+}
+
+float ReadBuffer::read_FP32(void)
+{
+ return floathacks::uint32_to_float(read_UI32());
+}
+
+std::uint8_t ReadBuffer::read_UI8(void)
+{
+ if((m_position + 1U) <= m_vector.size()) {
+ auto result = static_cast<std::uint8_t>(m_vector[m_position]);
+ m_position += 1U;
+ return result;
+ }
+
+ m_position += 1U;
+ return 0;
+}
+
+std::uint16_t ReadBuffer::read_UI16(void)
+{
+ if((m_position + 2U) <= m_vector.size()) {
+ auto result = UINT16_C(0x0000);
+ result |= static_cast<std::uint16_t>(m_vector[m_position + 0U]) << 8U;
+ result |= static_cast<std::uint16_t>(m_vector[m_position + 1U]) << 0U;
+ m_position += 2U;
+ return result;
+ }
+
+ m_position += 2U;
+ return 0;
+}
+
+std::uint32_t ReadBuffer::read_UI32(void)
+{
+ if((m_position + 4U) <= m_vector.size()) {
+ auto result = UINT32_C(0x00000000);
+ result |= static_cast<std::uint32_t>(m_vector[m_position + 0U]) << 24U;
+ result |= static_cast<std::uint32_t>(m_vector[m_position + 1U]) << 16U;
+ result |= static_cast<std::uint32_t>(m_vector[m_position + 2U]) << 8U;
+ result |= static_cast<std::uint32_t>(m_vector[m_position + 3U]) << 0U;
+ m_position += 4U;
+ return result;
+ }
+
+ m_position += 4U;
+ return 0;
+}
+
+std::uint64_t ReadBuffer::read_UI64(void)
+{
+ if((m_position + 8U) <= m_vector.size()) {
+ auto result = UINT64_C(0x0000000000000000);
+ result |= (0x00000000000000FF & static_cast<std::uint64_t>(m_vector[m_position + 0U])) << 56U;
+ result |= (0x00000000000000FF & static_cast<std::uint64_t>(m_vector[m_position + 1U])) << 48U;
+ result |= (0x00000000000000FF & static_cast<std::uint64_t>(m_vector[m_position + 2U])) << 40U;
+ result |= (0x00000000000000FF & static_cast<std::uint64_t>(m_vector[m_position + 3U])) << 32U;
+ result |= (0x00000000000000FF & static_cast<std::uint64_t>(m_vector[m_position + 4U])) << 24U;
+ result |= (0x00000000000000FF & static_cast<std::uint64_t>(m_vector[m_position + 5U])) << 16U;
+ result |= (0x00000000000000FF & static_cast<std::uint64_t>(m_vector[m_position + 6U])) << 8U;
+ result |= (0x00000000000000FF & static_cast<std::uint64_t>(m_vector[m_position + 7U])) << 0U;
+ m_position += 8U;
+ return result;
+ }
+
+ m_position += 8U;
+ return 0;
+}
+
+std::string ReadBuffer::read_string(void)
+{
+ auto size = static_cast<std::size_t>(read_UI16());
+ auto result = std::string();
+
+ for(std::size_t i = 0; i < size; ++i) {
+ if(m_position < m_vector.size()) {
+ result.push_back(static_cast<char>(m_vector[m_position]));
+ }
+
+ m_position += 1U;
+ }
+
+ return result;
+}
+
+std::size_t WriteBuffer::size(void) const
+{
+ return m_vector.size();
+}
+
+const std::byte* WriteBuffer::data(void) const
+{
+ return m_vector.data();
+}
+
+void WriteBuffer::reset(void)
+{
+ m_vector.clear();
+}
+
+void WriteBuffer::write_UI8(std::uint8_t value)
+{
+ m_vector.push_back(static_cast<std::byte>(value));
+}
+
+void WriteBuffer::write_UI16(std::uint16_t value)
+{
+ m_vector.push_back(static_cast<std::byte>(UINT16_C(0xFF) & ((value & UINT16_C(0xFF00)) >> 8U)));
+ m_vector.push_back(static_cast<std::byte>(UINT16_C(0xFF) & ((value & UINT16_C(0x00FF)) >> 0U)));
+}
+
+void WriteBuffer::write_UI32(std::uint32_t value)
+{
+ m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0xFF000000)) >> 24U)));
+ m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0x00FF0000)) >> 16U)));
+ m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0x0000FF00)) >> 8U)));
+ m_vector.push_back(static_cast<std::byte>(UINT32_C(0xFF) & ((value & UINT32_C(0x000000FF)) >> 0U)));
+}
+
+void WriteBuffer::write_UI64(std::uint64_t value)
+{
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0xFF00000000000000)) >> 56U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x00FF000000000000)) >> 48U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x0000FF0000000000)) >> 40U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x000000FF00000000)) >> 32U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x00000000FF000000)) >> 24U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x0000000000FF0000)) >> 16U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x000000000000FF00)) >> 8U)));
+ m_vector.push_back(static_cast<std::byte>(UINT64_C(0xFF) & ((value & UINT64_C(0x00000000000000FF)) >> 0U)));
+}
+
+void WriteBuffer::write_string(const std::string& value)
+{
+ const std::size_t size = vx::min<std::size_t>(UINT16_MAX, value.size());
+
+ write_UI16(static_cast<std::uint16_t>(size));
+
+ for(std::size_t i = 0; i < size; m_vector.push_back(static_cast<std::byte>(value[i++])))
+ ;
+}
+
+PHYSFS_File* WriteBuffer::to_file(const char* path, bool append) const
+{
+ if(auto file = (append ? PHYSFS_openAppend(path) : PHYSFS_openWrite(path))) {
+ PHYSFS_writeBytes(file, m_vector.data(), m_vector.size());
+ return file;
+ }
+
+ return nullptr;
+}
+
+ENetPacket* WriteBuffer::to_packet(enet_uint32 flags) const
+{
+ return enet_packet_create(m_vector.data(), m_vector.size(), flags);
+}
diff --git a/src/core/buffer.hh b/src/core/buffer.hh
new file mode 100644
index 0000000..1397e16
--- /dev/null
+++ b/src/core/buffer.hh
@@ -0,0 +1,255 @@
+#ifndef CORE_BUFFER_HH
+#define CORE_BUFFER_HH 1
+
+#include "core/floathacks.hh"
+#include "core/macros.hh"
+
+class ReadBuffer final {
+public:
+ DECLARE_DEFAULT_CONSTRUCTOR(ReadBuffer);
+ explicit ReadBuffer(const void* data, std::size_t size);
+ explicit ReadBuffer(const ENetPacket* packet);
+ explicit ReadBuffer(PHYSFS_File* file);
+ virtual ~ReadBuffer(void) = default;
+
+ std::size_t size(void) const;
+ const std::byte* data(void) const;
+
+ void reset(const void* data, std::size_t size);
+ void reset(const ENetPacket* packet);
+ void reset(PHYSFS_File* file);
+
+ float read_FP32(void);
+ std::uint8_t read_UI8(void);
+ std::uint16_t read_UI16(void);
+ std::uint32_t read_UI32(void);
+ std::uint64_t read_UI64(void);
+ std::string read_string(void);
+
+ inline std::int8_t read_I8(void);
+ inline std::int16_t read_I16(void);
+ inline std::int32_t read_I32(void);
+ inline std::int64_t read_I64(void);
+
+ inline ReadBuffer& operator>>(float& value);
+ inline ReadBuffer& operator>>(std::int8_t& value);
+ inline ReadBuffer& operator>>(std::int16_t& value);
+ inline ReadBuffer& operator>>(std::int32_t& value);
+ inline ReadBuffer& operator>>(std::int64_t& value);
+ inline ReadBuffer& operator>>(std::uint8_t& value);
+ inline ReadBuffer& operator>>(std::uint16_t& value);
+ inline ReadBuffer& operator>>(std::uint32_t& value);
+ inline ReadBuffer& operator>>(std::uint64_t& value);
+ inline ReadBuffer& operator>>(std::string& value);
+
+private:
+ std::vector<std::byte> m_vector;
+ std::size_t m_position;
+};
+
+class WriteBuffer final {
+public:
+ DECLARE_DEFAULT_CONSTRUCTOR(WriteBuffer);
+ virtual ~WriteBuffer(void) = default;
+
+ std::size_t size(void) const;
+ const std::byte* data(void) const;
+
+ void reset(void);
+
+ void write_FP32(float value);
+ void write_UI8(std::uint8_t value);
+ void write_UI16(std::uint16_t value);
+ void write_UI32(std::uint32_t value);
+ void write_UI64(std::uint64_t value);
+ void write_string(const std::string& value);
+
+ inline void write_I8(std::int8_t value);
+ inline void write_I16(std::int16_t value);
+ inline void write_I32(std::int32_t value);
+ inline void write_I64(std::int64_t value);
+
+ inline WriteBuffer& operator<<(float value);
+ inline WriteBuffer& operator<<(std::int8_t value);
+ inline WriteBuffer& operator<<(std::int16_t value);
+ inline WriteBuffer& operator<<(std::int32_t value);
+ inline WriteBuffer& operator<<(std::int64_t value);
+ inline WriteBuffer& operator<<(std::uint8_t value);
+ inline WriteBuffer& operator<<(std::uint16_t value);
+ inline WriteBuffer& operator<<(std::uint32_t value);
+ inline WriteBuffer& operator<<(std::uint64_t value);
+ inline WriteBuffer& operator<<(const std::string& value);
+
+ PHYSFS_File* to_file(const char* path, bool append = false) const;
+ ENetPacket* to_packet(enet_uint32 flags = ENET_PACKET_FLAG_RELIABLE) const;
+
+private:
+ std::vector<std::byte> m_vector;
+};
+
+inline std::int8_t ReadBuffer::read_I8(void)
+{
+ return static_cast<std::int8_t>(read_UI8());
+}
+
+inline std::int16_t ReadBuffer::read_I16(void)
+{
+ return static_cast<std::int16_t>(read_UI16());
+}
+
+inline std::int32_t ReadBuffer::read_I32(void)
+{
+ return static_cast<std::int32_t>(read_UI32());
+}
+
+inline std::int64_t ReadBuffer::read_I64(void)
+{
+ return static_cast<std::int64_t>(read_UI64());
+}
+
+inline ReadBuffer& ReadBuffer::operator>>(float& value)
+{
+ value = read_FP32();
+ return *this;
+}
+
+inline ReadBuffer& ReadBuffer::operator>>(std::int8_t& value)
+{
+ value = read_I8();
+ return *this;
+}
+
+inline ReadBuffer& ReadBuffer::operator>>(std::int16_t& value)
+{
+ value = read_I16();
+ return *this;
+}
+
+inline ReadBuffer& ReadBuffer::operator>>(std::int32_t& value)
+{
+ value = read_I32();
+ return *this;
+}
+
+inline ReadBuffer& ReadBuffer::operator>>(std::int64_t& value)
+{
+ value = read_I64();
+ return *this;
+}
+
+inline ReadBuffer& ReadBuffer::operator>>(std::uint8_t& value)
+{
+ value = read_UI8();
+ return *this;
+}
+
+inline ReadBuffer& ReadBuffer::operator>>(std::uint16_t& value)
+{
+ value = read_UI16();
+ return *this;
+}
+
+inline ReadBuffer& ReadBuffer::operator>>(std::uint32_t& value)
+{
+ value = read_UI32();
+ return *this;
+}
+
+inline ReadBuffer& ReadBuffer::operator>>(std::uint64_t& value)
+{
+ value = read_UI64();
+ return *this;
+}
+
+inline ReadBuffer& ReadBuffer::operator>>(std::string& value)
+{
+ value = read_string();
+ return *this;
+}
+
+inline void WriteBuffer::write_FP32(float value)
+{
+ write_UI32(floathacks::float_to_uint32(value));
+}
+
+inline void WriteBuffer::write_I8(std::int8_t value)
+{
+ write_UI8(static_cast<std::uint8_t>(value));
+}
+
+inline void WriteBuffer::write_I16(std::int16_t value)
+{
+ write_UI16(static_cast<std::uint16_t>(value));
+}
+
+inline void WriteBuffer::write_I32(std::int32_t value)
+{
+ write_UI32(static_cast<std::uint32_t>(value));
+}
+
+inline void WriteBuffer::write_I64(std::int64_t value)
+{
+ write_UI64(static_cast<std::uint64_t>(value));
+}
+
+inline WriteBuffer& WriteBuffer::operator<<(float value)
+{
+ write_FP32(value);
+ return *this;
+}
+
+inline WriteBuffer& WriteBuffer::operator<<(std::int8_t value)
+{
+ write_I8(value);
+ return *this;
+}
+
+inline WriteBuffer& WriteBuffer::operator<<(std::int16_t value)
+{
+ write_I16(value);
+ return *this;
+}
+
+inline WriteBuffer& WriteBuffer::operator<<(std::int32_t value)
+{
+ write_I32(value);
+ return *this;
+}
+
+inline WriteBuffer& WriteBuffer::operator<<(std::int64_t value)
+{
+ write_I64(value);
+ return *this;
+}
+
+inline WriteBuffer& WriteBuffer::operator<<(std::uint8_t value)
+{
+ write_UI8(value);
+ return *this;
+}
+
+inline WriteBuffer& WriteBuffer::operator<<(std::uint16_t value)
+{
+ write_UI16(value);
+ return *this;
+}
+
+inline WriteBuffer& WriteBuffer::operator<<(std::uint32_t value)
+{
+ write_UI32(value);
+ return *this;
+}
+
+inline WriteBuffer& WriteBuffer::operator<<(std::uint64_t value)
+{
+ write_UI64(value);
+ return *this;
+}
+
+inline WriteBuffer& WriteBuffer::operator<<(const std::string& value)
+{
+ write_string(value);
+ return *this;
+}
+
+#endif /* CORE_BUFFER_HH */
diff --git a/src/core/cmdline.cc b/src/core/cmdline.cc
new file mode 100644
index 0000000..c83f9e6
--- /dev/null
+++ b/src/core/cmdline.cc
@@ -0,0 +1,81 @@
+#include "core/pch.hh"
+
+#include "core/cmdline.hh"
+
+// Valid options always start with OPTION_PREFIX, can contain
+// a bunch of OPTION_PREFIX'es inside and never end with one
+constexpr static char OPTION_PREFIX = '-';
+
+static std::unordered_map<std::string, std::string> options;
+
+static inline bool is_option_string(const std::string& string)
+{
+ if(string.find_last_of(OPTION_PREFIX) >= (string.size() - 1)) {
+ return false;
+ }
+
+ return string[0] == OPTION_PREFIX;
+}
+
+static inline std::string get_option(const std::string& string)
+{
+ std::size_t i;
+ for(i = 0; string[i] == OPTION_PREFIX; ++i)
+ ;
+ return std::string(string.cbegin() + i, string.cend());
+}
+
+void cmdline::create(int argc, char** argv)
+{
+ for(int idx = 1; idx < argc; ++idx) {
+ std::string string = argv[idx];
+
+ if(!is_option_string(string)) {
+ spdlog::warn("cmdline: non-argument at {}: {}", idx, string);
+ continue;
+ }
+
+ auto option_string = get_option(string);
+ auto next_idx = idx + 1;
+
+ if(next_idx < argc) {
+ std::string argument = argv[next_idx];
+
+ if(!is_option_string(argument)) {
+ options.insert_or_assign(option_string, argument);
+ idx = next_idx;
+ continue;
+ }
+ }
+
+ // The option is either last or has no
+ // argument (happens when there is a valid
+ // option right next to the one we're parsing)
+ options.insert_or_assign(option_string, std::string());
+ }
+}
+
+void cmdline::insert(const char* option, const char* argument)
+{
+ if(argument == nullptr) {
+ options.insert_or_assign(option, std::string());
+ } else {
+ options.insert_or_assign(option, argument);
+ }
+}
+
+const char* cmdline::get(const char* option, const char* fallback)
+{
+ auto it = options.find(option);
+
+ if(it == options.cend()) {
+ return fallback;
+ }
+
+ return it->second.c_str();
+}
+
+bool cmdline::contains(const char* option)
+{
+ return options.count(option);
+}
diff --git a/src/core/cmdline.hh b/src/core/cmdline.hh
new file mode 100644
index 0000000..8441a44
--- /dev/null
+++ b/src/core/cmdline.hh
@@ -0,0 +1,13 @@
+#ifndef CORE_CMDLINE_HH
+#define CORE_CMDLINE_HH 1
+#pragma once
+
+namespace cmdline
+{
+void create(int argc, char** argv);
+void insert(const char* option, const char* argument = nullptr);
+const char* get(const char* option, const char* fallback = nullptr);
+bool contains(const char* option);
+} // namespace cmdline
+
+#endif /* CORE_CMDLINE_HH */
diff --git a/src/core/concepts.hh b/src/core/concepts.hh
new file mode 100644
index 0000000..47b01d2
--- /dev/null
+++ b/src/core/concepts.hh
@@ -0,0 +1,15 @@
+#ifndef CORE_CONCEPTS_HH
+#define CORE_CONCEPTS_HH 1
+#pragma once
+
+namespace vx
+{
+template<typename T>
+concept Arithmetic = std::is_arithmetic_v<T>;
+template<typename T>
+concept Integer = std::is_integral_v<T>;
+template<typename T>
+concept FloatingPoint = std::is_floating_point_v<T>;
+} // namespace vx
+
+#endif /* CORE_CONCEPTS_HH */
diff --git a/src/core/config.cc b/src/core/config.cc
new file mode 100644
index 0000000..3202fb6
--- /dev/null
+++ b/src/core/config.cc
@@ -0,0 +1,191 @@
+#include "core/pch.hh"
+
+#include "core/config.hh"
+
+#include "core/cmdline.hh"
+#include "core/strtools.hh"
+#include "core/version.hh"
+
+ConfigBoolean::ConfigBoolean(bool default_value)
+{
+ m_value = default_value;
+ m_string = ConfigBoolean::to_string(default_value);
+}
+
+void ConfigBoolean::set(const char* value)
+{
+ m_value = ConfigBoolean::from_string(value);
+ m_string = ConfigBoolean::to_string(m_value);
+}
+
+const char* ConfigBoolean::get(void) const
+{
+ return m_string.c_str();
+}
+
+bool ConfigBoolean::get_value(void) const
+{
+ return m_value;
+}
+
+void ConfigBoolean::set_value(bool value)
+{
+ m_value = value;
+ m_string = ConfigBoolean::to_string(m_value);
+}
+
+const char* ConfigBoolean::to_string(bool value)
+{
+ if(value) {
+ return "true";
+ } else {
+ return "false";
+ }
+}
+
+bool ConfigBoolean::from_string(const char* value)
+{
+ if(std::strcmp(value, "false") && !std::strcmp(value, "true")) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+ConfigString::ConfigString(const char* default_value)
+{
+ m_value = default_value;
+}
+
+void ConfigString::set(const char* value)
+{
+ m_value = value;
+}
+
+const char* ConfigString::get(void) const
+{
+ return m_value.c_str();
+}
+
+void Config::load_cmdline(void)
+{
+ for(auto it : m_values) {
+ if(auto value = cmdline::get(it.first.c_str())) {
+ it.second->set(value);
+ }
+ }
+}
+
+bool Config::load_file(const char* path)
+{
+ if(auto file = PHYSFS_openRead(path)) {
+ auto source = std::string(PHYSFS_fileLength(file), char(0x00));
+ PHYSFS_readBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+
+ std::string line;
+ std::string kv_string;
+ std::istringstream stream(source);
+
+ while(std::getline(stream, line)) {
+ auto comment = line.find_first_of('#');
+
+ if(comment == std::string::npos) {
+ kv_string = strtools::trim_whitespace(line);
+ } else {
+ kv_string = strtools::trim_whitespace(line.substr(0, comment));
+ }
+
+ if(strtools::is_whitespace(kv_string)) {
+ // Ignore empty or commented out lines
+ continue;
+ }
+
+ auto separator = kv_string.find('=');
+
+ if(separator == std::string::npos) {
+ spdlog::warn("config: {}: invalid line: {}", path, line);
+ continue;
+ }
+
+ auto kv_name = strtools::trim_whitespace(kv_string.substr(0, separator));
+ auto kv_value = strtools::trim_whitespace(kv_string.substr(separator + 1));
+
+ auto kv_pair = m_values.find(kv_name);
+
+ if(kv_pair == m_values.cend()) {
+ spdlog::warn("config: {}: unknown key: {}", path, kv_name);
+ continue;
+ }
+
+ kv_pair->second->set(kv_value.c_str());
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+bool Config::save_file(const char* path) const
+{
+ std::ostringstream stream;
+
+ auto curtime = std::time(nullptr);
+
+ stream << "# Voxelius " << project_version_string << " configuration file" << std::endl;
+ stream << "# Generated at: " << std::put_time(std::gmtime(&curtime), "%Y-%m-%d %H:%M:%S %z") << std::endl << std::endl;
+
+ for(const auto& it : m_values) {
+ stream << it.first << "=";
+ stream << it.second->get();
+ stream << std::endl;
+ }
+
+ if(auto file = PHYSFS_openWrite(path)) {
+ auto source = stream.str();
+ PHYSFS_writeBytes(file, source.data(), source.size());
+ PHYSFS_close(file);
+ return true;
+ }
+
+ return false;
+}
+
+bool Config::set_value(const char* name, const char* value)
+{
+ auto kv_pair = m_values.find(name);
+
+ if(kv_pair != m_values.cend()) {
+ kv_pair->second->set(value);
+ return true;
+ }
+
+ return false;
+}
+
+const char* Config::get_value(const char* name) const
+{
+ auto kv_pair = m_values.find(name);
+ if(kv_pair != m_values.cend()) {
+ return kv_pair->second->get();
+ } else {
+ return nullptr;
+ }
+}
+
+void Config::add_value(const char* name, IConfigValue& vref)
+{
+ m_values.insert_or_assign(name, &vref);
+}
+
+const IConfigValue* Config::find(const char* name) const
+{
+ auto kv_pair = m_values.find(name);
+
+ if(kv_pair != m_values.cend()) {
+ return kv_pair->second;
+ } else {
+ return nullptr;
+ }
+}
diff --git a/src/core/config.hh b/src/core/config.hh
new file mode 100644
index 0000000..a7f8500
--- /dev/null
+++ b/src/core/config.hh
@@ -0,0 +1,182 @@
+#ifndef CORE_CONFIG_HH
+#define CORE_CONFIG_HH 1
+#pragma once
+
+#include "core/concepts.hh"
+#include "core/macros.hh"
+
+class IConfigValue {
+public:
+ virtual ~IConfigValue(void) = default;
+ virtual void set(const char* value) = 0;
+ virtual const char* get(void) const = 0;
+};
+
+class ConfigBoolean final : public IConfigValue {
+public:
+ explicit ConfigBoolean(bool default_value = false);
+ virtual ~ConfigBoolean(void) = default;
+
+ virtual void set(const char* value) override;
+ virtual const char* get(void) const override;
+
+ bool get_value(void) const;
+ void set_value(bool value);
+
+private:
+ bool m_value;
+ std::string m_string;
+
+public:
+ static const char* to_string(bool value);
+ static bool from_string(const char* value);
+};
+
+template<vx::Arithmetic T>
+class ConfigNumber : public IConfigValue {
+public:
+ explicit ConfigNumber(T default_value = T(0));
+ explicit ConfigNumber(T default_value, T min_value, T max_value);
+ virtual ~ConfigNumber(void) = default;
+
+ virtual void set(const char* value) override;
+ virtual const char* get(void) const override;
+
+ T get_value(void) const;
+ void set_value(T value);
+
+ T get_min_value(void) const;
+ T get_max_value(void) const;
+ void set_limits(T min_value, T max_value);
+
+private:
+ T m_value;
+ T m_min_value;
+ T m_max_value;
+ std::string m_string;
+};
+
+class ConfigInt final : public ConfigNumber<int> {
+public:
+ using ConfigNumber<int>::ConfigNumber;
+};
+
+class ConfigFloat final : public ConfigNumber<float> {
+public:
+ using ConfigNumber<float>::ConfigNumber;
+};
+
+class ConfigUnsigned final : public ConfigNumber<unsigned int> {
+public:
+ using ConfigNumber<unsigned int>::ConfigNumber;
+};
+
+class ConfigUnsigned64 final : public ConfigNumber<std::uint64_t> {
+public:
+ using ConfigNumber<std::uint64_t>::ConfigNumber;
+};
+
+class ConfigSizeType final : public ConfigNumber<std::size_t> {
+public:
+ using ConfigNumber<std::size_t>::ConfigNumber;
+};
+
+class ConfigString final : public IConfigValue {
+public:
+ explicit ConfigString(const char* default_value);
+ virtual ~ConfigString(void) = default;
+
+ virtual void set(const char* value) override;
+ virtual const char* get(void) const override;
+
+private:
+ std::string m_value;
+};
+
+class Config final {
+public:
+ DECLARE_DEFAULT_CONSTRUCTOR(Config);
+ virtual ~Config(void) = default;
+
+ void load_cmdline(void);
+ bool load_file(const char* path);
+ bool save_file(const char* path) const;
+
+ bool set_value(const char* name, const char* value);
+ const char* get_value(const char* name) const;
+
+ void add_value(const char* name, IConfigValue& vref);
+
+ const IConfigValue* find(const char* name) const;
+
+private:
+ std::unordered_map<std::string, IConfigValue*> m_values;
+};
+
+template<vx::Arithmetic T>
+inline ConfigNumber<T>::ConfigNumber(T default_value)
+{
+ m_value = default_value;
+ m_min_value = std::numeric_limits<T>::min();
+ m_max_value = std::numeric_limits<T>::max();
+ m_string = std::to_string(default_value);
+}
+
+template<vx::Arithmetic T>
+inline ConfigNumber<T>::ConfigNumber(T default_value, T min_value, T max_value)
+{
+ m_value = default_value;
+ m_min_value = min_value;
+ m_max_value = max_value;
+ m_string = std::to_string(default_value);
+}
+
+template<vx::Arithmetic T>
+inline void ConfigNumber<T>::set(const char* value)
+{
+ std::istringstream(value) >> m_value;
+ m_value = std::clamp(m_value, m_min_value, m_max_value);
+ m_string = std::to_string(m_value);
+}
+
+template<vx::Arithmetic T>
+inline const char* ConfigNumber<T>::get(void) const
+{
+ return m_string.c_str();
+}
+
+template<vx::Arithmetic T>
+inline T ConfigNumber<T>::get_value(void) const
+{
+ return m_value;
+}
+
+template<vx::Arithmetic T>
+inline void ConfigNumber<T>::set_value(T value)
+{
+ m_value = std::clamp(value, m_min_value, m_max_value);
+ m_string = std::to_string(m_value);
+}
+
+template<vx::Arithmetic T>
+inline T ConfigNumber<T>::get_min_value(void) const
+{
+ return m_min_value;
+}
+
+template<vx::Arithmetic T>
+inline T ConfigNumber<T>::get_max_value(void) const
+{
+ return m_max_value;
+}
+
+template<vx::Arithmetic T>
+inline void ConfigNumber<T>::set_limits(T min_value, T max_value)
+{
+ m_min_value = min_value;
+ m_max_value = max_value;
+ m_value = std::clamp(m_value, m_min_value, m_max_value);
+ m_string = std::to_string(m_value);
+}
+
+#endif /* CORE_CONFIG_HH */
diff --git a/src/core/constexpr.hh b/src/core/constexpr.hh
new file mode 100644
index 0000000..18c83c0
--- /dev/null
+++ b/src/core/constexpr.hh
@@ -0,0 +1,191 @@
+#ifndef CORE_CONSTEXPR_HH
+#define CORE_CONSTEXPR_HH 1
+#pragma once
+
+#include "core/concepts.hh"
+
+namespace vx
+{
+template<vx::Arithmetic T>
+constexpr static inline const T abs(const T x);
+template<typename T, std::size_t L>
+constexpr static inline const std::size_t array_size(const T (&)[L]);
+template<vx::Integer T, vx::FloatingPoint F>
+constexpr static inline const T ceil(const F x);
+template<vx::Arithmetic T>
+constexpr static inline const T degrees(const T x);
+template<vx::Integer T, vx::FloatingPoint F>
+constexpr static inline const T floor(const F x);
+template<vx::Arithmetic T>
+constexpr static inline const T clamp(const T x, const T min, const T max);
+template<vx::Arithmetic T, vx::FloatingPoint F>
+constexpr static inline const T lerp(const T x, const T y, const F a);
+template<vx::Arithmetic T>
+constexpr static inline const T log2(const T x);
+template<vx::Arithmetic T>
+constexpr static inline const T max(const T x, const T y);
+template<vx::Arithmetic T>
+constexpr static inline const T min(const T x, const T y);
+template<vx::Integer T>
+requires std::is_signed_v<T>
+constexpr static inline const T mod_signed(const T x, const T m);
+template<vx::Arithmetic T>
+constexpr static inline const T pow2(const T x);
+template<vx::Arithmetic T>
+constexpr static inline const T radians(const T x);
+template<vx::Arithmetic T>
+constexpr static inline const bool range(const T x, const T min, const T max);
+template<vx::Arithmetic T, vx::FloatingPoint F>
+constexpr static inline const T sign(const F x);
+template<vx::Arithmetic T, vx::FloatingPoint F>
+constexpr static inline const T smoothstep(const T x, const T y, const F a);
+} // namespace vx
+
+template<vx::Arithmetic T>
+constexpr static inline const T vx::abs(const T x)
+{
+ if(x < static_cast<T>(0)) {
+ return -x;
+ } else {
+ return x;
+ }
+}
+
+template<typename T, std::size_t L>
+constexpr static inline const std::size_t vx::array_size(const T (&)[L])
+{
+ return L;
+}
+
+template<vx::Integer T, vx::FloatingPoint F>
+constexpr static inline const T vx::ceil(const F x)
+{
+ const T ival = static_cast<T>(x);
+
+ if(ival < x) {
+ return ival + static_cast<T>(1);
+ } else {
+ return ival;
+ }
+}
+
+template<vx::Arithmetic T>
+constexpr static inline const T vx::degrees(const T x)
+{
+ return x * static_cast<T>(180.0) / static_cast<T>(M_PI);
+}
+
+template<vx::Integer T, vx::FloatingPoint F>
+constexpr static inline const T vx::floor(const F x)
+{
+ const T ival = static_cast<T>(x);
+
+ if(ival > x) {
+ return ival - static_cast<T>(1);
+ } else {
+ return ival;
+ }
+}
+
+template<vx::Arithmetic T>
+constexpr static inline const T vx::clamp(const T x, const T min, const T max)
+{
+ if(x < min) {
+ return min;
+ } else if(x > max) {
+ return max;
+ } else {
+ return x;
+ }
+}
+
+template<vx::Arithmetic T, vx::FloatingPoint F>
+constexpr static inline const T vx::lerp(const T x, const T y, const F a)
+{
+ return static_cast<T>(static_cast<F>(x) * (static_cast<F>(1.0f) - a) + static_cast<F>(y) * a);
+}
+
+template<vx::Arithmetic T>
+constexpr static inline const T vx::log2(const T x)
+{
+ if(x < 2) {
+ return 0;
+ } else {
+ return vx::log2<T>((x + 1) >> 1) + 1;
+ }
+}
+
+template<vx::Arithmetic T>
+constexpr static inline const T vx::max(const T x, const T y)
+{
+ if(x < y) {
+ return y;
+ } else {
+ return x;
+ }
+}
+
+template<vx::Arithmetic T>
+constexpr static inline const T vx::min(const T x, const T y)
+{
+ if(x > y) {
+ return y;
+ } else {
+ return x;
+ }
+}
+
+template<vx::Integer T>
+requires std::is_signed_v<T>
+constexpr static inline const T vx::mod_signed(const T x, const T m)
+{
+ auto result = static_cast<T>(x % m);
+
+ if(result < T(0)) {
+ return result + m;
+ } else {
+ return result;
+ }
+}
+
+template<vx::Arithmetic T>
+constexpr static inline const T vx::pow2(const T x)
+{
+ T value = static_cast<T>(1);
+ while(value < x)
+ value *= static_cast<T>(2);
+ return value;
+}
+
+template<vx::Arithmetic T>
+constexpr static inline const T vx::radians(const T x)
+{
+ return x * static_cast<T>(M_PI) / static_cast<T>(180.0);
+}
+
+template<vx::Arithmetic T>
+constexpr static inline const bool vx::range(const T x, const T min, const T max)
+{
+ return ((x >= min) && (x <= max));
+}
+
+template<vx::Arithmetic T, vx::FloatingPoint F>
+constexpr static inline const T vx::sign(const F x)
+{
+ if(x < F(0)) {
+ return T(-1);
+ } else if(x > F(0)) {
+ return T(+1);
+ } else {
+ return T(0);
+ }
+}
+
+template<vx::Arithmetic T, vx::FloatingPoint F>
+constexpr static inline const T vx::smoothstep(const T x, const T y, const F a)
+{
+ const F t = vx::clamp<F>((a - x) / (y - x), F(0), F(1));
+ return static_cast<T>(t * t * (F(3) - F(2) * t));
+}
+
+#endif /* CORE_CONSTEXPR_HH */
diff --git a/src/core/crc64.cc b/src/core/crc64.cc
new file mode 100644
index 0000000..c2ca53c
--- /dev/null
+++ b/src/core/crc64.cc
@@ -0,0 +1,286 @@
+#include "core/pch.hh"
+
+#include "core/crc64.hh"
+
+// The lookup table for CRC64 checksum; this lookup
+// table is generated using ECMA-182 compilant parameters:
+// - Polynomial: `0x42F0E1EBA9EA3693`
+// - Initial value: `0x0000000000000000`
+// - Final xor: `0x0000000000000000`
+// CRC Calculator: https://www.sunshine2k.de/coding/javascript/crc/crc_js.html
+constexpr static const std::uint64_t crc_table[256] = {
+ 0x0000000000000000,
+ 0x42F0E1EBA9EA3693,
+ 0x85E1C3D753D46D26,
+ 0xC711223CFA3E5BB5,
+ 0x493366450E42ECDF,
+ 0x0BC387AEA7A8DA4C,
+ 0xCCD2A5925D9681F9,
+ 0x8E224479F47CB76A,
+ 0x9266CC8A1C85D9BE,
+ 0xD0962D61B56FEF2D,
+ 0x17870F5D4F51B498,
+ 0x5577EEB6E6BB820B,
+ 0xDB55AACF12C73561,
+ 0x99A54B24BB2D03F2,
+ 0x5EB4691841135847,
+ 0x1C4488F3E8F96ED4,
+ 0x663D78FF90E185EF,
+ 0x24CD9914390BB37C,
+ 0xE3DCBB28C335E8C9,
+ 0xA12C5AC36ADFDE5A,
+ 0x2F0E1EBA9EA36930,
+ 0x6DFEFF5137495FA3,
+ 0xAAEFDD6DCD770416,
+ 0xE81F3C86649D3285,
+ 0xF45BB4758C645C51,
+ 0xB6AB559E258E6AC2,
+ 0x71BA77A2DFB03177,
+ 0x334A9649765A07E4,
+ 0xBD68D2308226B08E,
+ 0xFF9833DB2BCC861D,
+ 0x388911E7D1F2DDA8,
+ 0x7A79F00C7818EB3B,
+ 0xCC7AF1FF21C30BDE,
+ 0x8E8A101488293D4D,
+ 0x499B3228721766F8,
+ 0x0B6BD3C3DBFD506B,
+ 0x854997BA2F81E701,
+ 0xC7B97651866BD192,
+ 0x00A8546D7C558A27,
+ 0x4258B586D5BFBCB4,
+ 0x5E1C3D753D46D260,
+ 0x1CECDC9E94ACE4F3,
+ 0xDBFDFEA26E92BF46,
+ 0x990D1F49C77889D5,
+ 0x172F5B3033043EBF,
+ 0x55DFBADB9AEE082C,
+ 0x92CE98E760D05399,
+ 0xD03E790CC93A650A,
+ 0xAA478900B1228E31,
+ 0xE8B768EB18C8B8A2,
+ 0x2FA64AD7E2F6E317,
+ 0x6D56AB3C4B1CD584,
+ 0xE374EF45BF6062EE,
+ 0xA1840EAE168A547D,
+ 0x66952C92ECB40FC8,
+ 0x2465CD79455E395B,
+ 0x3821458AADA7578F,
+ 0x7AD1A461044D611C,
+ 0xBDC0865DFE733AA9,
+ 0xFF3067B657990C3A,
+ 0x711223CFA3E5BB50,
+ 0x33E2C2240A0F8DC3,
+ 0xF4F3E018F031D676,
+ 0xB60301F359DBE0E5,
+ 0xDA050215EA6C212F,
+ 0x98F5E3FE438617BC,
+ 0x5FE4C1C2B9B84C09,
+ 0x1D14202910527A9A,
+ 0x93366450E42ECDF0,
+ 0xD1C685BB4DC4FB63,
+ 0x16D7A787B7FAA0D6,
+ 0x5427466C1E109645,
+ 0x4863CE9FF6E9F891,
+ 0x0A932F745F03CE02,
+ 0xCD820D48A53D95B7,
+ 0x8F72ECA30CD7A324,
+ 0x0150A8DAF8AB144E,
+ 0x43A04931514122DD,
+ 0x84B16B0DAB7F7968,
+ 0xC6418AE602954FFB,
+ 0xBC387AEA7A8DA4C0,
+ 0xFEC89B01D3679253,
+ 0x39D9B93D2959C9E6,
+ 0x7B2958D680B3FF75,
+ 0xF50B1CAF74CF481F,
+ 0xB7FBFD44DD257E8C,
+ 0x70EADF78271B2539,
+ 0x321A3E938EF113AA,
+ 0x2E5EB66066087D7E,
+ 0x6CAE578BCFE24BED,
+ 0xABBF75B735DC1058,
+ 0xE94F945C9C3626CB,
+ 0x676DD025684A91A1,
+ 0x259D31CEC1A0A732,
+ 0xE28C13F23B9EFC87,
+ 0xA07CF2199274CA14,
+ 0x167FF3EACBAF2AF1,
+ 0x548F120162451C62,
+ 0x939E303D987B47D7,
+ 0xD16ED1D631917144,
+ 0x5F4C95AFC5EDC62E,
+ 0x1DBC74446C07F0BD,
+ 0xDAAD56789639AB08,
+ 0x985DB7933FD39D9B,
+ 0x84193F60D72AF34F,
+ 0xC6E9DE8B7EC0C5DC,
+ 0x01F8FCB784FE9E69,
+ 0x43081D5C2D14A8FA,
+ 0xCD2A5925D9681F90,
+ 0x8FDAB8CE70822903,
+ 0x48CB9AF28ABC72B6,
+ 0x0A3B7B1923564425,
+ 0x70428B155B4EAF1E,
+ 0x32B26AFEF2A4998D,
+ 0xF5A348C2089AC238,
+ 0xB753A929A170F4AB,
+ 0x3971ED50550C43C1,
+ 0x7B810CBBFCE67552,
+ 0xBC902E8706D82EE7,
+ 0xFE60CF6CAF321874,
+ 0xE224479F47CB76A0,
+ 0xA0D4A674EE214033,
+ 0x67C58448141F1B86,
+ 0x253565A3BDF52D15,
+ 0xAB1721DA49899A7F,
+ 0xE9E7C031E063ACEC,
+ 0x2EF6E20D1A5DF759,
+ 0x6C0603E6B3B7C1CA,
+ 0xF6FAE5C07D3274CD,
+ 0xB40A042BD4D8425E,
+ 0x731B26172EE619EB,
+ 0x31EBC7FC870C2F78,
+ 0xBFC9838573709812,
+ 0xFD39626EDA9AAE81,
+ 0x3A28405220A4F534,
+ 0x78D8A1B9894EC3A7,
+ 0x649C294A61B7AD73,
+ 0x266CC8A1C85D9BE0,
+ 0xE17DEA9D3263C055,
+ 0xA38D0B769B89F6C6,
+ 0x2DAF4F0F6FF541AC,
+ 0x6F5FAEE4C61F773F,
+ 0xA84E8CD83C212C8A,
+ 0xEABE6D3395CB1A19,
+ 0x90C79D3FEDD3F122,
+ 0xD2377CD44439C7B1,
+ 0x15265EE8BE079C04,
+ 0x57D6BF0317EDAA97,
+ 0xD9F4FB7AE3911DFD,
+ 0x9B041A914A7B2B6E,
+ 0x5C1538ADB04570DB,
+ 0x1EE5D94619AF4648,
+ 0x02A151B5F156289C,
+ 0x4051B05E58BC1E0F,
+ 0x87409262A28245BA,
+ 0xC5B073890B687329,
+ 0x4B9237F0FF14C443,
+ 0x0962D61B56FEF2D0,
+ 0xCE73F427ACC0A965,
+ 0x8C8315CC052A9FF6,
+ 0x3A80143F5CF17F13,
+ 0x7870F5D4F51B4980,
+ 0xBF61D7E80F251235,
+ 0xFD913603A6CF24A6,
+ 0x73B3727A52B393CC,
+ 0x31439391FB59A55F,
+ 0xF652B1AD0167FEEA,
+ 0xB4A25046A88DC879,
+ 0xA8E6D8B54074A6AD,
+ 0xEA16395EE99E903E,
+ 0x2D071B6213A0CB8B,
+ 0x6FF7FA89BA4AFD18,
+ 0xE1D5BEF04E364A72,
+ 0xA3255F1BE7DC7CE1,
+ 0x64347D271DE22754,
+ 0x26C49CCCB40811C7,
+ 0x5CBD6CC0CC10FAFC,
+ 0x1E4D8D2B65FACC6F,
+ 0xD95CAF179FC497DA,
+ 0x9BAC4EFC362EA149,
+ 0x158E0A85C2521623,
+ 0x577EEB6E6BB820B0,
+ 0x906FC95291867B05,
+ 0xD29F28B9386C4D96,
+ 0xCEDBA04AD0952342,
+ 0x8C2B41A1797F15D1,
+ 0x4B3A639D83414E64,
+ 0x09CA82762AAB78F7,
+ 0x87E8C60FDED7CF9D,
+ 0xC51827E4773DF90E,
+ 0x020905D88D03A2BB,
+ 0x40F9E43324E99428,
+ 0x2CFFE7D5975E55E2,
+ 0x6E0F063E3EB46371,
+ 0xA91E2402C48A38C4,
+ 0xEBEEC5E96D600E57,
+ 0x65CC8190991CB93D,
+ 0x273C607B30F68FAE,
+ 0xE02D4247CAC8D41B,
+ 0xA2DDA3AC6322E288,
+ 0xBE992B5F8BDB8C5C,
+ 0xFC69CAB42231BACF,
+ 0x3B78E888D80FE17A,
+ 0x7988096371E5D7E9,
+ 0xF7AA4D1A85996083,
+ 0xB55AACF12C735610,
+ 0x724B8ECDD64D0DA5,
+ 0x30BB6F267FA73B36,
+ 0x4AC29F2A07BFD00D,
+ 0x08327EC1AE55E69E,
+ 0xCF235CFD546BBD2B,
+ 0x8DD3BD16FD818BB8,
+ 0x03F1F96F09FD3CD2,
+ 0x41011884A0170A41,
+ 0x86103AB85A2951F4,
+ 0xC4E0DB53F3C36767,
+ 0xD8A453A01B3A09B3,
+ 0x9A54B24BB2D03F20,
+ 0x5D45907748EE6495,
+ 0x1FB5719CE1045206,
+ 0x919735E51578E56C,
+ 0xD367D40EBC92D3FF,
+ 0x1476F63246AC884A,
+ 0x568617D9EF46BED9,
+ 0xE085162AB69D5E3C,
+ 0xA275F7C11F7768AF,
+ 0x6564D5FDE549331A,
+ 0x279434164CA30589,
+ 0xA9B6706FB8DFB2E3,
+ 0xEB46918411358470,
+ 0x2C57B3B8EB0BDFC5,
+ 0x6EA7525342E1E956,
+ 0x72E3DAA0AA188782,
+ 0x30133B4B03F2B111,
+ 0xF7021977F9CCEAA4,
+ 0xB5F2F89C5026DC37,
+ 0x3BD0BCE5A45A6B5D,
+ 0x79205D0E0DB05DCE,
+ 0xBE317F32F78E067B,
+ 0xFCC19ED95E6430E8,
+ 0x86B86ED5267CDBD3,
+ 0xC4488F3E8F96ED40,
+ 0x0359AD0275A8B6F5,
+ 0x41A94CE9DC428066,
+ 0xCF8B0890283E370C,
+ 0x8D7BE97B81D4019F,
+ 0x4A6ACB477BEA5A2A,
+ 0x089A2AACD2006CB9,
+ 0x14DEA25F3AF9026D,
+ 0x562E43B4931334FE,
+ 0x913F6188692D6F4B,
+ 0xD3CF8063C0C759D8,
+ 0x5DEDC41A34BBEEB2,
+ 0x1F1D25F19D51D821,
+ 0xD80C07CD676F8394,
+ 0x9AFCE626CE85B507,
+};
+
+std::uint64_t crc64::get(const void* buffer, std::size_t size, std::uint64_t combine)
+{
+ auto data = reinterpret_cast<const std::uint8_t*>(buffer);
+ for(std::size_t i = 0; i < size; ++i)
+ combine = crc_table[((combine >> 56) ^ data[i]) & 0xFF] ^ (combine << 8);
+ return combine;
+}
+
+std::uint64_t crc64::get(const std::vector<std::byte>& buffer, std::uint64_t combine)
+{
+ return crc64::get(buffer.data(), buffer.size(), combine);
+}
+
+std::uint64_t crc64::get(const std::string& buffer, std::uint64_t combine)
+{
+ return crc64::get(buffer.data(), buffer.size(), combine);
+}
diff --git a/src/core/crc64.hh b/src/core/crc64.hh
new file mode 100644
index 0000000..da4ad2c
--- /dev/null
+++ b/src/core/crc64.hh
@@ -0,0 +1,12 @@
+#ifndef CORE_CRC64_HH
+#define CORE_CRC64_HH 1
+#pragma once
+
+namespace crc64
+{
+std::uint64_t get(const void* buffer, std::size_t size, std::uint64_t combine = UINT64_C(0));
+std::uint64_t get(const std::vector<std::byte>& buffer, std::uint64_t combine = UINT64_C(0));
+std::uint64_t get(const std::string& buffer, std::uint64_t combine = UINT64_C(0));
+} // namespace crc64
+
+#endif /* CORE_CRC64_HH */
diff --git a/src/core/epoch.cc b/src/core/epoch.cc
new file mode 100644
index 0000000..978eeb3
--- /dev/null
+++ b/src/core/epoch.cc
@@ -0,0 +1,39 @@
+#include "core/pch.hh"
+
+#include "core/epoch.hh"
+
+std::uint64_t epoch::seconds(void)
+{
+ const auto elapsed = std::chrono::system_clock::now().time_since_epoch();
+ return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::seconds>(elapsed).count());
+}
+
+std::uint64_t epoch::milliseconds(void)
+{
+ const auto elapsed = std::chrono::system_clock::now().time_since_epoch();
+ return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count());
+}
+
+std::uint64_t epoch::microseconds(void)
+{
+ const auto elapsed = std::chrono::system_clock::now().time_since_epoch();
+ return static_cast<std::uint64_t>(std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count());
+}
+
+std::int64_t epoch::signed_seconds(void)
+{
+ const auto elapsed = std::chrono::system_clock::now().time_since_epoch();
+ return static_cast<std::int64_t>(std::chrono::duration_cast<std::chrono::seconds>(elapsed).count());
+}
+
+std::int64_t epoch::signed_milliseconds(void)
+{
+ const auto elapsed = std::chrono::system_clock::now().time_since_epoch();
+ return static_cast<std::int64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count());
+}
+
+std::int64_t epoch::signed_microseconds(void)
+{
+ const auto elapsed = std::chrono::system_clock::now().time_since_epoch();
+ return static_cast<std::int64_t>(std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count());
+}
diff --git a/src/core/epoch.hh b/src/core/epoch.hh
new file mode 100644
index 0000000..f590f27
--- /dev/null
+++ b/src/core/epoch.hh
@@ -0,0 +1,19 @@
+#ifndef CORE_EPOCH_HH
+#define CORE_EPOCH_HH 1
+#pragma once
+
+namespace epoch
+{
+std::uint64_t seconds(void);
+std::uint64_t milliseconds(void);
+std::uint64_t microseconds(void);
+} // namespace epoch
+
+namespace epoch
+{
+std::int64_t signed_seconds(void);
+std::int64_t signed_milliseconds(void);
+std::int64_t signed_microseconds(void);
+} // namespace epoch
+
+#endif /* CORE_EPOCH_HH */
diff --git a/src/core/floathacks.hh b/src/core/floathacks.hh
new file mode 100644
index 0000000..29b7cac
--- /dev/null
+++ b/src/core/floathacks.hh
@@ -0,0 +1,56 @@
+#ifndef CORE_FLOATHACKS_HH
+#define CORE_FLOATHACKS_HH 1
+#pragma once
+
+namespace floathacks
+{
+static inline float int32_to_float(const std::int32_t value);
+static inline float uint32_to_float(const std::uint32_t value);
+static inline std::int32_t float_to_int32(const float value);
+static inline std::uint32_t float_to_uint32(const float value);
+} // namespace floathacks
+
+static_assert(std::numeric_limits<float>::is_iec559, "Floathacks only works with IEEE 754 compliant floats");
+static_assert(sizeof(std::int32_t) == sizeof(float), "Floathacks requires 32-bit integers to match float size");
+
+static inline float floathacks::int32_to_float(const std::int32_t value)
+{
+ union {
+ std::int32_t src;
+ float dst;
+ } hack;
+ hack.src = value;
+ return hack.dst;
+}
+
+static inline float floathacks::uint32_to_float(const std::uint32_t value)
+{
+ union {
+ std::uint32_t src;
+ float dst;
+ } hack;
+ hack.src = value;
+ return hack.dst;
+}
+
+static inline std::int32_t floathacks::float_to_int32(const float value)
+{
+ union {
+ float src;
+ std::int32_t dst;
+ } hack;
+ hack.src = value;
+ return hack.dst;
+}
+
+static inline std::uint32_t floathacks::float_to_uint32(const float value)
+{
+ union {
+ float src;
+ std::uint32_t dst;
+ } hack;
+ hack.src = value;
+ return hack.dst;
+}
+
+#endif /* CORE_FLOATHACKS_HH */
diff --git a/src/core/image.cc b/src/core/image.cc
new file mode 100644
index 0000000..08be3d4
--- /dev/null
+++ b/src/core/image.cc
@@ -0,0 +1,112 @@
+#include "core/pch.hh"
+
+#include "core/image.hh"
+
+#include "core/resource.hh"
+
+static emhash8::HashMap<std::string, resource_ptr<Image>> resource_map;
+
+static int stbi_physfs_read(void* context, char* data, int size)
+{
+ return PHYSFS_readBytes(reinterpret_cast<PHYSFS_File*>(context), data, size);
+}
+
+static void stbi_physfs_skip(void* context, int count)
+{
+ auto file = reinterpret_cast<PHYSFS_File*>(context);
+ PHYSFS_seek(file, PHYSFS_tell(file) + count);
+}
+
+static int stbi_physfs_eof(void* context)
+{
+ return PHYSFS_eof(reinterpret_cast<PHYSFS_File*>(context));
+}
+
+template<>
+resource_ptr<Image> resource::load<Image>(const char* name, unsigned int flags)
+{
+ auto it = resource_map.find(name);
+
+ if(it != resource_map.cend()) {
+ // Return an existing resource
+ return it->second;
+ }
+
+ auto file = PHYSFS_openRead(name);
+
+ if(file == nullptr) {
+ spdlog::warn("resource: {}: {}", name, PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
+ return nullptr;
+ }
+
+ if(flags & IMAGE_LOAD_FLIP) {
+ stbi_set_flip_vertically_on_load(true);
+ } else {
+ stbi_set_flip_vertically_on_load(false);
+ }
+
+ stbi_io_callbacks callbacks;
+ callbacks.read = &stbi_physfs_read;
+ callbacks.skip = &stbi_physfs_skip;
+ callbacks.eof = &stbi_physfs_eof;
+
+ auto new_resource = std::make_shared<Image>();
+
+ if(flags & IMAGE_LOAD_GRAY) {
+ new_resource->pixels = stbi_load_from_callbacks(&callbacks, file, &new_resource->size.x, &new_resource->size.y, nullptr, STBI_grey);
+ } else {
+ new_resource->pixels = stbi_load_from_callbacks(
+ &callbacks, file, &new_resource->size.x, &new_resource->size.y, nullptr, STBI_rgb_alpha);
+ }
+
+ PHYSFS_close(file);
+
+ if(new_resource->pixels == nullptr) {
+ spdlog::warn("resource: {}: {}", name, stbi_failure_reason());
+ return nullptr;
+ }
+
+ if(new_resource->size.x <= 0 || new_resource->size.y <= 0) {
+ spdlog::warn("resource {}: invalid dimensions", name);
+ stbi_image_free(new_resource->pixels);
+ return nullptr;
+ }
+
+ return resource_map.insert_or_assign(name, new_resource).first->second;
+}
+
+template<>
+void resource::hard_cleanup<Image>(void)
+{
+ for(const auto& it : resource_map) {
+ if(it.second.use_count() > 1L) {
+ spdlog::warn("resource: zombie resource [Image] {} [use_count={}]", it.first, it.second.use_count());
+ } else {
+ spdlog::debug("resource: releasing [Image] {}", it.first);
+ }
+
+ stbi_image_free(it.second->pixels);
+ }
+
+ resource_map.clear();
+}
+
+template<>
+void resource::soft_cleanup<Image>(void)
+{
+ auto iter = resource_map.cbegin();
+
+ while(iter != resource_map.cend()) {
+ if(iter->second.use_count() == 1L) {
+ spdlog::debug("resource: releasing [Image] {}", iter->first);
+
+ stbi_image_free(iter->second->pixels);
+
+ iter = resource_map.erase(iter);
+
+ continue;
+ }
+
+ iter = std::next(iter);
+ }
+}
diff --git a/src/core/image.hh b/src/core/image.hh
new file mode 100644
index 0000000..92d99be
--- /dev/null
+++ b/src/core/image.hh
@@ -0,0 +1,13 @@
+#ifndef CORE_IMAGE_HH
+#define CORE_IMAGE_HH 1
+#pragma once
+
+constexpr static unsigned int IMAGE_LOAD_GRAY = 0x0001U;
+constexpr static unsigned int IMAGE_LOAD_FLIP = 0x0002U;
+
+struct Image final {
+ stbi_uc* pixels;
+ glm::ivec2 size;
+};
+
+#endif /* CORE_IMAGE_HH */
diff --git a/src/core/macros.hh b/src/core/macros.hh
new file mode 100644
index 0000000..9a76109
--- /dev/null
+++ b/src/core/macros.hh
@@ -0,0 +1,19 @@
+#ifndef CORE_MACROS_HH
+#define CORE_MACROS_HH 1
+#pragma once
+
+#define DISABLE_COPY_OPERATORS(class_name) \
+public: \
+ explicit class_name(const class_name& other) = delete; \
+ class_name& operator=(const class_name& other) = delete
+
+#define DISABLE_MOVE_OPERATORS(class_name) \
+public: \
+ explicit class_name(class_name&& other) = delete; \
+ class_name& operator=(class_name&& other) = delete
+
+#define DECLARE_DEFAULT_CONSTRUCTOR(class_name) \
+public: \
+ class_name(void) = default
+
+#endif /* CORE_MACROS_HH */
diff --git a/src/core/pch.hh b/src/core/pch.hh
new file mode 100644
index 0000000..795a287
--- /dev/null
+++ b/src/core/pch.hh
@@ -0,0 +1,50 @@
+#ifndef CORE_PCH_HH
+#define CORE_PCH_HH 1
+#pragma once
+
+#include <cinttypes>
+#include <cmath>
+#include <cstdarg>
+#include <cstddef>
+#include <cstdint>
+
+#include <algorithm>
+#include <chrono>
+#include <concepts>
+#include <filesystem>
+#include <format>
+#include <iostream>
+#include <limits>
+#include <mutex>
+#include <random>
+#include <sstream>
+#include <stdexcept>
+#include <thread>
+#include <type_traits>
+#include <unordered_map>
+#include <unordered_set>
+#include <vector>
+
+#include <emhash/hash_table8.hpp>
+
+#include <enet/enet.h>
+
+#include <glm/fwd.hpp>
+
+#include <glm/mat4x4.hpp>
+#include <glm/vec2.hpp>
+#include <glm/vec3.hpp>
+#include <glm/vec4.hpp>
+
+#include <glm/gtc/matrix_transform.hpp>
+#include <glm/gtc/quaternion.hpp>
+#include <glm/gtc/type_ptr.hpp>
+
+#include <physfs.h>
+
+#include <spdlog/spdlog.h>
+
+#include <stb_image.h>
+#include <stb_image_write.h>
+
+#endif /* CORE_PCH_HH */
diff --git a/src/core/randomizer.hh b/src/core/randomizer.hh
new file mode 100644
index 0000000..b60b839
--- /dev/null
+++ b/src/core/randomizer.hh
@@ -0,0 +1,57 @@
+#ifndef CORE_RANDOMIZER_HH
+#define CORE_RANDOMIZER_HH 1
+#pragma once
+
+template<typename T>
+class Randomizer final {
+public:
+ explicit Randomizer(void);
+ explicit Randomizer(std::uint64_t seed);
+ virtual ~Randomizer(void) = default;
+ void add(const T& value);
+ const T& get(void);
+ void clear(void);
+
+private:
+ std::vector<T> m_vector;
+ std::mt19937_64 m_twister;
+ std::uniform_int_distribution<std::size_t> m_dist;
+};
+
+template<typename T>
+inline Randomizer<T>::Randomizer(void)
+{
+ m_vector.clear();
+ m_twister.seed(std::random_device()());
+ m_dist = std::uniform_int_distribution<std::size_t>(0, 0);
+}
+
+template<typename T>
+inline Randomizer<T>::Randomizer(std::uint64_t seed)
+{
+ m_vector.clear();
+ m_twister.seed(seed);
+ m_dist = std::uniform_int_distribution<std::size_t>(0, 0);
+}
+
+template<typename T>
+inline void Randomizer<T>::add(const T& value)
+{
+ m_vector.push_back(value);
+ m_dist = std::uniform_int_distribution<std::size_t>(0, m_vector.size() - 1);
+}
+
+template<typename T>
+inline const T& Randomizer<T>::get(void)
+{
+ return m_vector.at(m_dist(m_twister));
+}
+
+template<typename T>
+inline void Randomizer<T>::clear(void)
+{
+ m_vector.clear();
+ m_dist = std::uniform_int_distribution<std::size_t>(0, 0);
+}
+
+#endif /* CORE_RANDOMIZER_HH */
diff --git a/src/core/resource.hh b/src/core/resource.hh
new file mode 100644
index 0000000..ab7b74f
--- /dev/null
+++ b/src/core/resource.hh
@@ -0,0 +1,18 @@
+#ifndef CORE_RESOURCE_HH
+#define CORE_RESOURCE_HH 1
+#pragma once
+
+template<typename T>
+using resource_ptr = std::shared_ptr<const T>;
+
+namespace resource
+{
+template<typename T>
+resource_ptr<T> load(const char* name, unsigned int flags = 0U);
+template<typename T>
+void hard_cleanup(void);
+template<typename T>
+void soft_cleanup(void);
+} // namespace resource
+
+#endif /* CORE_RESOURCE_HH */
diff --git a/src/core/strtools.cc b/src/core/strtools.cc
new file mode 100644
index 0000000..4edd86b
--- /dev/null
+++ b/src/core/strtools.cc
@@ -0,0 +1,54 @@
+#include "core/pch.hh"
+
+#include "core/strtools.hh"
+
+constexpr static const char* WHITESPACE_CHARS = " \t\r\n";
+
+bool strtools::is_whitespace(const std::string& string)
+{
+ if(string.find_first_not_of(WHITESPACE_CHARS) == std::string::npos) {
+ return true;
+ } else if((string.size() == 1) && string[0] == 0x00) {
+ return true;
+ } else {
+ return string.empty();
+ }
+}
+
+std::string strtools::join(const std::vector<std::string>& strings, const std::string& separator)
+{
+ std::ostringstream stream;
+ for(const std::string& str : strings)
+ stream << str << separator;
+ return stream.str();
+}
+
+std::vector<std::string> strtools::split(const std::string& string, const std::string& separator)
+{
+ std::size_t pos = 0;
+ std::size_t prev = 0;
+ std::vector<std::string> result;
+
+ while((pos = string.find(separator, prev)) != std::string::npos) {
+ result.push_back(string.substr(prev, pos - prev));
+ prev = pos + separator.length();
+ }
+
+ if(prev <= string.length()) {
+ result.push_back(string.substr(prev, string.length() - prev));
+ }
+
+ return result;
+}
+
+std::string strtools::trim_whitespace(const std::string& string)
+{
+ auto su = string.find_first_not_of(WHITESPACE_CHARS);
+ auto sv = string.find_last_not_of(WHITESPACE_CHARS);
+
+ if(su == std::string::npos) {
+ return std::string();
+ } else {
+ return string.substr(su, sv - su + 1);
+ }
+}
diff --git a/src/core/strtools.hh b/src/core/strtools.hh
new file mode 100644
index 0000000..1462978
--- /dev/null
+++ b/src/core/strtools.hh
@@ -0,0 +1,21 @@
+#ifndef CORE_STRTOOLS_HH
+#define CORE_STRTOOLS_HH 1
+#pragma once
+
+namespace strtools
+{
+bool is_whitespace(const std::string& string);
+} // namespace strtools
+
+namespace strtools
+{
+std::string join(const std::vector<std::string>& strings, const std::string& separator);
+std::vector<std::string> split(const std::string& string, const std::string& separator);
+} // namespace strtools
+
+namespace strtools
+{
+std::string trim_whitespace(const std::string& string);
+} // namespace strtools
+
+#endif /* CORE_STRTOOLS_HH */
diff --git a/src/core/vectors.hh b/src/core/vectors.hh
new file mode 100644
index 0000000..a6e9c75
--- /dev/null
+++ b/src/core/vectors.hh
@@ -0,0 +1,47 @@
+#ifndef CORE_VECTORS_HH
+#define CORE_VECTORS_HH 1
+#pragma once
+
+#include "core/concepts.hh"
+
+// core/vectors.hh - because NO ONE would POSSIBLY
+// need integer-based distance calculations in a
+// game about voxels. That would be INSANE! :D
+
+namespace vx
+{
+template<vx::Arithmetic T>
+constexpr static inline const T length2(const glm::vec<2, T>& vector);
+template<vx::Arithmetic T>
+constexpr static inline const T length2(const glm::vec<3, T>& vector);
+template<vx::Arithmetic T>
+constexpr static inline const T distance2(const glm::vec<2, T>& vector_a, const glm::vec<2, T>& vector_b);
+template<vx::Arithmetic T>
+constexpr static inline const T distance2(const glm::vec<3, T>& vector_a, const glm::vec<3, T>& vector_b);
+} // namespace vx
+
+template<vx::Arithmetic T>
+constexpr static inline const T vx::length2(const glm::vec<2, T>& vector)
+{
+ return (vector.x * vector.x) + (vector.y * vector.y);
+}
+
+template<vx::Arithmetic T>
+constexpr static inline const T vx::length2(const glm::vec<3, T>& vector)
+{
+ return (vector.x * vector.x) + (vector.y * vector.y) + (vector.z * vector.z);
+}
+
+template<vx::Arithmetic T>
+constexpr static inline const T vx::distance2(const glm::vec<2, T>& vector_a, const glm::vec<2, T>& vector_b)
+{
+ return vx::length2(vector_a - vector_b);
+}
+
+template<vx::Arithmetic T>
+constexpr static inline const T vx::distance2(const glm::vec<3, T>& vector_a, const glm::vec<3, T>& vector_b)
+{
+ return vx::length2(vector_a - vector_b);
+}
+
+#endif /* CORE_VECTORS_HH */
diff --git a/src/core/version.cc b/src/core/version.cc
new file mode 100644
index 0000000..d86564e
--- /dev/null
+++ b/src/core/version.cc
@@ -0,0 +1,12 @@
+#include "core/pch.hh"
+
+#include "core/version.hh"
+
+// clang-format off
+const unsigned long project_version_major = 0;
+const unsigned long project_version_minor = 0;
+const unsigned long project_version_patch = 1;
+const unsigned long project_version_tweak = 2526;
+// clang-format on
+
+const std::string project_version_string = "0.0.1.2526";
diff --git a/src/core/version.cc.in b/src/core/version.cc.in
new file mode 100644
index 0000000..0183ec0
--- /dev/null
+++ b/src/core/version.cc.in
@@ -0,0 +1,12 @@
+#include "core/pch.hh"
+
+#include "core/version.hh"
+
+// clang-format off
+const unsigned long project_version_major = ${PROJECT_VERSION_MAJOR};
+const unsigned long project_version_minor = ${PROJECT_VERSION_MINOR};
+const unsigned long project_version_patch = ${PROJECT_VERSION_PATCH};
+const unsigned long project_version_tweak = ${PROJECT_VERSION_TWEAK};
+// clang-format on
+
+const std::string project_version_string = "${PROJECT_VERSION}";
diff --git a/src/core/version.hh b/src/core/version.hh
new file mode 100644
index 0000000..2061c31
--- /dev/null
+++ b/src/core/version.hh
@@ -0,0 +1,12 @@
+#ifndef CORE_VERSION_HH
+#define CORE_VERSION_HH 1
+#pragma once
+
+extern const unsigned long project_version_major;
+extern const unsigned long project_version_minor;
+extern const unsigned long project_version_patch;
+extern const unsigned long project_version_tweak;
+
+extern const std::string project_version_string;
+
+#endif /* CORE_VERSION_HH */