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 418b49b..cc30b5c 100644 Binary files a/assets/shaders/chunk/node.geom.bin and b/assets/shaders/chunk/node.geom.bin differ diff --git a/assets/shaders/chunk/node_opaque.frag b/assets/shaders/chunk/node_opaque.frag index 708464c..f7c8c2a 100644 --- a/assets/shaders/chunk/node_opaque.frag +++ b/assets/shaders/chunk/node_opaque.frag @@ -2,6 +2,7 @@ layout(location = 0) in FragmentObj { vec3 GeoPos; // Реальная позиция в мире + vec3 Normal; flat uint Texture; // Текстура vec2 UV; } Fragment; @@ -69,8 +70,21 @@ vec4 atlasColor(uint texId, vec2 uv) return color; } +vec3 blendOverlay(vec3 base, vec3 blend) { + vec3 result; + for (int i = 0; i < 3; ++i) { + if (base[i] <= 0.5) + result[i] = 2.0 * base[i] * blend[i]; + else + result[i] = 1.0 - 2.0 * (1.0 - base[i]) * (1.0 - blend[i]); + } + return result; +} + void main() { Frame = atlasColor(Fragment.Texture, Fragment.UV); + Frame.xyz *= max(0.2f, dot(Fragment.Normal, normalize(vec3(0.5, 1, 0.8)))); + // Frame = vec4(blendOverlay(vec3(Frame), vec3(Fragment.GeoPos/64.f)), Frame.w); if(Frame.w == 0) discard; } \ No newline at end of file diff --git a/assets/shaders/chunk/node_opaque.frag.bin b/assets/shaders/chunk/node_opaque.frag.bin index 8d4a86e..bb2c2f5 100644 Binary files a/assets/shaders/chunk/node_opaque.frag.bin and b/assets/shaders/chunk/node_opaque.frag.bin differ diff --git a/assets/shaders/chunk/node_transparent.frag b/assets/shaders/chunk/node_transparent.frag index cb09d72..5d241a3 100644 --- a/assets/shaders/chunk/node_transparent.frag +++ b/assets/shaders/chunk/node_transparent.frag @@ -2,6 +2,7 @@ layout(location = 0) in FragmentObj { vec3 GeoPos; // Реальная позиция в мире + vec3 Normal; flat uint Texture; // Текстура vec2 UV; } Fragment; diff --git a/assets/shaders/chunk/node_transparent.frag.bin b/assets/shaders/chunk/node_transparent.frag.bin index 3541ada..8a5b3c5 100644 Binary files a/assets/shaders/chunk/node_transparent.frag.bin and b/assets/shaders/chunk/node_transparent.frag.bin differ diff --git a/assets/textures/acacia_planks.png b/assets/textures/acacia_planks.png new file mode 100644 index 0000000..01d81c9 Binary files /dev/null and b/assets/textures/acacia_planks.png differ diff --git a/assets/textures/grass.png~ b/assets/textures/grass.png~ deleted file mode 100644 index 3ec069c..0000000 Binary files a/assets/textures/grass.png~ and /dev/null differ diff --git a/assets/textures/jungle_planks.png b/assets/textures/jungle_planks.png new file mode 100644 index 0000000..1ecc2e6 Binary files /dev/null and b/assets/textures/jungle_planks.png differ diff --git a/assets/textures/oak_planks.png b/assets/textures/oak_planks.png new file mode 100644 index 0000000..73ffcbd Binary files /dev/null and b/assets/textures/oak_planks.png differ