From 3e3b66a4153557c01e52f8674041e7f37b5d272d Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Sun, 27 Jul 2025 17:58:44 +0600 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D1=87?= =?UTF-8?q?=D0=B8=D0=BA=20=D0=B4=D0=B2=D0=BE=D0=B8=D1=87=D0=BD=D1=8B=D1=85?= =?UTF-8?q?=20=D1=80=D0=B5=D1=81=D1=83=D1=80=D1=81=D0=BE=D0=B2=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B5=20(Alpha)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/Client/FrustumCull.h | 119 +++++++ Src/Client/Vulkan/VulkanRenderSession.cpp | 6 +- Src/Client/Vulkan/VulkanRenderSession.hpp | 35 +- Src/Common/Abstract.hpp | 12 + Src/Server/BinaryResourceManager.cpp | 314 +++++++++++++++++- Src/Server/BinaryResourceManager.hpp | 72 ++-- assets/shaders/chunk/node.geom | 4 + assets/shaders/chunk/node.geom.bin | Bin 2264 -> 2648 bytes assets/shaders/chunk/node_opaque.frag | 14 + assets/shaders/chunk/node_opaque.frag.bin | Bin 6376 -> 6812 bytes assets/shaders/chunk/node_transparent.frag | 1 + .../shaders/chunk/node_transparent.frag.bin | Bin 1280 -> 1304 bytes assets/textures/acacia_planks.png | Bin 0 -> 3800 bytes assets/textures/grass.png~ | Bin 10253 -> 0 bytes assets/textures/jungle_planks.png | Bin 0 -> 15990 bytes assets/textures/oak_planks.png | Bin 0 -> 15180 bytes 16 files changed, 514 insertions(+), 63 deletions(-) create mode 100644 Src/Client/FrustumCull.h create mode 100644 assets/textures/acacia_planks.png delete mode 100644 assets/textures/grass.png~ create mode 100644 assets/textures/jungle_planks.png create mode 100644 assets/textures/oak_planks.png diff --git a/Src/Client/FrustumCull.h b/Src/Client/FrustumCull.h new file mode 100644 index 0000000..22ba667 --- /dev/null +++ b/Src/Client/FrustumCull.h @@ -0,0 +1,119 @@ +// https://gist.github.com/podgorskiy/e698d18879588ada9014768e3e82a644 + +#include + +class Frustum +{ +public: + Frustum() {} + + // m = ProjectionMatrix * ViewMatrix + Frustum(glm::mat4 m); + + // http://iquilezles.org/www/articles/frustumcorrect/frustumcorrect.htm + bool IsBoxVisible(const glm::vec3& minp, const glm::vec3& maxp) const; + +private: + enum Planes + { + Left = 0, + Right, + Bottom, + Top, + Near, + Far, + Count, + Combinations = Count * (Count - 1) / 2 + }; + + template + struct ij2k + { + enum { k = i * (9 - i) / 2 + j - 1 }; + }; + + template + glm::vec3 intersection(const glm::vec3* crosses) const; + + glm::vec4 m_planes[Count]; + glm::vec3 m_points[8]; +}; + +inline Frustum::Frustum(glm::mat4 m) +{ + m = glm::transpose(m); + m_planes[Left] = m[3] + m[0]; + m_planes[Right] = m[3] - m[0]; + m_planes[Bottom] = m[3] + m[1]; + m_planes[Top] = m[3] - m[1]; + m_planes[Near] = m[3] + m[2]; + m_planes[Far] = m[3] - m[2]; + + glm::vec3 crosses[Combinations] = { + glm::cross(glm::vec3(m_planes[Left]), glm::vec3(m_planes[Right])), + glm::cross(glm::vec3(m_planes[Left]), glm::vec3(m_planes[Bottom])), + glm::cross(glm::vec3(m_planes[Left]), glm::vec3(m_planes[Top])), + glm::cross(glm::vec3(m_planes[Left]), glm::vec3(m_planes[Near])), + glm::cross(glm::vec3(m_planes[Left]), glm::vec3(m_planes[Far])), + glm::cross(glm::vec3(m_planes[Right]), glm::vec3(m_planes[Bottom])), + glm::cross(glm::vec3(m_planes[Right]), glm::vec3(m_planes[Top])), + glm::cross(glm::vec3(m_planes[Right]), glm::vec3(m_planes[Near])), + glm::cross(glm::vec3(m_planes[Right]), glm::vec3(m_planes[Far])), + glm::cross(glm::vec3(m_planes[Bottom]), glm::vec3(m_planes[Top])), + glm::cross(glm::vec3(m_planes[Bottom]), glm::vec3(m_planes[Near])), + glm::cross(glm::vec3(m_planes[Bottom]), glm::vec3(m_planes[Far])), + glm::cross(glm::vec3(m_planes[Top]), glm::vec3(m_planes[Near])), + glm::cross(glm::vec3(m_planes[Top]), glm::vec3(m_planes[Far])), + glm::cross(glm::vec3(m_planes[Near]), glm::vec3(m_planes[Far])) + }; + + m_points[0] = intersection(crosses); + m_points[1] = intersection(crosses); + m_points[2] = intersection(crosses); + m_points[3] = intersection(crosses); + m_points[4] = intersection(crosses); + m_points[5] = intersection(crosses); + m_points[6] = intersection(crosses); + m_points[7] = intersection(crosses); + +} + +// http://iquilezles.org/www/articles/frustumcorrect/frustumcorrect.htm +inline bool Frustum::IsBoxVisible(const glm::vec3& minp, const glm::vec3& maxp) const +{ + // check box outside/inside of frustum + for (int i = 0; i < Count; i++) + { + if ((glm::dot(m_planes[i], glm::vec4(minp.x, minp.y, minp.z, 1.0f)) < 0.0) && + (glm::dot(m_planes[i], glm::vec4(maxp.x, minp.y, minp.z, 1.0f)) < 0.0) && + (glm::dot(m_planes[i], glm::vec4(minp.x, maxp.y, minp.z, 1.0f)) < 0.0) && + (glm::dot(m_planes[i], glm::vec4(maxp.x, maxp.y, minp.z, 1.0f)) < 0.0) && + (glm::dot(m_planes[i], glm::vec4(minp.x, minp.y, maxp.z, 1.0f)) < 0.0) && + (glm::dot(m_planes[i], glm::vec4(maxp.x, minp.y, maxp.z, 1.0f)) < 0.0) && + (glm::dot(m_planes[i], glm::vec4(minp.x, maxp.y, maxp.z, 1.0f)) < 0.0) && + (glm::dot(m_planes[i], glm::vec4(maxp.x, maxp.y, maxp.z, 1.0f)) < 0.0)) + { + return false; + } + } + + // check frustum outside/inside box + int out; + out = 0; for (int i = 0; i<8; i++) out += ((m_points[i].x > maxp.x) ? 1 : 0); if (out == 8) return false; + out = 0; for (int i = 0; i<8; i++) out += ((m_points[i].x < minp.x) ? 1 : 0); if (out == 8) return false; + out = 0; for (int i = 0; i<8; i++) out += ((m_points[i].y > maxp.y) ? 1 : 0); if (out == 8) return false; + out = 0; for (int i = 0; i<8; i++) out += ((m_points[i].y < minp.y) ? 1 : 0); if (out == 8) return false; + out = 0; for (int i = 0; i<8; i++) out += ((m_points[i].z > maxp.z) ? 1 : 0); if (out == 8) return false; + out = 0; for (int i = 0; i<8; i++) out += ((m_points[i].z < minp.z) ? 1 : 0); if (out == 8) return false; + + return true; +} + +template +inline glm::vec3 Frustum::intersection(const glm::vec3* crosses) const +{ + float D = glm::dot(glm::vec3(m_planes[a]), crosses[ij2k::k]); + glm::vec3 res = glm::mat3(crosses[ij2k::k], -crosses[ij2k::k], crosses[ij2k::k]) * + glm::vec3(m_planes[a].w, m_planes[b].w, m_planes[c].w); + return res * (-1.0f / D); +} diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index eda796e..a687f54 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -1190,7 +1190,11 @@ void VulkanRenderSession::drawWorld(GlobalTime gTime, float dTime, VkCommandBuff MainAtlas_LightMap_PipelineLayout, 0, 2, (const VkDescriptorSet[]) {MainAtlasDescriptor, VoxelLightMapDescriptor}, 0, nullptr); - PCO.Model = glm::mat4(1); + { + // glm::vec4 offset = glm::inverse(Quat)*glm::vec4(0, 0, -64, 1); + // PCO.Model = glm::translate(glm::mat4(1), glm::vec3(offset)); + PCO.Model = glm::mat4(1); + } VkBuffer vkBuffer = VKCTX->TestQuad; VkDeviceSize vkOffsets = 0; diff --git a/Src/Client/Vulkan/VulkanRenderSession.hpp b/Src/Client/Vulkan/VulkanRenderSession.hpp index 351c7fe..8b1a759 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.hpp +++ b/Src/Client/Vulkan/VulkanRenderSession.hpp @@ -13,6 +13,8 @@ #include "Abstract.hpp" #include "TOSLib.hpp" #include "VertexPool.hpp" +#include "glm/fwd.hpp" +#include "../FrustumCull.h" /* У движка есть один текстурный атлас VK_IMAGE_VIEW_TYPE_2D_ARRAY(RGBA_UINT) и к нему Storage с инфой о положении текстур @@ -192,28 +194,16 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { if(iterWorld == ChunkMesh.end()) return {}; + Frustum fr(projView); + for(int z = -distance; z <= distance; z++) { for(int y = -distance; y <= distance; y++) { for(int x = -distance; x <= distance; x++) { Pos::GlobalRegion region = center + Pos::GlobalRegion(x, y, z); - glm::vec3 begin = glm::vec3(region - x64offset); + glm::vec3 begin = glm::vec3(region - x64offset) * 64.f; + glm::vec3 end = begin + glm::vec3(64.f); - bool isVisible = false; - for(int index = 0; index < 8; index++) { - glm::vec4 vec((begin+glm::vec3(index&1, (index>>1)&1, (index>>2)&1))*64.f, 1); - glm::vec4 temp = projView * vec; - temp /= temp.w; - - if(temp.x >= -1 && temp.x <= 1 - && temp.y >= -1 && temp.y <= 1 - && temp.z >= 0 && temp.z <= 1 - ) { - isVisible = true; - break; - } - } - - if(!isVisible) + if(!fr.IsBoxVisible(begin, end)) continue; auto iterRegion = iterWorld->second.find(region); @@ -223,12 +213,19 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent { Pos::GlobalChunk local = Pos::GlobalChunk(region) << 2; for(size_t index = 0; index < iterRegion->second.size(); index++) { + Pos::bvec4u localPos; + localPos.unpack(index); + + glm::vec3 chunkPos = begin+glm::vec3(localPos)*16.f; + if(!fr.IsBoxVisible(chunkPos, chunkPos+glm::vec3(16))) + continue; + auto &chunk = iterRegion->second[index]; if(chunk.VoxelPointer) - vertexVoxels.emplace_back(local+Pos::GlobalChunk(Pos::bvec4u().unpack(index)), VertexPool_Voxels.map(chunk.VoxelPointer), chunk.VoxelPointer.VertexCount); + vertexVoxels.emplace_back(local+Pos::GlobalChunk(localPos), VertexPool_Voxels.map(chunk.VoxelPointer), chunk.VoxelPointer.VertexCount); if(chunk.NodePointer) - vertexNodes.emplace_back(local+Pos::GlobalChunk(Pos::bvec4u().unpack(index)), VertexPool_Nodes.map(chunk.NodePointer), chunk.NodePointer.VertexCount); + vertexNodes.emplace_back(local+Pos::GlobalChunk(localPos), VertexPool_Nodes.map(chunk.NodePointer), chunk.NodePointer.VertexCount); } } } diff --git a/Src/Common/Abstract.hpp b/Src/Common/Abstract.hpp index c69cbc2..68c3947 100644 --- a/Src/Common/Abstract.hpp +++ b/Src/Common/Abstract.hpp @@ -549,4 +549,16 @@ struct hash> { } }; +template <> +struct hash { + std::size_t operator()(const LV::Hash_t& hash) const noexcept { + std::size_t v = 14695981039346656037ULL; + for (const auto& byte : hash) { + v ^= static_cast(byte); + v *= 1099511628211ULL; + } + return v; + } +}; + } diff --git a/Src/Server/BinaryResourceManager.cpp b/Src/Server/BinaryResourceManager.cpp index b1c01de..418fe35 100644 --- a/Src/Server/BinaryResourceManager.cpp +++ b/Src/Server/BinaryResourceManager.cpp @@ -1,3 +1,7 @@ +#include "Common/Abstract.hpp" +#include "Server/Abstract.hpp" +#include +#include #define BOOST_ASIO_HAS_IO_URING 1 #include "BinaryResourceManager.hpp" #include @@ -5,19 +9,316 @@ #include #include #include +#include namespace LV::Server { +struct UriParse { + std::string Protocol, Path; + std::string getFull() const { + return Protocol + "://" + Path; + } +}; + +UriParse parseUri(const std::string& uri) { + size_t pos = uri.find("://"); + + if(pos == std::string::npos) + return {"assets", uri}; + else + return {uri.substr(0, pos), uri.substr(pos+3)}; +} + +struct Resource { + // Файл загруженный с диска + std::shared_ptr Loaded; + // Источник + Hash_t Hash; + size_t LastUsedTime = 0; + EnumBinResource Type; + ResourceId_t ResId; + UriParse Uri; + + std::string LastError; +}; + +void BinaryResourceManager::run() { + TOS::Logger LOG = "BinaryResourceManager::run"; + LOG.debug() << "Поток чтения двоичных ресурсов запущен"; + + // Ресурсы - кешированные в оперативную память или в процессе загрузки + std::unordered_map> knownResource[(int) EnumBinResource::MAX_ENUM]; + // Трансляция идентификаторов в Uri (противоположность KnownResource) + std::vector resIdToUri[(int) EnumBinResource::MAX_ENUM]; + // Новые полученные идентификаторы и те, чьи ресурсы нужно снова загрузить + std::vector newRes[(int) EnumBinResource::MAX_ENUM]; + // Пути поиска ресурсов + std::vector assets; + // Запросы хешей + std::vector binToHash[(int) EnumBinResource::MAX_ENUM]; + // + std::unordered_map> hashToResource; + std::vector hashToLoad; + + auto lambdaLoadResource = [&](UriParse uri, int type) -> std::variant, std::string> + { + // std::shared_ptr resObj = std::make_shared(); + // knownResource[type][resId] = resObj; + + if(uri.Protocol != "assets") + return "Протокол не поддерживается"; + else { + auto var = loadFile(assets, uri.Path, (EnumBinResource) type); + + if(var.index() == 0) { + std::shared_ptr resource = std::get<0>(var); + resource->calcHash(); + return resource; + } else { + return std::get<1>(var); + } + } + }; + + try { + while(!NeedShutdown) { + bool hasWork = false; + auto lock = Local.lock(); + + for(int type = 0; type < (int) EnumBinResource::MAX_ENUM; type++) { + for(ResourceId_t iter = 0; iter < lock->ResIdToUri[type].size(); iter++) { + newRes[type].push_back(resIdToUri[type].size()+iter); + } + + resIdToUri[type].insert(resIdToUri[type].end(), lock->ResIdToUri[type].begin(), lock->ResIdToUri[type].end()); + resIdToUri[type].clear(); + + binToHash[type].insert(binToHash[type].end(), lock->BinToHash[type].begin(), lock->BinToHash[type].end()); + lock->BinToHash[type].clear(); + } + + bool assetsUpdate = false; + if(!lock->Assets.empty()) { + // Требуется пересмотр всех ресурсов + assets = std::move(lock->Assets); + assetsUpdate = true; + } + + std::vector hashRequest = std::move(lock->Hashes); + + lock.unlock(); + + if(!hashRequest.empty()) { + std::vector> hashToResourceOut; + + for(Hash_t hash : hashRequest) { + auto iter = hashToResource.find(hash); + + if(iter == hashToResource.end()) + continue; + + if(!iter->second->Loaded) { + hashToLoad.push_back(hash); + continue; + } + + iter->second->LastUsedTime = TOS::Time::getSeconds(); + hashToResourceOut.push_back(iter->second->Loaded); + } + + auto outLock = Out.lock(); + outLock->HashToResource.insert(outLock->HashToResource.end(), hashToResourceOut.begin(), hashToResourceOut.end()); + } + + { + std::unordered_map binToHashOut[(int) EnumBinResource::MAX_ENUM]; + + for(int type = 0; type < (int) EnumBinResource::MAX_ENUM; type++) { + for(ResourceId_t resId : binToHash[type]) { + std::shared_ptr resource = knownResource[type][resId]; + if(!resource) + continue; // Идентификатор не известен + + binToHashOut[type][resId] = resource->Hash; + } + } + } + + // Загрузка ресурсов по новым идентификаторам + for(int type = 0; type < (int) EnumBinResource::MAX_ENUM; type++) { + if(newRes[type].empty()) + continue; + + hasWork = true; + + while(!newRes[type].empty()) { + ResourceId_t resId = newRes[type].back(); + newRes[type].pop_back(); + + assert(resId < resIdToUri[type].size()); + UriParse uri = parseUri(resIdToUri[type][resId]); + std::shared_ptr resObj = std::make_shared(); + resObj->LastUsedTime = TOS::Time::getSeconds(); + resObj->Type = (EnumBinResource) type; + resObj->ResId = resId; + resObj->Uri = uri; + + auto var = lambdaLoadResource(uri, type); + + if(var.index() == 0) { + resObj->Loaded = std::get<0>(var); + resObj->Hash = resObj->Loaded->Hash; + hashToResource[resObj->Hash] = resObj; + } else { + std::fill(resObj->Hash.begin(), resObj->Hash.end(), 0); + resObj->LastError = std::get<1>(var); + } + + knownResource[type][resId] = resObj; + } + } + + while(!hashToLoad.empty()) { + Hash_t hash = hashToLoad.back(); + hashToLoad.pop_back(); + + auto iter = hashToResource.find(hash); + if(iter == hashToResource.end()) + continue; + + std::shared_ptr &res = iter->second; + + if(res->Loaded) { + Out.lock()->HashToResource.push_back(res->Loaded); + } else { + if(!res->LastError.empty()) + continue; + + auto var = lambdaLoadResource(res->Uri, (int) res->Type); + if(var.index() == 0) { + hasWork = true; + res->Loaded = std::get<0>(var); + res->LastUsedTime = TOS::Time::getSeconds(); + res->LastError.clear(); + + if(res->Hash != res->Loaded->Hash) { + // Хеш изменился + Out.lock()->BinToHash[(int) res->Type][res->ResId] = res->Loaded->Hash; + res->Hash = res->Loaded->Hash; + std::shared_ptr resObj = res; + hashToResource.erase(iter); + hashToResource[hash] = resObj; + } else { + Out.lock()->HashToResource.push_back(res->Loaded); + } + } else { + res->LastError = std::get<1>(var); + } + } + } + + // Удаляем долго не используемые ресурсы + { + size_t now = TOS::Time::getSeconds(); + for(int type = 0; type < (int) EnumBinResource::MAX_ENUM; type++) { + for(auto& resObj : knownResource[type]) { + if(now - resObj.second->LastUsedTime > 30) + resObj.second->Loaded = nullptr; + } + } + } + + if(assetsUpdate) { + hashToLoad.clear(); + hashToResource.clear(); + + for(int type = 0; type < (int) EnumBinResource::MAX_ENUM; type++) { + for(auto& [resId, resObj] : knownResource[type]) { + auto var = lambdaLoadResource(resObj->Uri, type); + + if(var.index() == 0) { + hasWork = true; + resObj->Loaded = std::get<0>(var); + resObj->LastUsedTime = TOS::Time::getSeconds(); + resObj->LastError.clear(); + + if(resObj->Hash != resObj->Loaded->Hash) { + // Хеш изменился + Out.lock()->BinToHash[type][resId] = resObj->Loaded->Hash; + resObj->Hash = resObj->Loaded->Hash; + } + + hashToResource[resObj->Hash] = resObj; + } else { + resObj->LastError = std::get<1>(var); + } + } + } + } + + if(!hasWork) + TOS::Time::sleep3(10); + } + } catch(const std::exception& exc) { + LOG.error() << exc.what(); + } + + NeedShutdown = true; + LOG.debug() << "Поток чтения двоичных ресурсов остановлен"; +} + +std::variant, std::string> +BinaryResourceManager::loadFile(const std::vector& assets, const std::string& path, EnumBinResource type) +{ + try { + std::shared_ptr file = std::make_shared(); + + std::string firstPath; + + switch(type) { + case EnumBinResource::Texture: firstPath = "texture"; break; + case EnumBinResource::Animation: firstPath = "animation"; break; + case EnumBinResource::Model: firstPath = "model"; break; + case EnumBinResource::Sound: firstPath = "sound"; break; + case EnumBinResource::Font: firstPath = "font"; break; + default: assert(false); + } + + for(fs::path assetsPath : assets) { + fs::path p = assetsPath / firstPath / path; + if(!fs::exists(p)) + continue; + + std::ifstream fd(p); + + if(!fd) + MAKE_ERROR("Не удалось открыть файл: " << p.string()); + + fd.seekg(0, std::ios::end); + std::streamsize size = fd.tellg(); + fd.seekg(0, std::ios::beg); + file->Data.resize(size); + fd.read((char*) file->Data.data(), size); + + return file; + } + + MAKE_ERROR("Файл не найден"); + } catch(const std::exception& exc) { + return exc.what(); + } +} BinaryResourceManager::BinaryResourceManager(asio::io_context &ioc) - : AsyncObject(ioc) + : AsyncObject(ioc), Thread(&BinaryResourceManager::run, this) { } BinaryResourceManager::~BinaryResourceManager() { - + NeedShutdown = true; + Thread.join(); } void BinaryResourceManager::recheckResources(std::vector assets /* Пути до активных папок assets */) { @@ -50,15 +351,6 @@ void BinaryResourceManager::update(float dtime) { // } } -BinaryResourceManager::UriParse BinaryResourceManager::parseUri(const std::string &uri) { - size_t pos = uri.find("://"); - - if(pos == std::string::npos) - return {"assets", uri}; - else - return {uri.substr(0, pos), uri.substr(pos+3)}; -} - // coro<> BinaryResourceManager::checkResource_Assets(ResourceId_t id, fs::path path, std::shared_ptr res) { // try { // asio::stream_file fd(IOC, path, asio::stream_file::flags::read_only); diff --git a/Src/Server/BinaryResourceManager.hpp b/Src/Server/BinaryResourceManager.hpp index 034f991..b324b3b 100644 --- a/Src/Server/BinaryResourceManager.hpp +++ b/Src/Server/BinaryResourceManager.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include "Abstract.hpp" @@ -23,62 +24,58 @@ namespace fs = std::filesystem; Чтение происходит отдельным потоком, переконвертацию пока предлагаю в realtime. Хэш вычисляется после чтения и может быть иным чем при прошлом чтении (ресурс изменили наживую) - тогда обычным оповещениям клиентам дойдёт новая версия + тогда обычным оповещением клиентам дойдёт новая версия Подержать какое-то время ресурс в памяти + + + + Ключи сопоставляются с идентификаторами и с хешеми. При появлении нового ключа, + ему выдаётся идентификатор и делается запрос на загрузку ресурса для вычисления хеша. + recheckResources делает повторную загрузку всех ресурсов для проверки изменения хешей. + + */ class BinaryResourceManager : public AsyncObject { -public: - private: - struct Resource { - // Файл загруженный с диска - std::shared_ptr Loaded; - // Источник - std::string Uri; - bool IsLoading = false; - size_t LastUsedTime = 0; - - std::string LastError; - }; - - struct UriParse { - std::string Protocol, Path; - - std::string getFull() const { - return Protocol + "://" + Path; - } - }; - // Поток сервера // Последовательная регистрация ресурсов ResourceId_t NextId[(int) EnumBinResource::MAX_ENUM] = {0}; // Известные ресурсы, им присвоен идентификатор + // Нужно для потока загрузки std::unordered_map KnownResource[(int) EnumBinResource::MAX_ENUM]; - std::unordered_map> ResourcesInfo[(int) EnumBinResource::MAX_ENUM]; // Местные потоки struct LocalObj_t { - // Ресурсы - кешированные в оперативную память или в процессе загрузки - std::map> InMemory[(int) EnumBinResource::MAX_ENUM]; - // Кому-то нужно сопоставить идентификаторы с хэшами + // Трансляция идентификаторов в Uri (противоположность KnownResource) + // Передаётся в отдельный поток + std::vector ResIdToUri[(int) EnumBinResource::MAX_ENUM]; + // Кому-то нужно сопоставить идентификаторы с хешами std::vector BinToHash[(int) EnumBinResource::MAX_ENUM]; // Запрос ресурсов, по которым потоки загружают ресурсы с диска std::vector Hashes; + // Передача новых путей поиска ресурсов в другой поток + std::vector Assets; }; TOS::SpinlockObject Local; public: // Подготовленные оповещения о ресурсах struct OutObj_t { - std::unordered_map BinToHash[(int) EnumBinResource::MAX_ENUM]; - std::vector> HashToResource; + std::unordered_map BinToHash[(int) EnumBinResource::MAX_ENUM]; + std::vector> HashToResource; }; private: TOS::SpinlockObject Out; + volatile bool NeedShutdown = false; + std::thread Thread; + + void run(); + std::variant, std::string> + loadFile(const std::vector& assets, const std::string& path, EnumBinResource type); public: // Если ресурс будет обновлён или загружен будет вызвано onResourceUpdate @@ -89,7 +86,17 @@ public: void recheckResources(std::vector assets /* Пути до активных папок assets */); // Выдаёт или назначает идентификатор для ресурса ResourceId_t getResource(const std::string& uri, EnumBinResource bin) { - std::string fullUri = parseUri(uri).getFull(); + std::string fullUri; + + { + size_t pos = uri.find("://"); + + if(pos == std::string::npos) + fullUri = "assets://" + uri; + else + fullUri = uri; + } + int index = (int) bin; auto &container = KnownResource[index]; @@ -98,6 +105,10 @@ public: assert(NextId[index] != ResourceId_t(-1)); ResourceId_t nextId = NextId[index]++; container.insert(iter, {fullUri, nextId}); + + auto lock = Local.lock(); + lock->ResIdToUri[index].push_back(uri); + return nextId; } @@ -135,9 +146,6 @@ public: // Серверный такт void update(float dtime); -protected: - UriParse parseUri(const std::string &uri); - }; diff --git a/assets/shaders/chunk/node.geom b/assets/shaders/chunk/node.geom index b85ee3e..fa2cb47 100644 --- a/assets/shaders/chunk/node.geom +++ b/assets/shaders/chunk/node.geom @@ -11,16 +11,20 @@ layout(location = 0) in GeometryObj { layout(location = 0) out FragmentObj { vec3 GeoPos; // Реальная позиция в мире + vec3 Normal; flat uint Texture; // Текстура vec2 UV; } Fragment; void main() { + vec3 normal = normalize(cross(Geometry[1].GeoPos-Geometry[0].GeoPos, Geometry[2].GeoPos-Geometry[0].GeoPos)); + for(int iter = 0; iter < 3; iter++) { gl_Position = gl_in[iter].gl_Position; Fragment.GeoPos = Geometry[iter].GeoPos; Fragment.Texture = Geometry[iter].Texture; Fragment.UV = Geometry[iter].UV; + Fragment.Normal = normal; EmitVertex(); } diff --git a/assets/shaders/chunk/node.geom.bin b/assets/shaders/chunk/node.geom.bin index 418b49b08b7bc835016404d5b20a6d9232409163..cc30b5c9f419c879cd891c8f851501f180142c0b 100644 GIT binary patch literal 2648 zcmZ{l=}uEo5XTSDvahna02WbEQQQDg1VNUfRzO+4HV8N5n$nW?V!%%(K9R5BW9WMr zH8Ju3yY~*cA#j>uX8yCBIWyBzQ$1AY(p9d>wYW_eoeo#6j}unws>HLo_1tJB*c}}k ze}G4;tAT~KHrEhpDe3#$ei6J~^hh)zdJ*b14tq~1r$Sj`7~igFZ2bw_Wy#!7qYrg_ zR}m@Ks5W7c&LWBthUn^DI2}XZ5yrbZ-> z%ZZ6>+%MaF!9Q5=E5UYghuR_=eZ_WID4f{R>NE4=q~-Zo{H%JwIpMvDdce=C2itk| zU^}~>RJc33CtN6IGA-Yw^7h`oR}5?>V4UPkz^G*%`HqXQbDQgF)TW8$(=w zbnvG({K++-x6yBW@xwL}$FxW$XP%*ssMl@|gXOWa{gZ*N8Z8yC=@un4!I6R5H1F$9-|^U;~oaAB({T zaoBqA2vVy{Rh zmc2>F_DRQPe*Kcqx`Xc=HoN1hWMaXab!*hisE-T6r~#j5y%F1>h#oJBGNKuEioSp& z(-Z3YquyN?r-$%g>cvg*VG#%WEphB{dd1$~7LntddceNljWNClf8~RILqslOkuCO# zWMb(7JNHz?9)er1*tusSY;(GUU3@NL@A2Uc==fOPS;>}{InRmc?X1Z1(wmneY~-C6 zr+@6L`a9iJcC5av59nHj;YwspzW_CMCPn`Ndi2ax literal 2264 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 literal 0 HcmV?d00001 diff --git a/assets/textures/grass.png~ b/assets/textures/grass.png~ deleted file mode 100644 index 3ec069ce303798e6c58657e59686b681b19d768c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10253 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 T2w=;e00000NkvXXu0mjfFoh(3 diff --git a/assets/textures/jungle_planks.png b/assets/textures/jungle_planks.png new file mode 100644 index 0000000000000000000000000000000000000000..1ecc2e641c9a90d7d0a704679a029c0ccfe33327 GIT binary patch literal 15990 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 T`)`360Fe4EFIFdF82o<#3>^3s literal 0 HcmV?d00001 diff --git a/assets/textures/oak_planks.png b/assets/textures/oak_planks.png new file mode 100644 index 0000000000000000000000000000000000000000..73ffcbdd23b8bc1d1d321de4561731b03b6e5836 GIT binary patch literal 15180 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

<7&10ID?v$*G>;Zb{7sQ}^7)(;Dpc-5 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 literal 0 HcmV?d00001