diff --git a/doc/en/scripting/builtins/libworld.md b/doc/en/scripting/builtins/libworld.md index fc18773e..97c6fdee 100644 --- a/doc/en/scripting/builtins/libworld.md +++ b/doc/en/scripting/builtins/libworld.md @@ -47,4 +47,18 @@ world.is_night() -> bool -- Returns the total number of chunks loaded into memory world.count_chunks() -> int + +-- Returns the compressed chunk data to send. +-- Currently includes: +-- 1. Voxel data (id and state) +-- 2. Voxel metadata (fields) +world.get_chunk_data(x: int, z: int) -> Bytearray + +-- Modifies the chunk based on the compressed data. +-- Returns true if the chunk exists. +world.set_chunk_data( + x: int, z: int, + -- compressed chunk data + data: Bytearray +) -> bool ``` diff --git a/doc/ru/scripting/builtins/libworld.md b/doc/ru/scripting/builtins/libworld.md index 0467d784..a7e0e5a6 100644 --- a/doc/ru/scripting/builtins/libworld.md +++ b/doc/ru/scripting/builtins/libworld.md @@ -46,4 +46,18 @@ world.is_night() -> bool -- Возвращает общее количество загруженных в память чанков world.count_chunks() -> int + +-- Возвращает сжатые данные чанка для отправки. +-- На данный момент включает: +-- 1. Данные вокселей (id и состояние) +-- 2. Метаданные (поля) вокселей +world.get_chunk_data(x: int, z: int) -> Bytearray + +-- Изменяет чанк на основе сжатых данных. +-- Возвращает true если чанк существует. +world.set_chunk_data( + x: int, z: int, + -- сжатые данные чанка + data: Bytearray +) -> bool ``` diff --git a/src/graphics/render/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp index da40d822..6e6b4bd3 100644 --- a/src/graphics/render/ChunksRenderer.cpp +++ b/src/graphics/render/ChunksRenderer.cpp @@ -135,7 +135,7 @@ const Mesh* ChunksRenderer::getOrRender( if (found == meshes.end()) { return render(chunk, important); } - if (chunk->flags.modified) { + if (chunk->flags.modified && chunk->flags.lighted) { render(chunk, important); } return found->second.mesh.get(); @@ -149,9 +149,17 @@ const Mesh* ChunksRenderer::retrieveChunk( size_t index, const Camera& camera, Shader& shader, bool culling ) { auto chunk = chunks.getChunks()[index]; - if (chunk == nullptr || !chunk->flags.lighted) { + if (chunk == nullptr) { return nullptr; } + if (!chunk->flags.lighted) { + const auto& found = meshes.find({chunk->x, chunk->z}); + if (found == meshes.end()) { + return nullptr; + } else { + return found->second.mesh.get(); + } + } float distance = glm::distance( camera.position, glm::vec3( diff --git a/src/logic/scripting/lua/libs/libworld.cpp b/src/logic/scripting/lua/libs/libworld.cpp index 1acc1ad7..5d936842 100644 --- a/src/logic/scripting/lua/libs/libworld.cpp +++ b/src/logic/scripting/lua/libs/libworld.cpp @@ -4,8 +4,6 @@ #include "api_lua.hpp" #include "assets/AssetsLoader.hpp" -#include "coders/compression.hpp" -#include "coders/gzip.hpp" #include "coders/json.hpp" #include "engine/Engine.hpp" #include "files/engine_paths.hpp" @@ -14,6 +12,7 @@ #include "voxels/Chunk.hpp" #include "voxels/Chunks.hpp" #include "voxels/GlobalChunks.hpp" +#include "voxels/compressed_chunks.hpp" #include "world/Level.hpp" #include "world/World.hpp" #include "logic/LevelController.hpp" @@ -123,122 +122,57 @@ static int l_get_generator(lua::State* L) { } static int l_get_chunk_data(lua::State* L) { - int x = (int)lua::tointeger(L, 1); - int y = (int)lua::tointeger(L, 2); - const auto& chunk = level->chunks->getChunk(x, y); + int x = static_cast(lua::tointeger(L, 1)); + int z = static_cast(lua::tointeger(L, 2)); + const auto& chunk = level->chunks->getChunk(x, z); if (chunk == nullptr) { lua::pushnil(L); return 0; } + auto chunkData = compressed_chunks::encode(*chunk); + return lua::newuserdata(L, std::move(chunkData)); +} - bool compress = false; - if (lua::gettop(L) >= 3) { - compress = lua::toboolean(L, 3); +static void integrate_chunk_client(Chunk& chunk) { + int x = chunk.x; + int z = chunk.z; + auto chunksController = controller->getChunksController(); + Lighting& lighting = *chunksController->lighting; + chunk.flags.loadedLights = false; + chunk.flags.lighted = false; + + Lighting::prebuildSkyLight(chunk, *indices); + lighting.onChunkLoaded(x, z, true); + + for (int lz = -1; lz <= 1; lz++) { + for (int lx = -1; lx <= 1; lx++) { + if (std::abs(lx) + std::abs(lz) != 1) { + continue; + } + if (auto other = level->chunks->getChunk(x + lx, z + lz)) { + other->flags.modified = true; + lighting.onChunkLoaded(x - 1, z, true); + } + } } - std::vector chunk_data; - if (compress) { - size_t rle_compressed_size; - size_t gzip_compressed_size; - const auto& data_ptr = chunk->encode(); - ubyte* data = data_ptr.get(); - const auto& rle_compressed_data_ptr = compression::compress( - data, - CHUNK_DATA_LEN, - rle_compressed_size, - compression::Method::EXTRLE16 - ); - const auto& gzip_compressed_data = compression::compress( - rle_compressed_data_ptr.get(), - rle_compressed_size, - gzip_compressed_size, - compression::Method::GZIP - ); - auto tmp = dataio::h2le(rle_compressed_size); - chunk_data.reserve(gzip_compressed_size + sizeof(tmp)); - chunk_data.insert( - chunk_data.begin() + 0, (char*)&tmp, ((char*)&tmp) + sizeof(tmp) - ); - chunk_data.insert( - chunk_data.begin() + sizeof(tmp), - gzip_compressed_data.get(), - gzip_compressed_data.get() + gzip_compressed_size - ); - } else { - const auto& data = chunk->encode(); - chunk_data.reserve(CHUNK_DATA_LEN); - chunk_data.insert( - chunk_data.begin(), data.get(), data.get() + CHUNK_DATA_LEN - ); - } - return lua::newuserdata(L, chunk_data); } static int l_set_chunk_data(lua::State* L) { - int x = (int)lua::tointeger(L, 1); - int y = (int)lua::tointeger(L, 2); + int x = static_cast(lua::tointeger(L, 1)); + int z = static_cast(lua::tointeger(L, 2)); auto buffer = lua::touserdata(L, 3); - bool is_compressed = false; - if (lua::gettop(L) >= 4) { - is_compressed = lua::toboolean(L, 4); - } - auto chunk = level->chunks->getChunk(x, y); + auto chunk = level->chunks->getChunk(x, z); if (chunk == nullptr) { return 0; } - if (is_compressed) { - std::vector& raw_data = buffer->data(); - size_t gzip_decompressed_size = - dataio::le2h(*(size_t*)(raw_data.data())); - const auto& rle_data = compression::decompress( - raw_data.data() + sizeof(gzip_decompressed_size), - buffer->data().size() - sizeof(gzip_decompressed_size), - gzip_decompressed_size, - compression::Method::GZIP - ); - const auto& data = compression::decompress( - rle_data.get(), - gzip_decompressed_size, - CHUNK_DATA_LEN, - compression::Method::EXTRLE16 - ); - chunk->decode(data.get()); - } else { - chunk->decode(buffer->data().data()); + compressed_chunks::decode( + *chunk, buffer->data().data(), buffer->data().size() + ); + if (controller->getChunksController()->lighting == nullptr) { + return lua::pushboolean(L, true); } - - auto chunksController = controller->getChunksController(); - if (chunksController == nullptr) { - return 1; - } - - Lighting& lighting = *chunksController->lighting; - chunk->updateHeights(); - lighting.buildSkyLight(x, y); - chunk->flags.modified = true; - lighting.onChunkLoaded(x, y, true); - - chunk = level->chunks->getChunk(x - 1, y); - if (chunk != nullptr) { - chunk->flags.modified = true; - lighting.onChunkLoaded(x - 1, y, true); - } - chunk = level->chunks->getChunk(x + 1, y); - if (chunk != nullptr) { - chunk->flags.modified = true; - lighting.onChunkLoaded(x + 1, y, true); - } - chunk = level->chunks->getChunk(x, y - 1); - if (chunk != nullptr) { - chunk->flags.modified = true; - lighting.onChunkLoaded(x, y - 1, true); - } - chunk = level->chunks->getChunk(x, y + 1); - if (chunk != nullptr) { - chunk->flags.modified = true; - lighting.onChunkLoaded(x, y + 1, true); - } - - return 1; + integrate_chunk_client(*chunk); + return lua::pushboolean(L, true); } static int l_count_chunks(lua::State* L) { diff --git a/src/voxels/Chunk.cpp b/src/voxels/Chunk.cpp index a32fc8ba..2c1f4fd8 100644 --- a/src/voxels/Chunk.cpp +++ b/src/voxels/Chunk.cpp @@ -13,19 +13,6 @@ Chunk::Chunk(int xpos, int zpos) : x(xpos), z(zpos) { top = CHUNK_H; } -bool Chunk::isEmpty() const { - int id = -1; - for (uint i = 0; i < CHUNK_VOL; i++) { - if (voxels[i].id != id) { - if (id != -1) - return false; - else - id = voxels[i].id; - } - } - return true; -} - void Chunk::updateHeights() { for (uint i = 0; i < CHUNK_VOL; i++) { if (voxels[i].id != 0) { diff --git a/src/voxels/Chunk.hpp b/src/voxels/Chunk.hpp index 55cdac21..3ce6e641 100644 --- a/src/voxels/Chunk.hpp +++ b/src/voxels/Chunk.hpp @@ -11,6 +11,7 @@ #include "maths/aabb.hpp" #include "voxel.hpp" +/// @brief Total bytes number of chunk voxel data inline constexpr int CHUNK_DATA_LEN = CHUNK_VOL * 4; class ContentReport; @@ -45,8 +46,7 @@ public: Chunk(int x, int z); - bool isEmpty() const; - + /// @brief Refresh `bottom` and `top` values void updateHeights(); // unused diff --git a/src/voxels/compressed_chunks.cpp b/src/voxels/compressed_chunks.cpp new file mode 100644 index 00000000..0e6772af --- /dev/null +++ b/src/voxels/compressed_chunks.cpp @@ -0,0 +1,61 @@ +#include "compressed_chunks.hpp" + +#include "coders/rle.hpp" +#include "coders/gzip.hpp" +#include "coders/byte_utils.hpp" +#include "voxels/Chunk.hpp" + +inline constexpr int HAS_VOXELS = 0x1; +inline constexpr int HAS_METADATA = 0x2; + +std::vector compressed_chunks::encode(const Chunk& chunk) { + auto data = chunk.encode(); + + /// world.get_chunk_data is only available in the main Lua state + static util::Buffer rleBuffer; + if (rleBuffer.size() < CHUNK_DATA_LEN * 2) { + rleBuffer = util::Buffer(CHUNK_DATA_LEN * 2); + } + size_t rleCompressedSize = + extrle::encode16(data.get(), CHUNK_DATA_LEN, rleBuffer.data()); + + const auto gzipCompressedData = gzip::compress( + rleBuffer.data(), rleCompressedSize + ); + auto metadataBytes = chunk.blocksMetadata.serialize(); + + ByteBuilder builder(2 + 8 + gzipCompressedData.size() + metadataBytes.size()); + builder.put(HAS_VOXELS | HAS_METADATA); // flags + builder.put(0); // reserved + builder.putInt32(gzipCompressedData.size()); + builder.put(gzipCompressedData.data(), gzipCompressedData.size()); + builder.putInt32(metadataBytes.size()); + builder.put(metadataBytes.data(), metadataBytes.size()); + return builder.build(); +} + +void compressed_chunks::decode(Chunk& chunk, const ubyte* src, size_t size) { + ByteReader reader(src, size); + + ubyte flags = reader.get(); + reader.skip(1); // reserved byte + + if (flags & HAS_VOXELS) { + size_t gzipCompressedSize = reader.getInt32(); + + auto rleData = gzip::decompress(reader.pointer(), gzipCompressedSize); + reader.skip(gzipCompressedSize); + + /// world.get_chunk_data is only available in the main Lua state + static util::Buffer voxelData (CHUNK_DATA_LEN); + extrle::decode16(rleData.data(), rleData.size(), voxelData.data()); + chunk.decode(voxelData.data()); + chunk.updateHeights(); + } + if (flags & HAS_METADATA) { + size_t metadataSize = reader.getInt32(); + chunk.blocksMetadata.deserialize(reader.pointer(), metadataSize); + reader.skip(metadataSize); + } + chunk.setModifiedAndUnsaved(); +} diff --git a/src/voxels/compressed_chunks.hpp b/src/voxels/compressed_chunks.hpp new file mode 100644 index 00000000..dc0bc5b6 --- /dev/null +++ b/src/voxels/compressed_chunks.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "typedefs.hpp" + +#include + +class Chunk; + +namespace compressed_chunks { + std::vector encode(const Chunk& chunk); + void decode(Chunk& chunk, const ubyte* src, size_t size); +}