Major SDK improvements + Cap'n Proto assets format

This commit is contained in:
legatt 2026-04-27 03:43:45 +03:00
parent 55258615c5
commit 7ec8c82569
53 changed files with 1091 additions and 1108 deletions

View File

@ -17,16 +17,15 @@ endif()
project (EtrediaEmpires LANGUAGES CXX)
add_subdirectory(sdk/map)
add_subdirectory(sdk/assets)
add_subdirectory(sdk)
add_subdirectory(p7lib)
add_subdirectory(utils)
add_subdirectory(server/lib)
#add_subdirectory(server/lib)
add_subdirectory(engine-api)
add_subdirectory(renderers/magnum-renderer)
# add_subdirectory(renderers/magnum-renderer)
add_subdirectory(renderers/vulkan)
add_subdirectory(engine)

View File

Before

Width:  |  Height:  |  Size: 33 MiB

After

Width:  |  Height:  |  Size: 33 MiB

View File

Before

Width:  |  Height:  |  Size: 364 KiB

After

Width:  |  Height:  |  Size: 364 KiB

View File

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

11
assets-src/regions.json Normal file
View File

@ -0,0 +1,11 @@
{
"pepene": "#AA00FF",
"scarna": "#FF007F",
"dineria": "#FFFF00",
"perdania": "#7FFF00",
"arstinia": "#FF0000",
"alende": "#0000FF",
"zerandia": "#FF5400",
"mgahvatia": "#5400FF"
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1 +0,0 @@
贔PD゙<EFBFBD>タイ

Binary file not shown.

View File

@ -1,7 +1,7 @@
project(engine-api)
include(${CMAKE_SOURCE_DIR}/cmake/library.cmake)
set(LIB engine-api)
# Find dependencies
find_package(glfw3 CONFIG REQUIRED)
@ -9,10 +9,9 @@ find_package(glm CONFIG REQUIRED)
# Link library
add_library_target(${PROJECT_NAME})
add_library_target(${LIB})
target_link_libraries(${PROJECT_NAME} PRIVATE p7)
target_link_libraries(${PROJECT_NAME} PUBLIC server)
target_link_libraries(${PROJECT_NAME} PUBLIC utils)
target_link_libraries(${PROJECT_NAME} PRIVATE glfw)
target_link_libraries(${PROJECT_NAME} PRIVATE glm::glm)
target_link_libraries(${LIB} PRIVATE p7)
target_link_libraries(${LIB} PUBLIC utils)
target_link_libraries(${LIB} PRIVATE glfw)
target_link_libraries(${LIB} PRIVATE glm::glm)

View File

@ -1,12 +1,12 @@
export module AssetData;
import GameData;
import views;
export import GameData;
export import views;
import std;
export namespace ee_assets {
struct AssetData {};
struct AssetTag {};
template <typename T> concept Asset = std::is_base_of_v<AssetData, T>;
template <typename T> concept Asset = requires { typename T::asset_tag; };
enum AssetType {
BINARY,
@ -19,33 +19,33 @@ export namespace ee_assets {
IMAGE
};
struct AssetBinary : public AssetData {
struct AssetBinary {
using asset_tag = AssetTag;
std::uint8_t *content = nullptr;
std::size_t size = 0;
AssetBinary(std::uint8_t * & source, const std::size_t sourceSize);
AssetBinary(std::uint8_t * & source, std::size_t sourceSize);
~AssetBinary();
};
struct AssetMapMesh : public AssetData {
ee_engine::MapMesh content;
struct AssetMapMesh {
using asset_tag = AssetTag;
std::uint8_t* Serialize(std::size_t& size);
AssetMapMesh() {};
AssetMapMesh(std::uint8_t * & source, const std::size_t sourceSize);
ee_engine::MapMesh content;
};
struct AssetSave : public AssetData {
ee_data::GameState content;
struct AssetSave {
using asset_tag = AssetTag;
std::uint8_t* Serialize(std::size_t& size);
AssetSave() {};
AssetSave(std::uint8_t * & source, const std::size_t sourceSize);
ee_data::GameState content;
};
using AssetShader = AssetBinary;
struct AssetMaterial : public AssetData {
struct AssetMaterial {
using asset_tag = AssetTag;
std::uint32_t vertexShaderAssetID;
std::uint32_t fragmentShaderAssetID;
};

View File

@ -0,0 +1,17 @@
export module Packets;
import std;
export namespace ee_data {
enum class Route {
LOAD_SAVE
};
struct Request {
Route route;
};
struct Response {
Route route;
std::span<std::byte> payload;
};
}

View File

@ -26,7 +26,7 @@ export namespace ee_engine {
float rotation;
};
struct RendererCommand : public ee_utils::WorkerCommand {
struct RendererCommand : ee_utils::WorkerCommand {
enum Type : std::uint8_t {
NONE,

View File

@ -12,7 +12,7 @@ export namespace ee_engine {
std::vector<std::uint16_t> indices;
};
std::unordered_map<std::uint32_t, std::vector<Province>> regions;
std::unordered_map<std::uint32_t /* colorId */, std::vector<Province>> regions;
};
struct ProvinceSSBO {

View File

@ -10,8 +10,6 @@ YOU CAN PREVENT SOURCE BUFFER FROM CLOSING.
IT CAN BE USED FOR STORING ASSET DATA WITHOUT COPY.
IN THIS CASE, YOU MUST SET SOURCE TO NULLPTR AND FREE BUFFER YOURSELF.
TODO: make universal binary format, think about flatbuffers or msgpacks
*/
ee_assets::AssetBinary::AssetBinary(std::uint8_t * & source, const std::size_t sourceSize) {
@ -24,128 +22,3 @@ ee_assets::AssetBinary::~AssetBinary() {
if (content != nullptr)
DeallocateArray(content);
}
ee_assets::AssetMapMesh::AssetMapMesh(std::uint8_t * & source, const std::size_t sourceSize) {
std::uint8_t *buffer = source;
std::uint8_t *pointer = source;
std::size_t leftoverSize = sourceSize;
source = nullptr;
std::uint32_t regionsCount = 0;
ee_utils::readNext(&regionsCount, pointer, leftoverSize, sizeof(std::uint32_t));
for (std::uint32_t region = 0; region < regionsCount; region++) {
std::uint32_t id = 0;
glm::u8vec3 color{};
if (!ee_utils::readNext(&color, pointer, leftoverSize, sizeof(glm::u8vec3)))
break;
id = static_cast<std::uint32_t>(color.r) << 16 |
static_cast<std::uint32_t>(color.g) << 8 |
static_cast<std::uint32_t>(color.b);
std::uint32_t provincesCount = 0;
ee_utils::readNext(&provincesCount, pointer, leftoverSize, sizeof(std::uint32_t));
for (std::uint32_t pi = 0; pi < provincesCount; pi++) {
ee_engine::MapMesh::Province prov;
std::uint32_t verticesCount = 0;
if (!ee_utils::readNext(&verticesCount, pointer, leftoverSize, sizeof(std::uint32_t)))
break;
prov.vertices.reserve(verticesCount);
for (std::uint32_t vi = 0; vi < verticesCount; vi++) {
glm::vec2 vertex;
if (!ee_utils::readNext(&vertex, pointer, leftoverSize, sizeof(glm::vec2)))
break;
prov.vertices.push_back(vertex);
}
std::uint32_t indicesCount = 0;
if (!ee_utils::readNext(&indicesCount, pointer, leftoverSize, sizeof(std::uint32_t)))
break;
prov.indices.reserve(indicesCount);
for (std::uint32_t ii = 0; ii < indicesCount; ii++) {
std::uint16_t index = 0;
if (!ee_utils::readNext(&index, pointer, leftoverSize, sizeof(std::uint16_t)))
break;
prov.indices.push_back(index);
}
content.regions[id].push_back(prov);
}
}
DeallocateArray(buffer);
}
ee_assets::AssetSave::AssetSave(std::uint8_t * & source, const std::size_t sourceSize) {
std::uint8_t *buffer = source;
std::uint8_t *pointer = source;
std::size_t leftoverSize = sourceSize;
source = nullptr;
std::uint16_t subjectsCount = static_cast<std::uint16_t>(content.subjects.size());
ee_utils::readNext(&subjectsCount, pointer, leftoverSize, sizeof(std::uint16_t));
for (std::uint16_t i = 0; i < subjectsCount; i++) {
ee_data::Subject subject;
ee_utils::readNext(&subject.tag, pointer, leftoverSize, sizeof(subject.tag));
ee_utils::readNext(&subject.color, pointer, leftoverSize, sizeof(subject.color));
std::uint16_t regionsCount = 0;
ee_utils::readNext(&regionsCount, pointer, leftoverSize, sizeof(std::uint16_t));
subject.regions.resize(regionsCount);
ee_utils::readNext(
subject.regions.data(),
pointer,
leftoverSize,
subject.regions.size() * sizeof(std::uint32_t)
);
content.subjects.push_back(subject);
}
DeallocateArray(buffer);
}
// SERIALIZERS
std::uint8_t* ee_assets::AssetSave::Serialize(std::size_t& size) {
size = sizeof(std::uint16_t);
for (ee_data::Subject& subject : content.subjects) {
size += sizeof(subject.tag);
size += sizeof(subject.color);
size += sizeof(std::uint16_t) + subject.regions.size() * sizeof(std::uint32_t);
}
std::size_t leftover = size;
std::uint8_t *result = AllocateArray<std::uint8_t>(size);
std::uint8_t *pointer = result;
std::uint16_t subjectsCount = static_cast<std::uint16_t>(content.subjects.size());
ee_utils::writeNext(pointer, &subjectsCount, leftover, sizeof(std::uint16_t));
for (ee_data::Subject& subject : content.subjects) {
ee_utils::writeNext(pointer, &subject.tag, leftover, sizeof(subject.tag));
ee_utils::writeNext(pointer, &subject.color, leftover, sizeof(subject.color));
std::uint16_t regionsCount = static_cast<std::uint16_t>(subject.regions.size());
ee_utils::writeNext(pointer, &regionsCount, leftover, sizeof(std::uint16_t));
ee_utils::writeNext(
pointer,
subject.regions.data(),
leftover,
subject.regions.size() * sizeof(std::uint32_t)
);
}
return result;
}

View File

@ -8,6 +8,7 @@ find_package(Boost REQUIRED COMPONENTS lockfree)
find_package(xxHash CONFIG REQUIRED)
find_package(glfw3 CONFIG REQUIRED)
find_package(glm CONFIG REQUIRED)
find_package(reflectcpp CONFIG REQUIRED)
# Link library
@ -15,7 +16,8 @@ add_executable_target(${EXE})
target_link_libraries(${EXE} PUBLIC utils)
target_link_libraries(${EXE} PUBLIC engine-api)
target_link_libraries(${EXE} PUBLIC magnum-renderer)
# target_link_libraries(${EXE} PUBLIC server)
# target_link_libraries(${EXE} PUBLIC magnum-renderer)
target_link_libraries(${EXE} PUBLIC vulkan-renderer)
target_link_libraries(${EXE} PRIVATE Boost::lockfree)
@ -23,6 +25,7 @@ target_link_libraries(${EXE} PRIVATE xxHash::xxhash)
target_link_libraries(${EXE} PRIVATE p7)
target_link_libraries(${EXE} PRIVATE glfw)
target_link_libraries(${EXE} PRIVATE glm::glm)
target_link_libraries(${EXE} PRIVATE reflectcpp::reflectcpp)
# Compile options
if (APP_VERSION)

View File

@ -2,6 +2,7 @@ module;
#include "compile_config.hpp"
#include <xxhash.h>
#include <rfl/capnproto.hpp>
export module AssetManager;
export import AssetData;
@ -29,7 +30,7 @@ export
std::atomic<bool> isReady;
AssetAddress *resourceAddress;
AssetInfo(void *resource) {
explicit AssetInfo(void *resource) {
this->resourceAddress = Allocate<AssetAddress>();
this->resourceAddress->resource.store(resource);
}
@ -122,7 +123,7 @@ export
Request(command);
while (command.out->load() == nullptr) {}
return *reinterpret_cast<AssetHandle*>(command.out->load());
return *static_cast<AssetHandle*>(command.out->load());
}
AssetHandle& Find(const std::uint32_t id) {
@ -133,8 +134,8 @@ export
command.out = &result;
Request(command);
while (command.out->load() == nullptr);
return *reinterpret_cast<AssetHandle*>(command.out->load());
while (command.out->load() == nullptr) {}
return *static_cast<AssetHandle*>(command.out->load());
}
// Returned resource reference should not be stored
@ -149,7 +150,7 @@ export
return Get<T>(Find(id));
}
void Redirect(const AssetHandle& handle, void *resource) {
void Redirect(const AssetHandle& handle, void *resource) const {
const AssetInfo& asset = activeAssets[handle.slot];
if (asset.resourceAddress == nullptr) {
LOG(WARNING, "Attempt to redirect empty handle");
@ -176,11 +177,11 @@ export
command.out = &result;
Request(command);
while (command.out->load() == nullptr);
return *reinterpret_cast<T*>(command.out->load());
while (command.out->load() == nullptr) {}
return *static_cast<T*>(command.out->load());
}
void Free(AssetHandle& handle) {
void Free(const AssetHandle& handle) {
AssetInfo& asset = activeAssets[handle.slot];
Redirect(handle, nullptr);
Deallocate(asset.resourceAddress);
@ -193,23 +194,22 @@ export
void Update() override {
// TODO: FinishLoadTasks()
for (AssetLoadingEntry& entry : loadingEntries) {
bool isReady = entry.fileOwner->PollReady(*entry.file);
if (isReady) {
if (entry.fileOwner->PollReady(*entry.file)) {
AssetInfo& asset = activeAssets[entry.handle.slot];
switch (asset.type) {
case ee_assets::MAP_MESH:
FinishLoadConstruct(entry, mapMeshesStorage);
FinishLoadCapnprotoDeserialize(entry, mapMeshesStorage);
break;
case ee_assets::SAVE:
FinishLoadConstruct(entry, savesStorage);
FinishLoadCapnprotoDeserialize(entry, savesStorage);
break;
case ee_assets::SHADER:
FinishLoadConstruct(entry, shadersStorage);
break;
case ee_assets::MATERIAL:
FinishLoadPlain(entry, materialsStorage);
FinishLoadCapnprotoDeserialize(entry, materialsStorage);
break;
case ee_assets::IMAGE:
break;
@ -298,13 +298,13 @@ export
assetPos = activeAssets.size() - 1;
}
handle->slot = assetPos;
auto assetPath = command.targetPath.generic_u8string();
std::uint32_t assetId = assetsIdMapping[command.targetPath] =
const std::basic_string<char8_t> assetPath = command.targetPath.generic_u8string();
const std::uint32_t assetId = assetsIdMapping[command.targetPath] =
XXH32(assetPath.data(), assetPath.size(), ASSETS_HASH_SPACE);
handle->id = assetId;
assetsMapping[assetId] = handle;
AssetLoadingEntry loadingEntry{
const AssetLoadingEntry loadingEntry{
.handle = *handle,
.file = &file,
.fileOwner = diskSystem
@ -324,7 +324,7 @@ export
id = idMapping->second;
}
std::unordered_map<std::uint32_t, AssetHandle*>::iterator assetMapping = assetsMapping.find(id);
const std::unordered_map<std::uint32_t, AssetHandle*>::iterator assetMapping = assetsMapping.find(id);
if (assetMapping == assetsMapping.end())
return;
@ -335,7 +335,7 @@ export
command.type = AssetStreamCommand::COMPLETED;
}
void GetCmd(AssetStreamCommand& command) {
void GetCmd(AssetStreamCommand& command) const {
if (activeAssets.size() <= command.target.slot)
return;
if (!activeAssets[command.target.slot].isReady)
@ -355,14 +355,37 @@ export
}
template <ee_assets::Asset T>
void FinishLoadPlain(AssetLoadingEntry& loaded, std::vector<T*>& storage) {
T *resource = Allocate<T>();
if (loaded.file->size < sizeof(T))
LOG(CRITICAL, "File size is less than needed for asset of type: %d", typeid(T).name());
std::memcpy(resource, loaded.file->data, sizeof(T));
storage.push_back(resource);
void FinishLoadCapnprotoDeserialize(AssetLoadingEntry& loaded, std::vector<T*>& storage) {
try {
if (rfl::Result<T> result = rfl::capnproto::read<T>(
reinterpret_cast<const char*>(loaded.file->data),
loaded.file->size
)) {
storage.push_back(Allocate<T>(std::move(*result)));
Redirect(loaded.handle, storage.back());
}
else {
#if !defined(NDEBUG)
LOG(
ERROR,
"Failed to read asset: [%s]. [%hs]",
loaded.file->debugPath.c_str(),
result.error().what().c_str()
);
#else
LOG(ERROR, "Failed to read asset: ID[%d]. [%hs]", loaded.file->id, result.error().what().c_str());
#endif
}
} catch (std::exception e) {
storage.push_back(Allocate<T>());
Redirect(loaded.handle, storage.back());
#if !defined(NDEBUG)
LOG(ERROR, "Failed to read asset: [%s]. [%hs]", loaded.file->debugPath.c_str(), e.what());
#else
LOG(ERROR, "Failed to read asset: ID[%d]. [%hs]", loaded.file->id, e.what());
#endif
}
}
template <ee_assets::Asset T>
void ClearStorage(std::vector<T*>& storage) {

View File

@ -7,7 +7,7 @@ import std;
export
{
struct ClientCommand : public ee_utils::WorkerCommand {
struct ClientCommand : ee_utils::WorkerCommand {
enum Type : std::uint8_t {
NONE,
@ -21,14 +21,14 @@ export
public:
Client();
~Client() override;
void Init();
void Init() const;
//void Send(); -> Request(SEND)
const IDataChannel* GetConnection();
const IDataChannel* GetConnection() const;
void ConnectServer();
void LoadSave(const AssetHandle save);
void LoadSave(AssetHandle save);
protected:
void Update() override;
@ -40,6 +40,6 @@ export
void SendCmd(ClientCommand& command);
void ViewModelSubject(ee_data::Subject subject);
static void ViewModelSubject(const ee_data::Subject &subject);
};
}

View File

@ -1,17 +1,26 @@
module;
export module DataChannel;
import std;
export
{
class IDataChannel {
public:
// virtual void Request() = 0;
virtual ~IDataChannel() = default;
virtual void Request() = 0;
// virtual void Listen() = 0;
// virtual void Mute() = 0;
};
class NetworkChannel final : public IDataChannel {};
class NetworkChannel final : public IDataChannel {
public:
void Request() override;
};
class ThreadChannel final : public IDataChannel {};
class ThreadChannel final : public IDataChannel {
public:
void Request() override;
};
}

View File

@ -30,16 +30,16 @@ export
class IFileSystem {
public:
inline static constexpr std::uint32_t FILE_OPEN_NONE = 0;
inline static constexpr std::uint32_t FILE_OPEN_READ = (1 << 0);
inline static constexpr std::uint32_t FILE_OPEN_WRITE = (1 << 1);
inline static constexpr std::uint32_t FILE_OPEN_MMAP = (1 << 2);
inline static constexpr std::uint32_t FILE_READ_SEQUENTIAL = 0;
inline static constexpr std::uint32_t FILE_READ_RANDOM_ACCESS = (1 << 0);
static constexpr std::uint32_t FILE_OPEN_NONE = 0;
static constexpr std::uint32_t FILE_OPEN_READ = (1 << 0);
static constexpr std::uint32_t FILE_OPEN_WRITE = (1 << 1);
static constexpr std::uint32_t FILE_OPEN_MMAP = (1 << 2);
static constexpr std::uint32_t FILE_READ_SEQUENTIAL = 0;
static constexpr std::uint32_t FILE_READ_RANDOM_ACCESS = (1 << 0);
virtual FileHandle& Open(const std::filesystem::path& path, const std::uint32_t flags) = 0;
virtual FileHandle& Open(const std::filesystem::path& path, std::uint32_t flags) = 0;
virtual void ReadAsync(FileHandle& file, const std::uint32_t bufferSize, const std::uint32_t flags) = 0;
virtual void ReadAsync(FileHandle& file, std::uint32_t bufferSize, std::uint32_t flags) = 0;
virtual bool PollReady(FileHandle& file) = 0;
@ -55,14 +55,12 @@ export
class NativeFileSystem final : public IFileSystem {
public:
FileHandle& Open(const std::filesystem::path& path, const std::uint32_t flags) override;
FileHandle& Open(const std::filesystem::path& path, std::uint32_t flags) override;
void ReadAsync(FileHandle& file, const std::uint32_t bufferSize, std::uint32_t flags) override;
void ReadAsync(FileHandle& file, std::uint32_t bufferSize, std::uint32_t flags) override;
bool PollReady(FileHandle& file) override;
void Close(FileHandle& file) override;
private:
};
}

View File

@ -7,7 +7,7 @@ Client::Client() : ThreadedWorker(L"Client", 0) {
commandsMapping[ClientCommand::SEND] = std::bind_front(&Client::SendCmd, this);
}
void Client::Init() {
void Client::Init() const {
if (Engine::renderer == nullptr || !Engine::renderer->isReady()) {
LOG(CRITICAL, "Renderer is not ready. Client cannot be created");
throw std::logic_error("Renderer is not ready");
@ -19,7 +19,7 @@ void Client::Init() {
ViewModelSubject(asset.content.subjects[1]);
}
const IDataChannel* Client::GetConnection() {
const IDataChannel* Client::GetConnection() const {
return networkConnection;
}
@ -39,14 +39,15 @@ void Client::Update() {}
void Client::Release() {}
// ReSharper disable once CppMemberFunctionMayBeStatic
void Client::SendCmd(ClientCommand& command) {
command.type = ClientCommand::COMPLETED;
}
void Client::ViewModelSubject(ee_data::Subject subject) {
void Client::ViewModelSubject(const ee_data::Subject &subject) {
for (const std::uint32_t region : subject.regions) {
ee_engine::ProvinceSSBO vm{
const ee_engine::ProvinceSSBO vm {
.regionId = region,
.color = subject.color
};

View File

@ -0,0 +1,7 @@
module DataChannel;
void NetworkChannel::Request() {
}
void ThreadChannel::Request() {}

View File

@ -31,7 +31,7 @@ int main(int argc, char **argv) {
glfwMakeContextCurrent(window);
Engine::window = window;
// Setup Asset Manager TODO: builder
// Setup Asset Manager
Engine::assetManager = Allocate<AssetManager>();
Engine::assetManager->Start();
Engine::assetManager->LoadDir(ee_assets::SHADER, "assets/shaders");
@ -41,18 +41,17 @@ int main(int argc, char **argv) {
Engine::assetManager->LoadAsync(ee_assets::MAP_MESH, "assets/map/regions.map");
Engine::assetManager->LoadAsync(ee_assets::SAVE, "assets/scenarios/empires.save");
// Setup input manager TODO: builder
// Setup input manager
Engine::input = Allocate<GLFWInput>(Actions::mappings);
// Setup renderer TODO: builder
//Engine::renderer = reinterpret_cast<ee_engine::IRenderer*>(Allocate<ee_magnum::MagnumRenderer>(window));
// Setup renderer
Engine::renderer = reinterpret_cast<ee_engine::IRenderer*>(Allocate<ee_vulkan::VulkanRenderer>(window));
ee_assets::AssetMapMesh& regions = Engine::assetManager->FindOnce<ee_assets::AssetMapMesh>("assets/map/regions.map");
Engine::renderer->RegisterMapMesh(&regions.content);
//TODO: ShaderRefs builder from set of AssetMaterial
ee_assets::AssetMaterial& provincesMaterial =
const ee_assets::AssetMaterial& provincesMaterial =
Engine::assetManager->FindOnce<ee_assets::AssetMaterial>("assets/materials/provinces.mat");
ee_assets::AssetShader& vertProv = Engine::assetManager->FindOnce<ee_assets::AssetShader>(
provincesMaterial.vertexShaderAssetID);
@ -74,7 +73,7 @@ int main(int argc, char **argv) {
Engine::renderer->ResizeWindow(width, height);
});
// Setup Client TODO: builder
// Setup Client
Engine::client = Allocate<Client>();
const AssetHandle save = Engine::assetManager->Find("assets/scenarios/empires.save");
Engine::client->LoadSave(save);

View File

@ -82,7 +82,7 @@ void ee_magnum::MagnumRenderer::LoadProvinces() {
regionMesh.setPrimitive(GL::MeshPrimitive::Triangles)
.setCount(static_cast<Magnum::Int>(province.indices.size()))
.addVertexBuffer(GL::Buffer{provinceVertices}, 0, GL::Attribute<0, Magnum::Vector2>{})
.addVertexBuffer(GL::Buffer{provinceVertices}, 0, GL::Attribute<0, Magnum::Vector3>{})
.setIndexBuffer(GL::Buffer{provinceIndices}, 0, GL::MeshIndexType::UnsignedShort);
provinceMeshes.push_back(std::move(regionMesh));

25
sdk/CMakeLists.txt Normal file
View File

@ -0,0 +1,25 @@
include(${CMAKE_SOURCE_DIR}/cmake/executable.cmake)
set(EXE sdk)
# Find dependencies
find_package(cxxopts CONFIG REQUIRED)
find_package(reflectcpp CONFIG REQUIRED)
find_package(glm CONFIG REQUIRED)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(xxHash CONFIG REQUIRED)
# Link library
add_executable_target(${EXE})
target_link_libraries(${EXE} PUBLIC utils)
target_link_libraries(${EXE} PUBLIC engine-api)
target_link_libraries(${EXE} PUBLIC p7)
target_link_libraries(${EXE} PRIVATE cxxopts::cxxopts)
target_link_libraries(${EXE} PRIVATE reflectcpp::reflectcpp)
target_link_libraries(${EXE} PRIVATE glm::glm)
target_link_libraries(${EXE} PRIVATE nlohmann_json::nlohmann_json)
target_link_libraries(${EXE} PRIVATE xxHash::xxhash)

View File

@ -1,26 +0,0 @@
cmake_minimum_required (VERSION 3.8)
if (POLICY CMP0141)
cmake_policy(SET CMP0141 NEW)
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project (compile_assets)
find_package(nlohmann_json CONFIG REQUIRED)
find_package(xxHash CONFIG REQUIRED)
add_executable(${PROJECT_NAME} compile_assets.cpp define/regions.def)
target_link_libraries(${PROJECT_NAME} PRIVATE nlohmann_json::nlohmann_json)
target_link_libraries(${PROJECT_NAME} PRIVATE xxHash::xxhash)
target_link_libraries(${PROJECT_NAME} PUBLIC engine-api)
target_link_libraries(${PROJECT_NAME} PUBLIC server)
target_compile_options(${PROJECT_NAME} PRIVATE /Zc:preprocessor)
if (CMAKE_VERSION VERSION_GREATER 3.12)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20)
endif()

View File

@ -1,187 +0,0 @@
import AssetData;
import GameData;
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <nlohmann/json.hpp>
#include <xxhash.h>
#include <string>
#include <charconv>
#include <functional>
using json = nlohmann::json;
#define bytes(value) reinterpret_cast<const char*>(value)
namespace ee_data
{
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
Subject,
tag,
color,
regions
)
}
namespace ee_data
{
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
GameState,
subjects
)
}
namespace ee_assets
{
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
AssetSave,
content
)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
AssetMaterial,
vertexShaderAssetID,
fragmentShaderAssetID
)
}
struct MappingEntry
{
std::string_view name;
uint32_t value;
};
static constexpr MappingEntry mappings[] = {
#define MAP(name, value) {#name, ((value & 0x0000FF) << 16) | ( value & 0x00FF00) | ((value & 0xFF0000) >> 16)},
#include "define/regions.def"
#undef MAP
};
void CompileRecursive(json &obj, std::function<void(const std::basic_string<char> &key, json &value)> callback)
{
if (obj.is_array()) {for (auto &element : obj) CompileRecursive(element, callback); return;}
if (!obj.is_object()) {callback("", obj); return;}
for (auto &[key, value] : obj.items())
{
if (value.is_object())
CompileRecursive(value, callback);
else if (value.is_array())
for (auto &element : value)
CompileRecursive(element, callback);
else callback(key, value);
}
}
int main(int argc, char **argv)
{
try
{
std::filesystem::path in = argv[1];
std::filesystem::path out = argv[2];
char* assetTypeStr = argv[3];
std::ifstream fin(in);
std::ofstream fout(out);
ee_assets::AssetType assetType = static_cast<ee_assets::AssetType>(atoi(assetTypeStr));
if (!fin.is_open()) {
std::cout << "Error: Could not open input file: " << in << std::endl;
return 1;
}
if (!fout.is_open()) {
std::cout << "Error: Could not open output file: " << out << std::endl;
return 1;
}
json data = json::parse(fin);
// Compile hashes
CompileRecursive(data, [](const std::basic_string<char> &key, json &value) {
if (!value.is_string()) return;
if (
!(key == "tag") &&
!key.ends_with("AssetID") &&
!key.ends_with("Type")
) return;
std::filesystem::path linkPath = value;
auto linkPath8 = linkPath.generic_u8string();
uint32_t assetId = XXH32(linkPath8.data(), linkPath8.size(), 0);
std::cout << "Replace string [" << value << "] with [" << assetId << "]" << std::endl;
value = assetId;
});
// Compile mappings
CompileRecursive(data, [](const std::basic_string<char> &key, json &value) {
if (!value.is_string()) return;
const std::string& value_string = value.get_ref<const std::string&>();
for (const MappingEntry &map : mappings)
{
if (value_string == map.name)
{
std::cout << "Replace string [" << value << "] with [" << map.value << "]" << std::endl;
value = map.value;
break;
}
}
});
// Compile colors
CompileRecursive(data, [](const std::basic_string<char> &key, json &value) {
if (!value.is_string()) return;
const char* color_str = value.get_ref<const std::string&>().c_str() + 1;
uint8_t b = 0, g = 0, r = 0;
uint32_t hex = 0;
std::from_chars(color_str, color_str + 2, r, 16); color_str += 2;
std::from_chars(color_str, color_str + 2, g, 16); color_str += 2;
std::from_chars(color_str, color_str + 2, b, 16); color_str += 2;
hex = static_cast<uint32_t>(r) << 16 |
static_cast<uint32_t>(g) << 8 |
static_cast<uint32_t>(b);
std::cout << "Replace string [" << value << "] with [" << (int)r << "," << (int)g << "," << (int)b << "]" << std::endl;
value = hex;
});
switch (assetType)
{
case ee_assets::MATERIAL:
{
ee_assets::AssetMaterial material = data.get<ee_assets::AssetMaterial>();
fout.write(bytes(&material), sizeof(material));
break;
}
case ee_assets::SAVE:
{
ee_assets::AssetSave save = data.get<ee_assets::AssetSave>();
size_t size = 0;
const char* data = bytes(save.Serialize(size));
fout.write(data, size);
break;
}
default:
break;
}
fin.close();
fout.close();
}
catch (const std::exception &e)
{
std::cerr << e.what() << std::endl;
return 1;
}
return 0;
}

View File

@ -1,15 +0,0 @@
#ifndef MAP
#define MAP(name, color)
#endif
MAP(pepene, 0xAA00FF)
MAP(scarna, 0xFF007F)
MAP(dineria, 0xFFFF00)
MAP(perdania, 0x7FFF00)
MAP(arstinia, 0xFF0000)
MAP(alende, 0x0000FF)
MAP(zerandia, 0xFF5400)
MAP(mgahvatia, 0x5400FF)
#undef MAP

View File

@ -1,10 +0,0 @@
@echo off
set ASSETS=../../assets
set COMPILE=%CD%/../../out/build/DEBUG/sdk/assets/compile_assets.exe
set MATERIAL=4
set SAVE=2
for %%F in (materials/*) do %COMPILE% "materials/%%~nF.json" "%ASSETS%/materials/%%~nF" %MATERIAL% && echo "Compiled: %%~nF"
for %%F in (saves/*) do %COMPILE% "saves/%%~nF.json" "%ASSETS%/scenarios/%%~nF" %SAVE% && echo "Compiled: %%~nF"

View File

@ -1,4 +0,0 @@
@echo off
glslc provinces/provinces.frag -O -o ../../../assets/shaders/provinces.frag
glslc provinces/provinces.vert -O -o ../../../assets/shaders/provinces.vert

View File

@ -1,10 +0,0 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}

View File

@ -1,21 +0,0 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(binding = 0) uniform Global {
mat4 view;
mat4 proj;
} global;
layout(binding = 1) uniform Transform {
mat4 model;
} transform;
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor;
layout(location = 0) out vec3 fragColor;
void main() {
gl_Position = global.proj * global.view * transform.model * vec4(inPosition, 0.0, 1.0);
fragColor = inColor;
}

View File

@ -1,10 +0,0 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec3 fragColor;
layout(location = 0) out vec4 outColor;
void main() {
outColor = vec4(fragColor, 1.0);
}

View File

@ -1,35 +0,0 @@
#version 450
#extension GL_ARB_separate_shader_objects : enable
layout(location = 0) in vec2 position;
layout(binding = 0) uniform Camera {
mat4 viewProjection;
} camera;
layout(push_constant) uniform Model {
mat4 model;
} model;
struct Province {
uint regionId;
uint color;
};
layout(std430, binding = 1) readonly buffer Data {
Province provinces[];
};
layout(location = 0) out vec3 fragColor;
vec3 unpackColor(uint c) {
return vec3(
c & 0xFFu,
(c >> 8) & 0xFFu,
(c >> 16) & 0xFFu
) / 255.0;
}
void main() {
gl_Position = camera.viewProjection * vec4(position, 0.0, 1.0);
fragColor = unpackColor(provinces[gl_InstanceIndex].color);
}

View File

@ -1,45 +0,0 @@
cmake_minimum_required (VERSION 3.8)
if (POLICY CMP0141)
cmake_policy(SET CMP0141 NEW)
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>:ProgramDatabase>>")
endif()
set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
project (convert_map)
# find_package(glm CONFIG REQUIRED)
add_executable(${PROJECT_NAME} convert_map.cpp)
# target_link_libraries(${PROJECT_NAME} PRIVATE glm::glm)
# target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_SOURCE_DIR}/engine/include/)
if (CMAKE_VERSION VERSION_GREATER 3.12)
set_property(TARGET ${PROJECT_NAME} PROPERTY CXX_STANDARD 20)
endif()
file(GLOB MAP_FILES "${CMAKE_CURRENT_SOURCE_DIR}/*.bmp")
set(COMPILED_DIR "${CMAKE_SOURCE_DIR}/assets/map")
foreach(MAP ${MAP_FILES})
get_filename_component(MAP_NAME ${MAP} NAME_WE)
set(OUT_FILE "${COMPILED_DIR}/${MAP_NAME}.map")
add_custom_command(
OUTPUT ${OUT_FILE}
COMMAND $<TARGET_FILE:${PROJECT_NAME}> ${MAP} ${OUT_FILE}
DEPENDS ${MAP} ${PROJECT_NAME}
COMMENT "Converting map ${MAP_NAME}"
VERBATIM
)
list(APPEND COMPILED_BINARIES ${OUT_FILE})
endforeach()
add_custom_target(compile_maps ALL
DEPENDS ${COMPILED_BINARIES}
)

View File

@ -1,499 +0,0 @@
#include <fstream>
#include <iostream>
#include <cstdint>
#include <vector>
#include <unordered_map>
#include <queue>
#include <stack>
#include <limits>
#define bytes(value) reinterpret_cast<const char*>(value)
#pragma pack(push, 1)
struct BMPFileHeader {
uint16_t bfType;
uint32_t bfSize;
uint16_t bfReserved1;
uint16_t bfReserved2;
uint32_t bfOffBits;
};
struct BMPInfoHeader {
uint32_t biSize;
int32_t biWidth;
int32_t biHeight;
uint16_t biPlanes;
uint16_t biBitCount;
uint32_t biCompression;
uint32_t biSizeImage;
int32_t biXPelsPerMeter;
int32_t biYPelsPerMeter;
uint32_t biClrUsed;
uint32_t biClrImportant;
};
struct BGR { uint8_t b,g,r; };
#pragma pack(pop)
struct vertex { float x,y; };
struct BGRHash {
size_t operator()(const BGR& c) const noexcept {
return static_cast<uint32_t>(c.r) << 16 |
static_cast<uint32_t>(c.g) << 8 |
static_cast<uint32_t>(c.b);
}
};
bool operator==(const BGR&a,const BGR&b){
return a.r==b.r && a.g==b.g && a.b==b.b;
}
BGR EMPTY_COLOR{255,255,255};
uint8_t* imageData;
uint8_t* tempImageData;
size_t width,height,rowSize;
struct prov
{
std::vector<size_t> vertices;
std::vector<uint16_t> indices;
};
std::unordered_map<BGR,std::vector<prov>,BGRHash> pixelsPerProvincesPerState;
inline BGR getPixelSafe(size_t r,size_t c){
if(r>=height||c>=width) return EMPTY_COLOR;
return *reinterpret_cast<BGR*>(
imageData + r*rowSize + c*3
);
}
inline BGR getPixelSafeIndex(size_t i){
return getPixelSafe(i/width,i%width);
}
/* ================= FLOOD FILL ================= */
void FloodFill(){
size_t size=width*height;
for(size_t i=0;i<size;i++){
if(*reinterpret_cast<BGR*>(tempImageData + (i/width)*rowSize + (i%width)*3)==EMPTY_COLOR)
continue;
BGR color=*reinterpret_cast<BGR*>(tempImageData + (i/width)*rowSize + (i%width)*3);
std::vector<size_t> prov;
std::queue<size_t> q;
q.push(i);
while(!q.empty()){
size_t n=q.front(); q.pop();
BGR& px=*reinterpret_cast<BGR*>(tempImageData + (n/width)*rowSize + (n%width)*3);
if(px!=color) continue;
px=EMPTY_COLOR;
prov.push_back(n);
if(n>=width) q.push(n-width);
if(n+width<size) q.push(n+width);
if(n%width) q.push(n-1);
if(n%width+1<width) q.push(n+1);
}
pixelsPerProvincesPerState[color].push_back({prov});
}
}
/* ================= MOORE ================= */
struct offset{int8_t x,y;};
static const offset moore[8]={
{1,0},{1,-1},{0,-1},{-1,-1},
{-1,0},{-1,1},{0,1},{1,1}
};
bool MooreStep(size_t cur,size_t prev,BGR id,size_t& next){
int cr=cur/width, cc=cur%width;
int pr=prev/width,pc=prev%width;
int dx=cc-pc, dy=cr-pr;
int startDir=0;
for(int i=0;i<8;i++)
if(moore[i].x==dx&&moore[i].y==dy)
startDir=(i+6)&7;
for(int k=0;k<8;k++){
int d=(startDir+k)&7;
int nr=cr+moore[d].y;
int nc=cc+moore[d].x;
if(getPixelSafe(nr,nc)==id){
next=size_t(nr)*width+size_t(nc);
return true;
}
}
return false;
}
/* ================= FIND CONTOUR ================= */
size_t FindStart(const std::vector<size_t>&prov,BGR id){
for(size_t i:prov){
size_t r=i/width,c=i%width;
if(getPixelSafe(r-1,c)!=id||
getPixelSafe(r+1,c)!=id||
getPixelSafe(r,c-1)!=id||
getPixelSafe(r,c+1)!=id)
return i;
}
return std::numeric_limits<size_t>::max();
}
void FindContour(){
for(auto&[state,provs]:pixelsPerProvincesPerState){
for(auto&prov:provs){
if(prov.vertices.empty()) continue;
BGR id=getPixelSafeIndex(prov.vertices[0]);
size_t start=FindStart(prov.vertices,id);
if(start==std::numeric_limits<size_t>::max()) continue;
std::vector<size_t> contour;
contour.push_back(start);
size_t cur=start;
size_t prev = (start % width > 0) ? start - 1 : start + 1;
const size_t MAX=prov.vertices.size()*8;
for(size_t i=0;i<MAX;i++){
size_t next;
if(!MooreStep(cur,prev,id,next)) break;
if(next==start) break;
contour.push_back(next);
prev=cur;
cur=next;
}
prov.vertices.swap(contour);
}
}
}
static double PerpendicularDistance(size_t p, size_t a, size_t b, size_t width)
{
double px = p % width;
double py = p / width;
double ax = a % width;
double ay = a / width;
double bx = b % width;
double by = b / width;
double dx = bx - ax;
double dy = by - ay;
if(dx == 0 && dy == 0)
return std::hypot(px - ax, py - ay);
double num = std::abs(dy * px - dx * py + bx * ay - by * ax);
double den = std::sqrt(dx * dx + dy * dy);
return num / den;
}
// Douglas-Peucker iterative
void Aproximate(size_t epsilon)
{
for(auto& [state, provs] : pixelsPerProvincesPerState)
{
for(prov& prov : provs)
{
if(prov.vertices.size() < 3)
continue;
const size_t n = prov.vertices.size();
std::vector<bool> keep_point(n, true);
std::stack<std::pair<size_t, size_t>> st;
// в стек кладем первый и последний индекс
st.push({0, n - 1});
while(!st.empty())
{
auto [startIndex, endIndex] = st.top();
st.pop();
double dMax = 0.0;
size_t index = startIndex;
for(size_t i = startIndex + 1; i < endIndex; ++i)
{
if(keep_point[i])
{
double d = PerpendicularDistance(
prov.vertices[i],
prov.vertices[startIndex],
prov.vertices[endIndex],
width
);
if(d > dMax)
{
dMax = d;
index = i;
}
}
}
if(dMax >= epsilon)
{
st.push({startIndex, index});
st.push({index, endIndex});
}
else
{
for(size_t j = startIndex + 1; j < endIndex; ++j)
keep_point[j] = false;
}
}
// формируем итоговый список
std::vector<size_t> result;
result.reserve(n);
for(size_t i = 0; i < n; ++i)
{
if(keep_point[i])
result.push_back(prov.vertices[i]);
}
prov.vertices.swap(result);
}
}
}
struct Pt {
int x, y;
size_t id;
};
inline Pt make_pt(size_t i, size_t width) {
return { int(i % width), int(i / width), i };
}
inline long long cross(const Pt& a, const Pt& b, const Pt& c) {
return 1LL * (b.x - a.x) * (c.y - a.y)
- 1LL * (b.y - a.y) * (c.x - a.x);
}
long long signed_area2(const std::vector<Pt>& p) {
long long a = 0;
for (size_t i = 0; i < p.size(); ++i) {
const auto& p0 = p[i];
const auto& p1 = p[(i + 1) % p.size()];
a += 1LL * p0.x * p1.y - 1LL * p1.x * p0.y;
}
return a;
}
bool point_in_triangle(const Pt& p,
const Pt& a,
const Pt& b,
const Pt& c)
{
long long c1 = cross(a, b, p);
long long c2 = cross(b, c, p);
long long c3 = cross(c, a, p);
return (c1 >= 0 && c2 >= 0 && c3 >= 0);
}
std::vector<size_t> triangulate_prov(const std::vector<size_t>& prov,
size_t width)
{
std::vector<Pt> poly;
poly.reserve(prov.size());
for (size_t i : prov)
poly.push_back(make_pt(i, width));
// CCW
if (signed_area2(poly) < 0)
std::reverse(poly.begin(), poly.end());
std::vector<size_t> out; // a,b,c,a,b,c,...
while (poly.size() > 3) {
bool ear_found = false;
for (size_t i = 0; i < poly.size(); ++i) {
size_t i0 = (i + poly.size() - 1) % poly.size();
size_t i1 = i;
size_t i2 = (i + 1) % poly.size();
const Pt& a = poly[i0];
const Pt& b = poly[i1];
const Pt& c = poly[i2];
if (cross(a, b, c) <= 0)
continue; // не выпуклая
bool inside = false;
for (size_t j = 0; j < poly.size(); ++j) {
if (j == i0 || j == i1 || j == i2) continue;
if (point_in_triangle(poly[j], a, b, c)) {
inside = true;
break;
}
}
if (!inside) {
out.push_back(a.id);
out.push_back(b.id);
out.push_back(c.id);
poly.erase(poly.begin() + i1);
ear_found = true;
break;
}
}
if (!ear_found) {
// плохой контур (самопересечение / коллинеарность)
break;
}
}
if (poly.size() == 3) {
out.push_back(poly[0].id);
out.push_back(poly[1].id);
out.push_back(poly[2].id);
}
return out;
}
void Triangulate()
{
for(auto& [state, provs] : pixelsPerProvincesPerState)
{
for(prov& prov : provs)
{
if (prov.vertices.size() < 3)
continue;
prov.vertices = triangulate_prov(prov.vertices, width);
}
}
}
void Optimize()
{
for(auto& [state, provs] : pixelsPerProvincesPerState)
{
for(prov& prov : provs)
{
std::unordered_map<size_t, uint16_t> vertexToIndex;
vertexToIndex.reserve(prov.vertices.size());
std::vector<size_t> uniqueVertices;
uniqueVertices.reserve(prov.vertices.size());
prov.indices.clear();
for (size_t v : prov.vertices)
{
auto it = vertexToIndex.find(v);
if (it != vertexToIndex.end())
{
// вершина уже есть
prov.indices.push_back(it->second);
}
else
{
// новая вершина
uint16_t newIndex = static_cast<uint16_t>(uniqueVertices.size());
uniqueVertices.push_back(v);
vertexToIndex[v] = newIndex;
prov.indices.push_back(newIndex);
}
}
prov.vertices.swap(uniqueVertices);
}
}
}
/* ================= OUTPUT ================= */
void GenVertices(std::ofstream&out){
uint32_t statesCount = static_cast<uint32_t>(pixelsPerProvincesPerState.size());
out.write(bytes(&statesCount),4);
for(auto&[color, provs]:pixelsPerProvincesPerState)
{
out.write(bytes(&color), sizeof(BGR));
uint32_t provsCount = static_cast<uint32_t>(provs.size());
out.write(bytes(&provsCount), sizeof(uint32_t));
for(prov& prov:provs)
{
//Skip invalid meshes
if (prov.indices.size() % 3 != 0)
{
prov.vertices.clear();
prov.indices.clear();
}
uint32_t verticesCount = static_cast<uint32_t>(prov.vertices.size());
out.write(bytes(&verticesCount), sizeof(uint32_t));
for(size_t i: prov.vertices)
{
vertex v{
static_cast<float>(i%width),
static_cast<float>(i/width)
};
out.write(bytes(&v),sizeof(v));
}
uint32_t indicesCount = static_cast<uint32_t>(prov.indices.size());
out.write(bytes(&indicesCount), sizeof(uint32_t));
out.write(reinterpret_cast<char*>(prov.indices.data()), prov.indices.size() * sizeof(uint16_t));
}
}
}
/* ================= MAIN ================= */
int main(int argc,char**argv){
std::ifstream fin(argv[1],std::ios::binary);
std::ofstream fout(argv[2],std::ios::binary);
BMPFileHeader fh;
BMPInfoHeader ih;
fin.read((char*)&fh,sizeof(fh));
fin.read((char*)&ih,sizeof(ih));
width=ih.biWidth;
height=ih.biHeight;
rowSize=(width*3+3)&~3;
std::vector<uint8_t> img(rowSize*height);
std::vector<uint8_t> tmp(rowSize*height);
fin.seekg(fh.bfOffBits);
fin.read((char*)img.data(),img.size());
fin.seekg(fh.bfOffBits);
fin.read((char*)tmp.data(),tmp.size());
imageData=img.data();
tempImageData=tmp.data();
FloodFill();
FindContour();
Aproximate(1);
Triangulate();
Optimize();
GenVertices(fout);
}

View File

@ -1,6 +0,0 @@
@echo off
set ASSETS=../../assets
set COMPILE=%CD%/../../out/build/DEBUG/sdk/map/convert_map.exe
for %%F in (maps/*) do %COMPILE% "maps/%%~nF.bmp" "%ASSETS%/map/%%~nF" && echo "Compiled: %%~nF"

View File

@ -0,0 +1,121 @@
module;
#include <xxhash.h>
#include <nlohmann/json.hpp>
export module compile.json;
import utils;
import io;
import std;
using json = nlohmann::json;
export namespace ee_sdk {
class JsonCompiler
{
public:
explicit JsonCompiler(const std::filesystem::path &mappingsConfig) {
for (
const json mappingsJson = ReadJson(mappingsConfig);
auto &[key, value] : mappingsJson.items()
) {
const char* color_str = value.get_ref<const std::string&>().c_str() + 1;
std::uint8_t b = 0, g = 0, r = 0;
std::uint32_t hex = 0;
std::from_chars(color_str, color_str + 2, r, 16); color_str += 2;
std::from_chars(color_str, color_str + 2, g, 16); color_str += 2;
std::from_chars(color_str, color_str + 2, b, 16);
hex = colorToHex(r, g, b);
mappings[key] = hex;
}
}
void Compile(json &asset) const {
CompileRecursive(asset, CompileHashes);
CompileRecursive(asset, std::bind_front(&JsonCompiler::CompileMappings, this));
CompileRecursive(asset, &CompileColors);
}
private:
std::unordered_map<std::string, std::uint32_t> mappings;
static void CompileRecursive(
json &obj,
const std::function<void(const std::basic_string<char> &key, json &value)> &callback
)
{
if (obj.is_array()) {for (auto &element : obj) CompileRecursive(element, callback); return;}
if (!obj.is_object()) {callback("", obj); return;}
for (auto &[key, value] : obj.items())
{
if (value.is_object())
CompileRecursive(value, callback);
else if (value.is_array())
for (auto &element : value)
CompileRecursive(element, callback);
else callback(key, value);
}
}
//TODO: extern config
static bool NeedsCompile(const std::basic_string<char> &key) {
return
key == "tag" ||
key.ends_with("AssetID") ||
key.ends_with("Type");
}
static void CompileHashes(const std::basic_string<char> &key, json &value) {
if (!value.is_string()) return;
if (!NeedsCompile(key)) return;
const std::filesystem::path linkPath = value;
const auto linkPath8 = linkPath.generic_u8string();
std::uint32_t assetId = XXH32(linkPath8.data(), linkPath8.size(), 0);
LOG(INFO, "COMPILED HASH: Replace string [%s] with [%d] asset ID", linkPath.c_str(), assetId);
value = assetId;
}
void CompileMappings(const std::basic_string<char> &, json &value) const {
if (!value.is_string()) return;
const std::string& value_string = value.get_ref<const std::string&>();
for (const auto &[name, toValue] : mappings)
{
if (value_string == name)
{
LOG(INFO, "COMPILED MAPPING: Replace string [%hs] with [0x%X] (%d) value",
value_string.c_str(),
toValue,
toValue
);
value = toValue;
break;
}
}
}
static void CompileColors(const std::basic_string<char> &, json &value) {
if (!value.is_string()) return;
const char *colorStr = value.get_ref<const std::string&>().c_str() + 1;
const char *colorStrPointer = colorStr;
std::uint8_t b = 0, g = 0, r = 0;
std::uint32_t hex = 0;
std::from_chars(colorStrPointer, colorStrPointer + 2, r, 16); colorStrPointer += 2;
std::from_chars(colorStrPointer, colorStrPointer + 2, g, 16); colorStrPointer += 2;
std::from_chars(colorStrPointer, colorStrPointer + 2, b, 16); colorStrPointer += 2;
hex = colorToHex(r, g, b);
LOG(INFO, "COMPILED COLOR: Replace string [%hs] with 0x[%X] (RGB %d, %d, %d) color", colorStr, hex, r, g, b);
value = hex;
}
};
}

View File

@ -0,0 +1,499 @@
module;
#include <glm/glm.hpp>
export module compile.mapmesh;
import utils;
import io;
import AssetData;
import std;
struct Pt;
namespace ee_sdk {
#pragma pack(push, 1)
struct BMPFileHeader {
std::uint16_t bfType;
std::uint32_t bfSize;
std::uint16_t bfReserved1;
std::uint16_t bfReserved2;
std::uint32_t bfOffBits;
};
struct BMPInfoHeader {
std::uint32_t biSize;
std::int32_t biWidth;
std::int32_t biHeight;
std::uint16_t biPlanes;
std::uint16_t biBitCount;
std::uint32_t biCompression;
std::uint32_t biSizeImage;
std::int32_t biXPelsPerMeter;
std::int32_t biYPelsPerMeter;
std::uint32_t biClrUsed;
std::uint32_t biClrImportant;
};
#pragma pack(pop)
using BGR = glm::vec<3, std::uint8_t>;
struct BGRHash {
std::size_t operator()(const BGR& c) const noexcept {return colorToHex(c.r, c.g, c.b, true);}
};
struct prov
{
std::vector<std::size_t> vertices; // pixels indices
std::vector<std::uint16_t> indices;
};
using PixelMesh = std::unordered_map<BGR,std::vector<prov>,BGRHash>;
struct Image {
std::int32_t width;
std::int32_t height;
std::size_t rowSize;
std::vector<std::byte> image;
std::vector<std::byte> tempImage;
};
class Point {
public:
std::int32_t x, y;
std::size_t id;
Point() = default;
Point (const std::size_t i, const std::size_t width) {
x = i % width;
y = i / width;
id = i;
}
static std::int64_t Cross(const Point &a, const Point &b, const Point &c) {
return 1LL * (b.x - a.x) * (c.y - a.y) - 1LL * (b.y - a.y) * (c.x - a.x);
}
static std::int64_t SignedArea2(const std::span<Point> &p) {
std::int64_t a = 0;
for (std::size_t i = 0; i < p.size(); i++) {
const Point &p0 = p[i];
const Point& p1 = p[(i + 1) % p.size()];
a += 1LL * p0.x * p1.y - 1LL * p1.x * p0.y;
}
return a;
}
bool IsInTriangle(const Point &a, const Point &b, const Point &c) const {
const std::int64_t c1 = Cross(a, b, *this);
const std::int64_t c2 = Cross(b, c, *this);
const std::int64_t c3 = Cross(c, a, *this);
return (c1 >= 0 && c2 >= 0 && c3 >= 0) || (c1 <= 0 && c2 <= 0 && c3 <= 0);
}
};
}
export namespace ee_sdk {
class MapMeshCompiler {
public:
static ee_engine::MapMesh CompileBMP(std::span<std::byte> asset) {
ee_engine::MapMesh mesh;
// Read BMP file info from header
BMPFileHeader fileHeader;
BMPInfoHeader infoHeader;
std::memcpy(&fileHeader, asset.data(), sizeof(BMPFileHeader));
std::memcpy(&infoHeader, asset.data() + sizeof(BMPFileHeader), sizeof(BMPInfoHeader));
Image imageInfo {
.width = infoHeader.biWidth,
.height = infoHeader.biHeight,
.rowSize = static_cast<std::size_t>(infoHeader.biWidth)*3+3&~3
};
// Copy file to buffers for algorithms
imageInfo.image.resize(imageInfo.rowSize * imageInfo.height);
imageInfo.tempImage.resize(imageInfo.rowSize * imageInfo.height);
std::memcpy(imageInfo.image.data(), asset.data() + fileHeader.bfOffBits, imageInfo.image.size());
std::memcpy(imageInfo.tempImage.data(), asset.data() + fileHeader.bfOffBits, imageInfo.tempImage.size());
PixelMesh pixelMesh;
// Extract mesh from image
FloodFill(imageInfo, pixelMesh);
FindContour(imageInfo, pixelMesh);
Aproximate(imageInfo, pixelMesh, 1);
Triangulate(imageInfo, pixelMesh);
Optimize(pixelMesh);
// Provide mesh in suitable format
for(auto&[color, provs]: pixelMesh)
{
std::vector<ee_engine::MapMesh::Province> outProvinces;
for(auto&[vertices, indices]: provs)
{
ee_engine::MapMesh::Province outProvince;
//Skip invalid meshes
if (indices.size() % 3 != 0)
{
vertices.clear();
indices.clear();
}
for(std::size_t i: vertices)
{
glm::vec2 v{
static_cast<float>(i % imageInfo.width),
static_cast<float>(i / imageInfo.width)
};
outProvince.vertices.push_back(v);
}
outProvince.indices = indices;
outProvinces.push_back(outProvince);
}
mesh.regions[colorToHex(color.r, color.g, color.b, true)] = outProvinces;
}
LOG(INFO, "COMPILED MAPMESH: generated [%d] regions", mesh.regions.size());
return mesh;
}
private:
static constexpr BGR EMPTY_COLOR = {255, 255, 255};
static inline BGR getPixelSafe(const Image &image, const std::size_t r, const std::size_t c) {
if(r >= image.height || c >= image.width) return EMPTY_COLOR;
return *reinterpret_cast<const BGR*>(
image.image.data() + r * image.rowSize + c * 3
);
}
static inline BGR getPixelSafeIndex(const Image &image, const std::size_t i) {
return getPixelSafe(image, i / image.width, i % image.width);
}
static void FloodFill(Image &image, PixelMesh &mesh) {
const std::size_t size = image.width * image.height;
for(std::size_t i = 0; i < size; i++) {
BGR color= *reinterpret_cast<BGR*>(image.tempImage.data() + (i/image.width)*image.rowSize + (i%image.width)*3);
if(color == EMPTY_COLOR)
continue;
std::vector<std::size_t> prov;
std::queue<std::size_t> q;
q.push(i);
while (!q.empty()) {
std::size_t n = q.front(); q.pop();
BGR& px = *reinterpret_cast<BGR*>(image.tempImage.data() + (n/image.width)*image.rowSize + (n%image.width)*3);
if(px != color) continue;
px = EMPTY_COLOR;
prov.push_back(n);
if(n >= image.width) q.push(n - image.width);
if(n + image.width < size) q.push(n + image.width);
if(n % image.width) q.push(n - 1);
if(n % image.width + 1 < image.width) q.push(n + 1);
}
mesh[color].push_back({.vertices = prov});
}
}
static bool MooreStep(
const Image &image,
const std::size_t cur,
const std::size_t prev,
const BGR id,
std::size_t &next
) {
struct offset{int8_t x,y;};
static const offset moore[8]={
{1,0},{1,-1},{0,-1},{-1,-1},
{-1,0},{-1,1},{0,1},{1,1}
};
const std::int32_t cr = cur / image.width, cc = cur % image.width;
const std::int32_t pr = prev / image.width, pc = prev % image.width;
const std::int32_t dx = cc - pc, dy = cr - pr;
std::int32_t startDir = 0;
for(std::int32_t i = 0; i < 8; i++) {
if(moore[i].x == dx && moore[i].y == dy)
startDir=(i+6)&7;
}
for(std::int32_t k=0; k<8; k++){
const std::int32_t d = (startDir+k)&7;
const std::int32_t nr = cr + moore[d].y;
if(
const std::int32_t nc = cc + moore[d].x;
getPixelSafe(image, nr,nc) == id
){
next= static_cast<std::size_t>(nr) * image.width + static_cast<std::size_t>(nc);
return true;
}
}
return false;
}
static std::size_t FindStart(const Image &image, const std::vector<size_t>&prov, const BGR &id) {
for(const std::size_t i : prov){
const std::size_t r = i / image.width;
const std::size_t c = i % image.width;
if(getPixelSafe(image, r - 1, c) != id||
getPixelSafe(image, r + 1, c) != id||
getPixelSafe(image, r, c- 1) != id||
getPixelSafe(image, r, c+ 1) != id)
return i;
}
return std::numeric_limits<std::size_t>::max();
}
static void FindContour(const Image &image, PixelMesh &mesh) {
for(auto &provs : mesh | std::views::values){
for(auto&[vertices, indices] : provs){
if(vertices.empty()) continue;
BGR id = getPixelSafeIndex(image, vertices[0]);
std::size_t start = FindStart(image, vertices,id);
if(start == std::numeric_limits<std::size_t>::max()) continue;
std::vector<std::size_t> contour;
contour.push_back(start);
std::size_t cur=start;
std::size_t prev = (start % image.width > 0) ? start - 1 : start + 1;
const std::size_t MAX = vertices.size()*8;
for(std::size_t i = 0 ;i < MAX; i++){
std::size_t next;
if(!MooreStep(image, cur, prev, id, next)) break;
if(next == start) break;
contour.push_back(next);
prev = cur;
cur = next;
}
vertices.swap(contour);
}
}
}
static double PerpendicularDistance(
const std::size_t p,
const std::size_t a,
const std::size_t b,
const std::size_t width
)
{
const double px = p % width;
const double py = p / width;
const double ax = a % width;
const double ay = a / width;
const double bx = b % width;
const double by = b / width;
const double dx = bx - ax;
const double dy = by - ay;
if(dx == 0 && dy == 0)
return std::hypot(px - ax, py - ay);
const double num = std::abs(dy * px - dx * py + bx * ay - by * ax);
const double den = std::sqrt(dx * dx + dy * dy);
return num / den;
}
// Douglas-Peucker iterative
static void Aproximate(const Image &image, PixelMesh &mesh, const std::size_t epsilon)
{
for(auto &provs : mesh | std::views::values)
{
for(auto&[vertices, indices] : provs)
{
if(vertices.size() < 3)
continue;
const std::size_t n = vertices.size();
std::vector keep_point(n, true);
std::stack<std::pair<std::size_t, std::size_t>> st;
// в стек кладем первый и последний индекс
st.push({0, n - 1});
while(!st.empty())
{
auto [startIndex, endIndex] = st.top();
st.pop();
double dMax = 0.0;
std::size_t index = startIndex;
for(std::size_t i = startIndex + 1; i < endIndex; ++i)
{
if(keep_point[i])
{
double d = PerpendicularDistance(
vertices[i],
vertices[startIndex],
vertices[endIndex],
image.width
);
if(d > dMax)
{
dMax = d;
index = i;
}
}
}
if(dMax >= epsilon)
{
st.push({startIndex, index});
st.push({index, endIndex});
}
else
{
for(size_t j = startIndex + 1; j < endIndex; ++j)
keep_point[j] = false;
}
}
// формируем итоговый список
std::vector<std::size_t> result;
result.reserve(n);
for(std::size_t i = 0; i < n; ++i)
{
if(keep_point[i])
result.push_back(vertices[i]);
}
vertices.swap(result);
}
}
}
static std::vector<std::size_t> TriangulateProv(const std::vector<std::size_t> &prov, const std::size_t width) {
std::vector<Point> poly;
poly.reserve(prov.size());
for (const std::size_t i : prov)
poly.push_back(Point(i, width));
// CCW
if (Point::SignedArea2(poly) < 0)
std::ranges::reverse(poly);
std::vector<std::size_t> out; // a,b,c,a,b,c,...
while (poly.size() > 3) {
bool ear_found = false;
for (std::size_t i = 0; i < poly.size(); ++i) {
const std::size_t i0 = (i + poly.size() - 1) % poly.size();
const std::size_t i1 = i;
const std::size_t i2 = (i + 1) % poly.size();
const Point& a = poly[i0];
const Point& b = poly[i1];
const Point& c = poly[i2];
if (Point::Cross(a, b, c) <= 0)
continue; // не выпуклая
bool inside = false;
for (std::size_t j = 0; j < poly.size(); ++j) {
if (j == i0 || j == i1 || j == i2) continue;
if (poly[j].IsInTriangle(a, b, c)) {
inside = true;
break;
}
}
if (!inside) {
out.push_back(a.id);
out.push_back(b.id);
out.push_back(c.id);
poly.erase(poly.begin() + i1);
ear_found = true;
break;
}
}
if (!ear_found) {
// плохой контур (самопересечение / коллинеарность)
break;
}
}
if (poly.size() == 3) {
out.push_back(poly[0].id);
out.push_back(poly[1].id);
out.push_back(poly[2].id);
}
return out;
}
static void Triangulate(const Image &image, PixelMesh &pixelMesh)
{
for(auto &provs : pixelMesh | std::views::values)
{
for(auto&[vertices, indices] : provs)
{
if (vertices.size() < 3)
continue;
vertices = TriangulateProv(vertices, image.width);
}
}
}
static void Optimize(PixelMesh &pixelMesh)
{
for(auto &provs : pixelMesh | std::views::values)
{
for(auto&[vertices, indices] : provs)
{
std::unordered_map<std::size_t, std::uint16_t> vertexToIndex;
vertexToIndex.reserve(vertices.size());
std::vector<std::size_t> uniqueVertices;
uniqueVertices.reserve(vertices.size());
indices.clear();
for (std::size_t v : vertices)
{
if (auto it = vertexToIndex.find(v); it != vertexToIndex.end())
{
indices.push_back(it->second);
}
else
{
std::uint16_t newIndex = static_cast<std::uint16_t>(uniqueVertices.size());
uniqueVertices.push_back(v);
vertexToIndex[v] = newIndex;
indices.push_back(newIndex);
}
}
vertices.swap(uniqueVertices);
}
}
}
};
}

113
sdk/modules/io.cppm Normal file
View File

@ -0,0 +1,113 @@
module;
#include <nlohmann/json.hpp>
#include <rfl/capnproto.hpp>
export module io;
import utils;
import AssetData;
import std;
using json = nlohmann::json;
export namespace ee_data
{
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
Subject,
tag,
color,
regions
)
}
export namespace ee_data
{
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
GameState,
subjects
)
}
export namespace ee_assets
{
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
AssetSave,
content
)
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(
AssetMaterial,
vertexShaderAssetID,
fragmentShaderAssetID
)
}
export namespace ee_sdk {
std::optional<json> ReadJson(const std::filesystem::path &assetPath) {
std::ifstream fin(assetPath);
if (!fin.is_open()) {
LOG(ERROR, "Could not open output file: [%s]", assetPath.c_str());
return std::nullopt;
}
json data = json::parse(fin);
fin.close();
return data;
}
std::optional<std::vector<std::byte>> ReadBinary(const std::filesystem::path &assetPath) {
std::ifstream fin(assetPath, std::ios::binary);
if (!fin.is_open()) {
LOG(ERROR, "Could not open output file: [%s]", assetPath.c_str());
return std::nullopt;
}
std::vector<std::byte> data;
// Read size
fin.seekg(0, std::ios::end);
const std::streamsize size = fin.tellg();
fin.seekg(0, std::ios::beg);
data.reserve(size);
// Read content
fin.read(reinterpret_cast<char *>(data.data()), size);
fin.close();
return data;
}
template <ee_assets::Asset TAsset>
void WriteAsset(const std::filesystem::path &assetPath, const TAsset &asset) {
std::ofstream fout(assetPath, std::ios::binary);
if (!fout.is_open()) {
LOG(ERROR, "Could not open input file: [%s]", assetPath.c_str());
return;
}
const std::vector<char> data = rfl::capnproto::write(asset);
fout.write(data.data(), data.size());
fout.close();
}
constexpr std::uint32_t colorToHex(
const std::uint8_t r,
const std::uint8_t g,
const std::uint8_t b,
const bool isBGR = false
) {
if (isBGR) {
return
static_cast<std::uint32_t>(b) << 16 |
static_cast<std::uint32_t>(g) << 8 |
static_cast<std::uint32_t>(r);
}
return
static_cast<std::uint32_t>(r) << 16 |
static_cast<std::uint32_t>(g) << 8 |
static_cast<std::uint32_t>(b);
}
}

95
sdk/src/main.cpp Normal file
View File

@ -0,0 +1,95 @@
#include <cxxopts.hpp>
#include <nlohmann/json.hpp>
// IT'S NEEDED FOR MSVC COMPILER TO NOT CRASH WITH C1001
#include <rfl/capnproto.hpp>
import utils;
import AssetData;
import io;
import compile.json;
import compile.mapmesh;
import std;
using json = nlohmann::json;
using path = std::filesystem::path;
template <ee_assets::Asset TAsset>
void CompileJson(const ee_sdk::JsonCompiler &compiler, const path &relative, const path &src, const path &out) {
LOG(INFO, "Start compiling %s...", relative.c_str());
std::filesystem::create_directories(out/relative);
for (const std::filesystem::directory_entry &asset : std::filesystem::directory_iterator(src/relative)) {
if (!asset.is_regular_file()) continue;
std::optional<json> data = ee_sdk::ReadJson(asset.path());
if (!data.has_value()) continue;
compiler.Compile(*data);
ee_sdk::WriteAsset(out/relative/asset.path().stem().filename(), data->get<TAsset>());
}
}
void CompileMaps(const ee_sdk::MapMeshCompiler &compiler, const path &relative, const path &src, const path &out) {
LOG(INFO, "Start compiling %s...", relative.c_str());
std::filesystem::create_directories(out/relative);
for (const std::filesystem::directory_entry &asset : std::filesystem::directory_iterator(src/relative)) {
if (!asset.is_regular_file()) continue;
std::optional<std::vector<std::byte>> data = ee_sdk::ReadBinary(asset.path());
if (!data.has_value()) continue;
ee_sdk::WriteAsset(
out/relative/asset.path().stem().filename(),
ee_assets::AssetMapMesh{ .content = compiler.CompileBMP(*data) }
);
}
}
int main(const int argc, char **argv) {
std::uint32_t status = 0;
Logger::Init(L"/P7.Sink=Console");
Logger::RegisterThread(L"Main");
try {
cxxopts::Options options(
"Etredia Empires SDK",
"This modding kit prepares your content assets for integration into game."
);
options.add_options()
("src", "Path to your mod source assets", cxxopts::value<path>())
("o,out", "Path to compiled assets", cxxopts::value<path>()->default_value("out"))
("h,help", "Print usage");
if (
const cxxopts::ParseResult cmd = options.parse(argc, argv);
cmd.contains("help")
) {
LOG(INFO, "\n%hs", options.help().c_str());
}
else {
const path &src = cmd["src"].as<path>();
const path &out = cmd["out"].as<path>();
const ee_sdk::JsonCompiler jsonCompiler(src/"regions.json");
CompileJson<ee_assets::AssetMaterial>(jsonCompiler, "materials", src, out);
CompileJson<ee_assets::AssetSave>(jsonCompiler, "scenarios", src, out);
constexpr ee_sdk::MapMeshCompiler mapMeshCompiler;
CompileMaps(mapMeshCompiler, "map", src, out);
}
}
catch (const std::exception &e)
{
LOG(CRITICAL, "%hs", e.what());
status = 1;
}
Logger::UnregisterThread();
Logger::Release();
return status;
}

View File

@ -5,6 +5,8 @@ set(LIB utils)
# Find dependencies
find_package(Boost REQUIRED COMPONENTS lockfree)
find_package(reflectcpp CONFIG REQUIRED)
find_package(glm CONFIG REQUIRED)
# Link library
@ -12,3 +14,5 @@ add_library_target(${LIB})
target_link_libraries(${LIB} PUBLIC p7)
target_link_libraries(${LIB} PRIVATE Boost::lockfree)
target_link_libraries(${LIB} PRIVATE reflectcpp::reflectcpp)
target_link_libraries(${LIB} PRIVATE glm::glm)

View File

@ -83,6 +83,7 @@ namespace logger {
return;
}
P7_Set_Crash_Handler();
client = P7_Create_Client(options);
P7_Set_Crash_Handler();
trace = P7_Create_Trace(client, L"TC 1");
@ -159,10 +160,14 @@ namespace logger {
nullptr,
location.line(),
location.file_name(),
location.function_name(),
"", //TODO: location.function_name(),
format,
std::forward<Args>(args)...
);
if (level == ERROR || level == CRITICAL) {
Logger::client->Flush();
}
}
};
@ -202,10 +207,14 @@ namespace logger {
nullptr,
location.line(),
location.file_name(),
location.function_name(),
"", //TODO: location.function_name(),
TM(format),
std::forward<Args>(args)...
);
if (level == ERROR || level == CRITICAL) {
Logger::client->Flush();
}
}
};

View File

@ -0,0 +1,35 @@
module;
#include <glm/glm.hpp>
#include <rfl/capnproto.hpp>
export module utils:serialization;
namespace rfl {
template <glm::length_t length, typename T>
struct GlmVecImpl {
float x; float y;
static GlmVecImpl from_class(const glm::vec<length, T>& vector) noexcept {
return {vector.x,vector.y};
}
glm::vec<length, T> to_class() const { return {x, y}; }
};
}
export namespace rfl {
template <class ReaderType, class WriterType, class ProcessorsType>
struct parsing::Parser<ReaderType, WriterType, glm::vec2, ProcessorsType>
: CustomParser<ReaderType, WriterType, ProcessorsType, glm::vec2, GlmVecImpl<2, float>> {};
template <class ReaderType, class WriterType, class ProcessorsType>
struct parsing::Parser<ReaderType, WriterType, glm::vec3, ProcessorsType>
: CustomParser<ReaderType, WriterType, ProcessorsType, glm::vec3, GlmVecImpl<3, float>> {};
template <class ReaderType, class WriterType, class ProcessorsType>
struct parsing::Parser<ReaderType, WriterType, glm::vec4, ProcessorsType>
: CustomParser<ReaderType, WriterType, ProcessorsType, glm::vec4, GlmVecImpl<4, float>> {};
}
export namespace ee_utils {
}

View File

@ -4,5 +4,6 @@ export import :worker;
export import :buffers;
export import :lockfree;
export import :logger;
export import :serialization;
export namespace ee_utils {}

View File

@ -1,15 +1,24 @@
{
"dependencies": [
"cxxopts",
"boost-lockfree",
"xxhash",
"nlohmann-json",
{
"name": "reflectcpp",
"features": [
"capnproto"
]
},
"entt",
"vulkan",
"glfw3",
"glm",
{
"name": "magnum",
"features": ["glfwapplication"],
"features": [
"glfwapplication"
],
"version>=": "0"
},
{
@ -18,5 +27,7 @@
},
"rmlui",
"gtest"
]
],
"version": "0.0.1",
"name": "etredia-empires"
}