diff --git a/doc/en/scripting/builtins/libgfx-blockwraps.md b/doc/en/scripting/builtins/libgfx-blockwraps.md index e03374dd7..7c99210ef 100644 --- a/doc/en/scripting/builtins/libgfx-blockwraps.md +++ b/doc/en/scripting/builtins/libgfx-blockwraps.md @@ -7,7 +7,14 @@ Block wrappers are introduced to implement block destruction animation and can b ```lua -- Creates a wrapper at the specified position, with the specified texture. -- Returns the wrapper id. -gfx.blockwraps.wrap(position: vec3, texture: str) --> int +gfx.blockwraps.wrap( + -- block position + position: vec3, + -- wrap texture + texture: string, + -- emission [0.0; 1.0] + [optional] emission: number = 1.0 +) -> int -- Removes the wrapper, if it exists. gfx.blockwraps.unwrap(id: int) @@ -17,6 +24,35 @@ gfx.blockwraps.set_pos(id: int, position: vec3) -- Changes the texture of the wrapper, if it exists. gfx.blockwraps.set_texture(id: int, texture: str) + +-- Sets the face textures (-X, +X, -Y, +Y, -Z, +Z). +-- - nil - disables face rendering. +-- - "" - monotone texture. +gfx.blockwraps.set_faces( + -- wrapper id + id: int, + -- face texture 1 (-X) + face1?: string, + -- face texture 2 (+X) + face2?: string, + ... + -- face texture 6 (+Z) + face6?: string +) + +-- Sets the tint of the sides (-X, +X, -Y, +Y, -Z, +Z). +-- RGB [0.0; 1.0] +gfx.blockwraps.set_tints( + -- wrapper id + id: int, + -- side 1 color (-X) + face1?: vec3, + -- side 2 color (+X) + face2?: vec3, + ... + -- side 6 color (+Z) + face6?: vec3 +) ``` -Wrappers are not automatically removed without calling `unwrap`. +Wrappers will not be automatically removed without calling `unwrap`. diff --git a/doc/ru/scripting/builtins/libgfx-blockwraps.md b/doc/ru/scripting/builtins/libgfx-blockwraps.md index 645df7ec9..ca39e3ed9 100644 --- a/doc/ru/scripting/builtins/libgfx-blockwraps.md +++ b/doc/ru/scripting/builtins/libgfx-blockwraps.md @@ -8,7 +8,14 @@ ```lua -- Создаёт обертку на указанной позиции, с указанной текстурой. -- Возвращает id обёртки. -gfx.blockwraps.wrap(position: vec3, texture: string) -> int +gfx.blockwraps.wrap( + -- Позиция блока + position: vec3, + -- Текстура обёртки + texture: string, + -- Излучение [0.0; 1.0] + [опционально] emission: number = 1.0 +) -> int -- Удаляет обертку, если она существует. gfx.blockwraps.unwrap(id: int) @@ -18,6 +25,35 @@ gfx.blockwraps.set_pos(id: int, position: vec3) -- Меняет текстуру обёртки, если она существует. gfx.blockwraps.set_texture(id: int, texture: string) + +-- Устанавливает текстуры сторон (-X, +X, -Y, +Y, -Z, +Z). +-- - nil - отключает отображение стороны. +-- - "" - монотонная текстура. +gfx.blockwraps.set_faces( + -- id обёртки + id: int, + -- текстура стороны 1 (-X) + face1?: string, + -- текстура стороны 2 (+X) + face2?: string, + ... + -- текстура стороны 6 (+Z) + face6?: string +) + +-- Устанавливает окраску сторон (-X, +X, -Y, +Y, -Z, +Z). +-- RGB [0.0; 1.0] +gfx.blockwraps.set_tints( + -- id обёртки + id: int, + -- цвет стороны 1 (-X) + face1?: vec3, + -- цвет стороны 2 (+X) + face2?: vec3, + ... + -- цвет стороны 6 (+Z) + face6?: vec3 +) ``` -Обертки не удаляются автоматически без вызова `unwrap`. \ No newline at end of file +Обертки не удаляются автоматически без вызова `unwrap`. diff --git a/src/graphics/render/BlockWrapsRenderer.cpp b/src/graphics/render/BlockWrapsRenderer.cpp index 86c46c984..24a7955b8 100644 --- a/src/graphics/render/BlockWrapsRenderer.cpp +++ b/src/graphics/render/BlockWrapsRenderer.cpp @@ -1,18 +1,20 @@ #include "BlockWrapsRenderer.hpp" #include "assets/Assets.hpp" -#include "assets/assets_util.hpp" #include "constants.hpp" #include "content/Content.hpp" #include "graphics/core/Atlas.hpp" #include "graphics/core/Shader.hpp" #include "graphics/core/DrawContext.hpp" #include "graphics/render/MainBatch.hpp" +#include "lighting/Lightmap.hpp" #include "objects/Player.hpp" #include "voxels/Block.hpp" #include "voxels/Chunks.hpp" #include "world/Level.hpp" +#include + BlockWrapsRenderer::BlockWrapsRenderer( const Assets& assets, const Level& level, const Chunks& chunks ) @@ -24,78 +26,145 @@ BlockWrapsRenderer::BlockWrapsRenderer( BlockWrapsRenderer::~BlockWrapsRenderer() = default; -void BlockWrapsRenderer::draw(const BlockWrapper& wrapper) { - auto textureRegion = util::get_texture_region(assets, wrapper.texture, ""); - - auto& shader = assets.require("entity"); - shader.use(); - shader.uniform1i("u_alphaClip", false); - - const UVRegion& cracksRegion = textureRegion.region; - UVRegion regions[6] { - cracksRegion, cracksRegion, cracksRegion, - cracksRegion, cracksRegion, cracksRegion - }; - batch->setTexture(textureRegion.texture); - - const voxel* vox = chunks.get(wrapper.position); - if (vox == nullptr) { +void BlockWrapsRenderer::draw(BlockWrapper& wrapper, const Texture* texture) { + if (wrapper.cullingBits == 0x0) { return; } - if (vox->id != BLOCK_VOID) { - const auto& def = level.content.getIndices()->blocks.require(vox->id); - switch (def.getModel(vox->state.userbits).type) { - case BlockModelType::BLOCK: - batch->cube( - glm::vec3(wrapper.position) + glm::vec3(0.5f), - glm::vec3(1.01f), - regions, - glm::vec4(1,1,1,0), - false - ); - break; - case BlockModelType::AABB: { - const auto& aabb = - (def.rotatable ? def.rt.hitboxes[vox->state.rotation] - : def.hitboxes) - .at(0); - const auto& size = aabb.size(); - regions[0].scale(size.z, size.y); - regions[1].scale(size.z, size.y); - regions[2].scale(size.x, size.z); - regions[3].scale(size.x, size.z); - regions[4].scale(size.x, size.y); - regions[5].scale(size.x, size.y); - batch->cube( - glm::vec3(wrapper.position) + aabb.center(), - size * glm::vec3(1.01f), - regions, - glm::vec4(1,1,1,0), - false - ); - break; - } - default: - break; + + uint8_t cullingBits = wrapper.cullingBits; + for (int i = 0; i < 6; i++) { + if ((cullingBits & (1 << i)) == 0x0) { + continue; } + cullingBits &= ~((wrapper.texRegions[i].texture != texture) << i); + } + if (cullingBits == 0x0) { + return; + } + + const voxel* vox = chunks.get(wrapper.position); + if (vox == nullptr || vox->id == BLOCK_VOID) { + return; + } + // one frame can be invalid due to texture change but ok + const auto& def = + level.content.getIndices()->blocks.require(vox->id); + + if (wrapper.modelType != def.getModel(vox->state.userbits).type) { + wrapper.dirtySides = 0xFF; + refreshWrapper(wrapper); + } + glm::vec4 light(1, 1, 1, 0); + if (wrapper.emission < 1.0f) { + light = Lightmap::extractNormalized(chunks.getLight(wrapper.position)); + light.r += wrapper.emission; + light.g += wrapper.emission; + light.b += wrapper.emission; + } + switch (wrapper.modelType) { + case BlockModelType::BLOCK: + batch->cube( + glm::vec3(wrapper.position) + glm::vec3(0.5f), + glm::vec3(1.01f), + wrapper.uvRegions, + light, + wrapper.tints.data(), + wrapper.emission, + cullingBits + ); + break; + case BlockModelType::AABB: { + const auto& aabb = + (def.rotatable ? def.rt.hitboxes[vox->state.rotation] + : def.hitboxes).at(0); + const auto& size = aabb.size(); + batch->cube( + glm::vec3(wrapper.position) + aabb.center(), + size * glm::vec3(1.01f), + wrapper.uvRegions, + light, + wrapper.tints.data(), + wrapper.emission, + cullingBits + ); + break; + } + default: + break; + } +} + +void BlockWrapsRenderer::refreshWrapper(BlockWrapper& wrapper) { + for (int i = 0; i < 6; i++) { + if ((wrapper.cullingBits & (1 << i)) == 0) { + continue; + } + auto texRegion = + util::get_texture_region(assets, wrapper.textureFaces[i], ""); + wrapper.texRegions[i] = texRegion; + wrapper.uvRegions[i] = texRegion.region; + + renderOrder.insert({texRegion.texture, &wrapper}); + } + wrapper.dirtySides = 0x0; + + const voxel* vox = chunks.get(wrapper.position); + if (vox == nullptr || vox->id == BLOCK_VOID) { + return; + } + const auto& def = level.content.getIndices()->blocks.require(vox->id); + wrapper.modelType = def.getModel(vox->state.userbits).type; + switch (wrapper.modelType) { + case BlockModelType::AABB: { + const auto& aabb = + (def.rotatable ? def.rt.hitboxes[vox->state.rotation] + : def.hitboxes).at(0); + const auto& size = aabb.size(); + wrapper.uvRegions[0].scale(size.z, size.y); + wrapper.uvRegions[1].scale(size.z, size.y); + wrapper.uvRegions[2].scale(size.x, size.z); + wrapper.uvRegions[3].scale(size.x, size.z); + wrapper.uvRegions[4].scale(size.x, size.y); + wrapper.uvRegions[5].scale(size.x, size.y); + break; + } + default: + break; } } void BlockWrapsRenderer::draw(const DrawContext& pctx, const Player& player) { auto ctx = pctx.sub(); + + auto& shader = assets.require("entity"); + shader.use(); + shader.uniform1i("u_alphaClip", false); + for (const auto& [_, wrapper] : wrappers) { - draw(*wrapper); + if (wrapper->dirtySides) { + refreshWrapper(*wrapper); + } + } + + for (const auto& [texture, wrapper] : renderOrder) { + batch->setTexture(texture); + draw(*wrapper, texture); } batch->flush(); } u64id_t BlockWrapsRenderer::add( - const glm::ivec3& position, const std::string& texture + const glm::ivec3& position, + const std::string& texture, + const glm::vec3& tint, + float emission ) { u64id_t id = nextWrapper++; - wrappers[id] = std::make_unique( - BlockWrapper {position, texture} - ); + wrappers[id] = std::make_unique(BlockWrapper { + position, + {texture, texture, texture, texture, texture, texture}, + {tint, tint, tint, tint, tint, tint}, + emission}); return id; } @@ -108,5 +177,19 @@ BlockWrapper* BlockWrapsRenderer::get(u64id_t id) const { } void BlockWrapsRenderer::remove(u64id_t id) { + auto found = wrappers.find(id); + if (found == wrappers.end()) { + return; + } + auto wrapper = std::move(found->second); wrappers.erase(id); + + auto it = renderOrder.begin(); + while (it != renderOrder.end()) { + if (it->second == wrapper.get()) { + it = renderOrder.erase(it); + } else { + ++it; + } + } } diff --git a/src/graphics/render/BlockWrapsRenderer.hpp b/src/graphics/render/BlockWrapsRenderer.hpp index 9d6184b90..f19bf6b66 100644 --- a/src/graphics/render/BlockWrapsRenderer.hpp +++ b/src/graphics/render/BlockWrapsRenderer.hpp @@ -1,21 +1,37 @@ #pragma once +#include "assets/assets_util.hpp" +#include "voxels/Block.hpp" +#include "MainBatch.hpp" +#include "typedefs.hpp" + +#include #include #include #include - -#include "MainBatch.hpp" -#include "typedefs.hpp" +#include +#include class Assets; class Player; class Level; +struct voxel; class Chunks; +class Texture; class DrawContext; struct BlockWrapper { glm::ivec3 position; - std::string texture; + std::array textureFaces {}; + std::array tints {}; + float emission = 0.0f; + + // --- render cache --- + util::TextureRegion texRegions[6] {}; + UVRegion uvRegions[6] {}; + BlockModelType modelType {}; + uint8_t cullingBits = 0xFF; + uint8_t dirtySides = 0xFF; }; class BlockWrapsRenderer { @@ -24,10 +40,13 @@ class BlockWrapsRenderer { const Chunks& chunks; std::unique_ptr batch; + std::multimap renderOrder; std::unordered_map> wrappers; u64id_t nextWrapper = 1; - void draw(const BlockWrapper& wrapper); + void draw(BlockWrapper& wrapper, const Texture* texture); + + void refreshWrapper(BlockWrapper& wrapper); public: BlockWrapsRenderer( const Assets& assets, const Level& level, const Chunks& chunks @@ -36,7 +55,12 @@ public: void draw(const DrawContext& ctx, const Player& player); - u64id_t add(const glm::ivec3& position, const std::string& texture); + u64id_t add( + const glm::ivec3& position, + const std::string& texture, + const glm::vec3& tint, + float emission + ); BlockWrapper* get(u64id_t id) const; diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index 8466008da..5320edd73 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -452,10 +452,7 @@ glm::vec4 BlocksRenderer::pickLight(int x, int y, int z) const { if (isOpenForLight(x, y, z)) { light_t light = voxelsBuffer->pickLight(chunk->x * CHUNK_W + x, y, chunk->z * CHUNK_D + z); - return glm::vec4(Lightmap::extract(light, 0), - Lightmap::extract(light, 1), - Lightmap::extract(light, 2), - Lightmap::extract(light, 3)) / 15.0f; + return Lightmap::extractNormalized(light); } else { return glm::vec4(0.0f); } diff --git a/src/graphics/render/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp index 8289967c1..78bf6f2e7 100644 --- a/src/graphics/render/ChunksRenderer.cpp +++ b/src/graphics/render/ChunksRenderer.cpp @@ -128,12 +128,11 @@ const Mesh* ChunksRenderer::render( chunk->flags.modified = false; if (important) { auto voxelsBuffer = prepareVoxelsVolume(*chunk); - inwork[key] = true; auto mesh = renderer->render(chunk.get(), *voxelsBuffer); - meshes[glm::ivec2(chunk->x, chunk->z)] = + meshes[key] = ChunkMesh {std::move(mesh.mesh), std::move(mesh.sortingMeshData)}; - return meshes[glm::ivec2(chunk->x, chunk->z)].mesh.get(); + return meshes[key].mesh.get(); } if (inwork.find(key) != inwork.end()) { return nullptr; diff --git a/src/graphics/render/MainBatch.cpp b/src/graphics/render/MainBatch.cpp index f05b715d7..5b08ffe04 100644 --- a/src/graphics/render/MainBatch.cpp +++ b/src/graphics/render/MainBatch.cpp @@ -83,50 +83,58 @@ inline glm::vec4 do_tint(float value) { } void MainBatch::cube( - const glm::vec3 &coord, - const glm::vec3 &size, - const UVRegion(&texfaces)[6], - const glm::vec4 &tint, - bool shading + const glm::vec3& coord, + const glm::vec3& size, + const UVRegion(&texfaces)[6], + const glm::vec4& lights, + const glm::vec3 tints[], + float emission, + uint8_t cullingBits ) { const glm::vec3 X(1.0f, 0.0f, 0.0f); const glm::vec3 Y(0.0f, 1.0f, 0.0f); const glm::vec3 Z(0.0f, 0.0f, 1.0f); + if (cullingBits & 0x20) quad( coord + Z * size.z * 0.5f, X, Y, Z, glm::vec2(size.x, size.y), - (shading ? do_tint(0.8) * tint : tint), - glm::vec3(1.0f), texfaces[5] + do_tint((1.0f - emission) * 0.8 + emission) * lights, + tints[5], texfaces[5] ); + if (cullingBits & 0x10) quad( coord - Z * size.z * 0.5f, -X, Y, -Z, glm::vec2(size.x, size.y), - (shading ? do_tint(0.9f) * tint : tint), - glm::vec3(1.0f), texfaces[4] + do_tint((1.0f - emission) * 0.9 + emission) * lights, + tints[4], texfaces[4] ); + if (cullingBits & 0x8) quad( coord + Y * size.y * 0.5f, -X, Z, Y, glm::vec2(size.x, size.z), - (shading ? do_tint(1.0f) * tint : tint), - glm::vec3(1.0f), texfaces[3] + do_tint((1.0f - emission) * 1.0 + emission) * lights, + tints[3], texfaces[3] ); + if (cullingBits & 0x4) quad( coord - Y * size.y * 0.5f, X, Z, -Y, glm::vec2(size.x, size.z), - (shading ? do_tint(0.7f) * tint : tint), - glm::vec3(1.0f), texfaces[2] + do_tint((1.0f - emission) * 0.7 + emission) * lights, + tints[2], texfaces[2] ); + if (cullingBits & 0x2) quad( coord + X * size.x * 0.5f, -Z, Y, X, glm::vec2(size.z, size.y), - (shading ? do_tint(0.8f) * tint : tint), - glm::vec3(1.0f), texfaces[1] + do_tint((1.0f - emission) * 0.8 + emission) * lights, + tints[1], texfaces[1] ); + if (cullingBits & 0x1) quad( coord - X * size.x * 0.5f, Z, Y, -X, glm::vec2(size.z, size.y), - (shading ? do_tint(0.9f) * tint : tint), - glm::vec3(1.0f), texfaces[1] + do_tint((1.0f - emission) * 0.9 + emission) * lights, + tints[0], texfaces[0] ); } diff --git a/src/graphics/render/MainBatch.hpp b/src/graphics/render/MainBatch.hpp index ec85b0082..8ad119199 100644 --- a/src/graphics/render/MainBatch.hpp +++ b/src/graphics/render/MainBatch.hpp @@ -151,7 +151,9 @@ public: const glm::vec3& coord, const glm::vec3& size, const UVRegion(&texfaces)[6], - const glm::vec4& tint, - bool shading + const glm::vec4& lights, + const glm::vec3 tints[], + float emission, + uint8_t cullingBits = 0xFF ); }; diff --git a/src/lighting/Lightmap.hpp b/src/lighting/Lightmap.hpp index 858e37388..c13058904 100644 --- a/src/lighting/Lightmap.hpp +++ b/src/lighting/Lightmap.hpp @@ -5,6 +5,7 @@ #include #include +#include inline constexpr int LIGHTMAP_DATA_LEN = CHUNK_VOL/2; @@ -87,6 +88,15 @@ public: return (light >> (channel << 2)) & 0xF; } + static glm::vec4 extractNormalized(light_t light) { + return glm::vec4( + extract(light, 0) / 15.0f, + extract(light, 1) / 15.0f, + extract(light, 2) / 15.0f, + extract(light, 3) / 15.0f + ); + } + std::unique_ptr encode() const; void decode(const ubyte* src); diff --git a/src/logic/scripting/lua/libs/libblockwraps.cpp b/src/logic/scripting/lua/libs/libblockwraps.cpp index ceeec744c..5b28f7dbb 100644 --- a/src/logic/scripting/lua/libs/libblockwraps.cpp +++ b/src/logic/scripting/lua/libs/libblockwraps.cpp @@ -9,9 +9,14 @@ using namespace scripting; static int l_wrap(lua::State* L) { auto position = lua::tovec3(L, 1); std::string texture = lua::require_string(L, 2); + float emission = lua::isnumber(L, 3) ? lua::tonumber(L, 3) : 1.0f; + glm::vec3 tint = lua::istable(L, 4) ? lua::tovec3(L, 4) : glm::vec3(1.0f); return lua::pushinteger( - L, renderer->blockWraps->add(position, std::move(texture)) + L, + renderer->blockWraps->add( + position, std::move(texture), std::move(tint), emission + ) ); } @@ -29,7 +34,46 @@ static int l_set_pos(lua::State* L) { static int l_set_texture(lua::State* L) { if (auto wrapper = renderer->blockWraps->get(lua::tointeger(L, 1))) { - wrapper->texture = lua::require_string(L, 2); + for (int i = 0; i < wrapper->textureFaces.size(); i++) { + wrapper->textureFaces[i] = lua::require_string(L, 2); + } + wrapper->dirtySides = 0xFF; + } + return 0; +} + +static int l_set_faces(lua::State* L) { + if (auto wrapper = renderer->blockWraps->get(lua::tointeger(L, 1))) { + for (int i = 0; i < wrapper->textureFaces.size(); i++) { + if (lua::isnil(L, 2 + i)) { + if (wrapper->cullingBits & (1 << i)) { + wrapper->cullingBits &= ~(1 << i); + wrapper->textureFaces[i] = ""; + wrapper->dirtySides |= (1 << i); + } + } else { + auto texture = lua::require_string(L, 2 + i);; + if ((wrapper->cullingBits & (1 << i)) == 0x0 + || wrapper->textureFaces[i] != texture) { + wrapper->cullingBits |= (1 << i); + wrapper->textureFaces[i] = texture; + } + } + } + } + return 0; +} + +static int l_set_tints(lua::State* L) { + if (auto wrapper = renderer->blockWraps->get(lua::tointeger(L, 1))) { + for (int i = 0; i < wrapper->textureFaces.size(); i++) { + if (lua::isnil(L, 2 + i)) { + wrapper->tints[i] = glm::vec3(1.0f); + } else { + wrapper->tints[i] = lua::tovec3(L, 2 + i); + } + } + wrapper->dirtySides = 0xFF; } return 0; } @@ -39,5 +83,7 @@ const luaL_Reg blockwrapslib[] = { {"unwrap", lua::wrap}, {"set_pos", lua::wrap}, {"set_texture", lua::wrap}, + {"set_faces", lua::wrap}, + {"set_tints", lua::wrap}, {nullptr, nullptr} }; diff --git a/src/voxels/Chunks.cpp b/src/voxels/Chunks.cpp index 96c709639..a4b693829 100644 --- a/src/voxels/Chunks.cpp +++ b/src/voxels/Chunks.cpp @@ -100,6 +100,10 @@ bool Chunks::isObstacleBlock(int32_t x, int32_t y, int32_t z) { return indices.blocks.require(v->id).obstacle; } +light_t Chunks::getLight(const glm::ivec3& pos) const { + return getLight(pos.x, pos.y, pos.z); +} + ubyte Chunks::getLight(int32_t x, int32_t y, int32_t z, int channel) const { if (y < 0 || y >= CHUNK_H) { return 0; diff --git a/src/voxels/Chunks.hpp b/src/voxels/Chunks.hpp index 2448d8333..e185ce584 100644 --- a/src/voxels/Chunks.hpp +++ b/src/voxels/Chunks.hpp @@ -76,6 +76,7 @@ public: return get(pos.x, pos.y, pos.z); } + light_t getLight(const glm::ivec3& pos) const; light_t getLight(int32_t x, int32_t y, int32_t z) const; ubyte getLight(int32_t x, int32_t y, int32_t z, int channel) const; void set(int32_t x, int32_t y, int32_t z, uint32_t id, blockstate state);