diff --git a/doc/en/world-generator.md b/doc/en/world-generator.md index 40fd1b37c..a173f4428 100644 --- a/doc/en/world-generator.md +++ b/doc/en/world-generator.md @@ -427,6 +427,22 @@ Where: - point_a, point_b - vec3, vec3 positions of the start and end of the tunnel. - radius - radius of the tunnel in blocks +Single block: +```lua +{":block", block_id, position, [rotation], [priority]} +``` + +Where: +- block_id: numeric runtime id of the block to place. +- position: vec3 world position in blocks, relative to the current chunk start. +- rotation: 0–3, rotation around the Y axis. Default: 0. For extended blocks (size > 1), all segments use this rotation. +- priority: integer order. Higher values are placed later and overwrite lower‑priority placements. + +Notes: +- `:block` automatically expands extended blocks into all their segments and replaces any voxels occupying those cells. +- Placement is chunk‑border safe: the engine distributes the placement to all affected chunk prototypes based on the block’s size/AABB. +- Use `:block` for single blocks; use `:line` for tunnels or continuous lines. + ### Small structures placement ```lua diff --git a/doc/ru/world-generator.md b/doc/ru/world-generator.md index 7f7be5c49..5e0c50c1b 100644 --- a/doc/ru/world-generator.md +++ b/doc/ru/world-generator.md @@ -430,6 +430,22 @@ end - точка_а, точка_б - vec3, vec3 позиции начала и конца тоннеля. - радиус - радиус тоннеля в блоках +Одиночный блок: +```lua +{":block", id_блока, позиция, [поворот], [приоритет]} +``` + +Где: +- id_блока: числовой runtime‑id блока, который нужно поставить. +- позиция: vec3 позиция в блоках относительно начала текущего чанка. +- поворот: 0–3, поворот вокруг оси Y. По умолчанию: 0. Для расширенных блоков (размер > 1) этот поворот применяется ко всем сегментам. +- приоритет: целое число. Бóльший приоритет ставится позже и перезаписывает более низкий. + +Примечания: +- `:block` автоматически раскладывает расширенные блоки на сегменты и заменяет любые блоки в занимаемых ячейках. +- Размещение корректно работает на границах чанков: движок сам разносит плейсмент по затрагиваемым прототипам на основе размера/AABB блока. +- `:block` используйте для точечных блоков; `:line` — для туннелей/линий. + ### Расстановка малых структур diff --git a/src/graphics/render/BlocksRenderer.cpp b/src/graphics/render/BlocksRenderer.cpp index f2788df38..4b9c94e89 100644 --- a/src/graphics/render/BlocksRenderer.cpp +++ b/src/graphics/render/BlocksRenderer.cpp @@ -12,6 +12,61 @@ const glm::vec3 BlocksRenderer::SUN_VECTOR(0.528265, 0.833149, -0.163704); const float DIRECTIONAL_LIGHT_FACTOR = 0.3f; +namespace { +static constexpr float K_CHUNK_CENTER_BIAS = 0.5f; +static constexpr float K_AO_NORMAL_PUSH = 0.75f; + +static inline void expand_aabb_point(AABB& aabb, bool& init, const glm::vec3& p) { + if (!init) { aabb.a = aabb.b = p; init = true; } else { aabb.addPoint(p); } +} + +static inline float apply_directional_factor(float d) { + return (1.0f - DIRECTIONAL_LIGHT_FACTOR) + d * DIRECTIONAL_LIGHT_FACTOR; +} + +static inline void expand_aabb_4( + AABB& aabb, bool& init, + const glm::vec3& p0, const glm::vec3& p1, + const glm::vec3& p2, const glm::vec3& p3 +) { + expand_aabb_point(aabb, init, p0); + expand_aabb_point(aabb, init, p1); + expand_aabb_point(aabb, init, p2); + expand_aabb_point(aabb, init, p3); +} + +static inline void expand_aabb_4_if_needed( + AABB& aabb, bool& init, bool densePass, + const glm::vec3& p0, const glm::vec3& p1, + const glm::vec3& p2, const glm::vec3& p3 +) { + if (!densePass) { + expand_aabb_4(aabb, init, p0, p1, p2, p3); + } +} + +static inline void compute_face_points( + const glm::vec3& coord, + const glm::vec3& X, const glm::vec3& Y, const glm::vec3& Z, + float bias, + glm::vec3& p0, glm::vec3& p1, glm::vec3& p2, glm::vec3& p3 +) { + float s = bias; + p0 = coord + (-X - Y + Z) * s; + p1 = coord + ( X - Y + Z) * s; + p2 = coord + ( X + Y + Z) * s; + p3 = coord + (-X + Y + Z) * s; +} + +static inline void fill_texfaces( + const ContentGfxCache& cache, + blockid_t id, uint8_t variantId, bool densePass, + UVRegion (&out)[6] +) { + for (int f = 0; f < 6; ++f) out[f] = cache.getRegion(id, variantId, f, densePass); +} +} + BlocksRenderer::BlocksRenderer( size_t capacity, const Content& content, @@ -91,23 +146,30 @@ void BlocksRenderer::face( auto X = axisX * w; auto Y = axisY * h; auto Z = axisZ * d; - float s = 0.5f; - vertex(coord + (-X - Y + Z) * s, region.u1, region.v1, lights[0] * tint, axisZ, 0); - vertex(coord + ( X - Y + Z) * s, region.u2, region.v1, lights[1] * tint, axisZ, 0); - vertex(coord + ( X + Y + Z) * s, region.u2, region.v2, lights[2] * tint, axisZ, 0); - vertex(coord + (-X + Y + Z) * s, region.u1, region.v2, lights[3] * tint, axisZ, 0); + glm::vec3 p0, p1, p2, p3; + compute_face_points(coord, X, Y, Z, K_CHUNK_CENTER_BIAS, p0, p1, p2, p3); + vertex(p0, region.u1, region.v1, lights[0] * tint, axisZ, 0); + vertex(p1, region.u2, region.v1, lights[1] * tint, axisZ, 0); + vertex(p2, region.u2, region.v2, lights[2] * tint, axisZ, 0); + vertex(p3, region.u1, region.v2, lights[3] * tint, axisZ, 0); index(0, 1, 3, 1, 2, 3); + + // Expand local opaque AABB while vertices are still in chunk-local space + expand_aabb_4_if_needed(localAabb, localAabbInit, densePass, p0, p1, p2, p3); } void BlocksRenderer::vertexAO( const glm::vec3& coord, float u, float v, const glm::vec4& tint, + float normalHalfLen, const glm::vec3& axisX, const glm::vec3& axisY, const glm::vec3& axisZ ) { - auto pos = coord+axisZ*0.5f+(axisX+axisY)*0.5f; + // Sample AO in world-voxel grid with a slightly longer reach along the normal + // to avoid sampling the same chunk voxel when faces reside over neighbor chunk. + auto pos = coord + axisZ * normalHalfLen + (axisX + axisY) * 0.5f; auto light = pickSoftLight( glm::ivec3(std::round(pos.x), std::round(pos.y), std::round(pos.z)), axisX, @@ -129,27 +191,33 @@ void BlocksRenderer::faceAO( return; } - float s = 0.5f; if (lights) { - float d = glm::dot(glm::normalize(Z), SUN_VECTOR); - d = (1.0f - DIRECTIONAL_LIGHT_FACTOR) + d * DIRECTIONAL_LIGHT_FACTOR; + const auto nZ = glm::normalize(Z); + float d = apply_directional_factor(glm::dot(nZ, SUN_VECTOR)); auto axisX = glm::normalize(X); auto axisY = glm::normalize(Y); - auto axisZ = glm::normalize(Z); + auto axisZ = nZ; glm::vec4 tint(d); - vertexAO(coord + (-X - Y + Z) * s, region.u1, region.v1, tint, axisX, axisY, axisZ); - vertexAO(coord + ( X - Y + Z) * s, region.u2, region.v1, tint, axisX, axisY, axisZ); - vertexAO(coord + ( X + Y + Z) * s, region.u2, region.v2, tint, axisX, axisY, axisZ); - vertexAO(coord + (-X + Y + Z) * s, region.u1, region.v2, tint, axisX, axisY, axisZ); + const float nh = K_AO_NORMAL_PUSH; // push AO sample a bit farther along normal + glm::vec3 p0, p1, p2, p3; + compute_face_points(coord, X, Y, Z, K_CHUNK_CENTER_BIAS, p0, p1, p2, p3); + vertexAO(p0, region.u1, region.v1, tint, nh, axisX, axisY, axisZ); + vertexAO(p1, region.u2, region.v1, tint, nh, axisX, axisY, axisZ); + vertexAO(p2, region.u2, region.v2, tint, nh, axisX, axisY, axisZ); + vertexAO(p3, region.u1, region.v2, tint, nh, axisX, axisY, axisZ); + expand_aabb_4_if_needed(localAabb, localAabbInit, densePass, p0, p1, p2, p3); } else { auto axisZ = glm::normalize(Z); glm::vec4 tint(1.0f); - vertex(coord + (-X - Y + Z) * s, region.u1, region.v1, tint, axisZ, 1); - vertex(coord + ( X - Y + Z) * s, region.u2, region.v1, tint, axisZ, 1); - vertex(coord + ( X + Y + Z) * s, region.u2, region.v2, tint, axisZ, 1); - vertex(coord + (-X + Y + Z) * s, region.u1, region.v2, tint, axisZ, 1); + glm::vec3 p0, p1, p2, p3; + compute_face_points(coord, X, Y, Z, K_CHUNK_CENTER_BIAS, p0, p1, p2, p3); + vertex(p0, region.u1, region.v1, tint, axisZ, 1); + vertex(p1, region.u2, region.v1, tint, axisZ, 1); + vertex(p2, region.u2, region.v2, tint, axisZ, 1); + vertex(p3, region.u1, region.v2, tint, axisZ, 1); + expand_aabb_4_if_needed(localAabb, localAabbInit, densePass, p0, p1, p2, p3); } index(0, 1, 2, 0, 2, 3); } @@ -168,16 +236,19 @@ void BlocksRenderer::face( return; } - float s = 0.5f; + const auto nZ = glm::normalize(Z); if (lights) { - float d = glm::dot(glm::normalize(Z), SUN_VECTOR); - d = (1.0f - DIRECTIONAL_LIGHT_FACTOR) + d * DIRECTIONAL_LIGHT_FACTOR; + float d = apply_directional_factor(glm::dot(nZ, SUN_VECTOR)); tint *= d; } - vertex(coord + (-X - Y + Z) * s, region.u1, region.v1, tint, Z, lights ? 0 : 1); - vertex(coord + ( X - Y + Z) * s, region.u2, region.v1, tint, Z, lights ? 0 : 1); - vertex(coord + ( X + Y + Z) * s, region.u2, region.v2, tint, Z, lights ? 0 : 1); - vertex(coord + (-X + Y + Z) * s, region.u1, region.v2, tint, Z, lights ? 0 : 1); + const auto nZ2 = lights ? nZ : Z; + glm::vec3 p0, p1, p2, p3; + compute_face_points(coord, X, Y, Z, K_CHUNK_CENTER_BIAS, p0, p1, p2, p3); + vertex(p0, region.u1, region.v1, tint, nZ2, lights ? 0 : 1); + vertex(p1, region.u2, region.v1, tint, nZ2, lights ? 0 : 1); + vertex(p2, region.u2, region.v2, tint, nZ2, lights ? 0 : 1); + vertex(p3, region.u1, region.v2, tint, nZ2, lights ? 0 : 1); + expand_aabb_4_if_needed(localAabb, localAabbInit, densePass, p0, p1, p2, p3); index(0, 1, 2, 0, 2, 3); } @@ -333,8 +404,7 @@ void BlocksRenderer::blockCustomModel( continue; } - float d = glm::dot(n, SUN_VECTOR); - d = (1.0f - DIRECTIONAL_LIGHT_FACTOR) + d * DIRECTIONAL_LIGHT_FACTOR; + float d = apply_directional_factor(glm::dot(n, SUN_VECTOR)); glm::vec3 t = glm::cross(r, n); for (int i = 0; i < 3; i++) { @@ -347,14 +417,18 @@ void BlocksRenderer::blockCustomModel( vcoord.z * Z + r * 0.5f + t * 0.5f + n * 0.5f; aoColor = pickSoftLight(p.x, p.y, p.z, glm::ivec3(r), glm::ivec3(t)); } + auto pLocal = coord + vcoord.x * X + vcoord.y * Y + vcoord.z * Z; this->vertex( - coord + vcoord.x * X + vcoord.y * Y + vcoord.z * Z, + pLocal, vertex.uv.x, vertex.uv.y, mesh.shading ? (glm::vec4(d, d, d, d) * aoColor) : glm::vec4(1, 1, 1, d), n, mesh.shading ? 0.0f : 1.0 ); + if (!densePass) { + expand_aabb_point(localAabb, localAabbInit, pLocal); + } indexBuffer[indexCount++] = vertexOffset++; } } @@ -502,14 +576,8 @@ void BlocksRenderer::render( if (def.translucent) { continue; } - const UVRegion texfaces[6] { - cache.getRegion(id, variantId, 0, densePass), - cache.getRegion(id, variantId, 1, densePass), - cache.getRegion(id, variantId, 2, densePass), - cache.getRegion(id, variantId, 3, densePass), - cache.getRegion(id, variantId, 4, densePass), - cache.getRegion(id, variantId, 5, densePass) - }; + UVRegion texfaces[6]; + fill_texfaces(cache, id, variantId, densePass, texfaces); int x = i % CHUNK_W; int y = i / (CHUNK_D * CHUNK_W); int z = (i / CHUNK_D) % CHUNK_W; @@ -581,14 +649,8 @@ SortingMeshData BlocksRenderer::renderTranslucent( if (!def.translucent) { continue; } - const UVRegion texfaces[6] { - cache.getRegion(id, variantId, 0, densePass), - cache.getRegion(id, variantId, 1, densePass), - cache.getRegion(id, variantId, 2, densePass), - cache.getRegion(id, variantId, 3, densePass), - cache.getRegion(id, variantId, 4, densePass), - cache.getRegion(id, variantId, 5, densePass) - }; + UVRegion texfaces[6]; + fill_texfaces(cache, id, variantId, densePass, texfaces); int x = i % CHUNK_W; int y = i / (CHUNK_D * CHUNK_W); int z = (i / CHUNK_D) % CHUNK_W; @@ -654,6 +716,9 @@ SortingMeshData BlocksRenderer::renderTranslucent( aabb.addPoint(vertex.position); } + // also widen overall local AABB for translucent geometry + expand_aabb_point(localAabb, localAabbInit, vertex.position); + vertex.position.x += chunk->x * CHUNK_W + 0.5f; vertex.position.y += 0.5f; vertex.position.z += chunk->z * CHUNK_D + 0.5f; @@ -689,6 +754,9 @@ SortingMeshData BlocksRenderer::renderTranslucent( void BlocksRenderer::build(const Chunk* chunk, const Chunks* chunks) { this->chunk = chunk; + // reset local AABB accumulation + localAabbInit = false; + localAabb = AABB{glm::vec3(0.0f), glm::vec3(0.0f)}; voxelsBuffer->setPosition( chunk->x * CHUNK_W - voxelBufferPadding, 0, chunk->z * CHUNK_D - voxelBufferPadding); @@ -766,7 +834,8 @@ ChunkMeshData BlocksRenderer::createMesh() { ChunkVertex::ATTRIBUTES, sizeof(ChunkVertex::ATTRIBUTES) / sizeof(VertexAttribute) ) ), - std::move(sortingMesh) + std::move(sortingMesh), + localAabbInit ? localAabb : AABB{glm::vec3(0.0f), glm::vec3(0.0f)} }; } diff --git a/src/graphics/render/BlocksRenderer.hpp b/src/graphics/render/BlocksRenderer.hpp index a954fad6e..ceeef96e5 100644 --- a/src/graphics/render/BlocksRenderer.hpp +++ b/src/graphics/render/BlocksRenderer.hpp @@ -11,6 +11,7 @@ #include "maths/util.hpp" #include "commons.hpp" #include "settings.hpp" +#include "maths/aabb.hpp" template class Mesh; class Content; @@ -32,7 +33,8 @@ class BlocksRenderer { size_t indexCount; size_t denseIndexCount; size_t capacity; - int voxelBufferPadding = 2; + // Increased to better cover cross-chunk sampling for extended blocks (e.g., 3x3x3) + int voxelBufferPadding = 4; bool overflow = false; bool cancelled = false; bool densePass = false; @@ -47,6 +49,9 @@ class BlocksRenderer { util::PseudoRandom randomizer; SortingMeshData sortingMesh; + // Accumulated local-space AABB over opaque build pass + AABB localAabb {glm::vec3(0.0f), glm::vec3(0.0f)}; + bool localAabbInit = false; void vertex( const glm::vec3& coord, @@ -61,6 +66,7 @@ class BlocksRenderer { void vertexAO( const glm::vec3& coord, float u, float v, const glm::vec4& brightness, + float normalHalfLen, const glm::vec3& axisX, const glm::vec3& axisY, const glm::vec3& axisZ @@ -170,6 +176,7 @@ class BlocksRenderer { ChunkMesh render(const Chunk* chunk, const Chunks* chunks); ChunkMeshData createMesh(); VoxelsVolume* getVoxelsBuffer() const; + inline const AABB& getLocalAabb() const { return localAabb; } size_t getMemoryConsumption() const; diff --git a/src/graphics/render/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp index b0dfaec3e..3309e3a94 100644 --- a/src/graphics/render/ChunksRenderer.cpp +++ b/src/graphics/render/ChunksRenderer.cpp @@ -1,375 +1,427 @@ -#include "ChunksRenderer.hpp" -#include "BlocksRenderer.hpp" -#include "debug/Logger.hpp" -#include "assets/Assets.hpp" -#include "graphics/core/Mesh.hpp" -#include "graphics/core/Shader.hpp" -#include "graphics/core/Texture.hpp" -#include "graphics/core/Atlas.hpp" -#include "voxels/Chunk.hpp" -#include "voxels/Chunks.hpp" -#include "world/Level.hpp" -#include "window/Camera.hpp" -#include "maths/FrustumCulling.hpp" -#include "util/listutil.hpp" -#include "settings.hpp" - -static debug::Logger logger("chunks-render"); - -size_t ChunksRenderer::visibleChunks = 0; - -class RendererWorker : public util::Worker, RendererResult> { - const Chunks& chunks; - BlocksRenderer renderer; -public: - RendererWorker( - const Level& level, - const Chunks& chunks, - const ContentGfxCache& cache, - const EngineSettings& settings - ) - : chunks(chunks), - renderer( - settings.graphics.denseRender.get() - ? settings.graphics.chunkMaxVerticesDense.get() - : settings.graphics.chunkMaxVertices.get(), - level.content, - cache, - settings - ) { - } - - RendererResult operator()(const std::shared_ptr& chunk) override { - renderer.build(chunk.get(), &chunks); - if (renderer.isCancelled()) { - return RendererResult { - glm::ivec2(chunk->x, chunk->z), true, ChunkMeshData {}}; - } - auto meshData = renderer.createMesh(); - return RendererResult { - glm::ivec2(chunk->x, chunk->z), false, std::move(meshData)}; - } -}; - -ChunksRenderer::ChunksRenderer( - const Level* level, - const Chunks& chunks, - const Assets& assets, - const Frustum& frustum, - const ContentGfxCache& cache, - const EngineSettings& settings -) - : chunks(chunks), - assets(assets), - frustum(frustum), - settings(settings), - threadPool( - "chunks-render-pool", - [&]() { - return std::make_shared( - *level, chunks, cache, settings - ); - }, - [&](RendererResult& result) { - if (!result.cancelled) { - auto meshData = std::move(result.meshData); - meshes[result.key] = ChunkMesh { - std::make_unique>(meshData.mesh), - std::move(meshData.sortingMesh)}; - } - inwork.erase(result.key); - }, - settings.graphics.chunkMaxRenderers.get() - ) { - threadPool.setStopOnFail(false); - renderer = std::make_unique( - settings.graphics.chunkMaxVertices.get(), - level->content, cache, settings - ); - logger.info() << "created " << threadPool.getWorkersCount() << " workers"; - logger.info() << "memory consumption is " - << renderer->getMemoryConsumption() * threadPool.getWorkersCount() - << " B"; -} - -ChunksRenderer::~ChunksRenderer() = default; - -const Mesh* ChunksRenderer::render( - const std::shared_ptr& chunk, bool important -) { - chunk->flags.modified = false; - if (important) { - auto mesh = renderer->render(chunk.get(), &chunks); - meshes[glm::ivec2(chunk->x, chunk->z)] = ChunkMesh { - std::move(mesh.mesh), std::move(mesh.sortingMeshData) - }; - return meshes[glm::ivec2(chunk->x, chunk->z)].mesh.get(); - } - glm::ivec2 key(chunk->x, chunk->z); - if (inwork.find(key) != inwork.end()) { - return nullptr; - } - inwork[key] = true; - threadPool.enqueueJob(chunk); - return nullptr; -} - -void ChunksRenderer::unload(const Chunk* chunk) { - auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); - if (found != meshes.end()) { - meshes.erase(found); - } -} - -void ChunksRenderer::clear() { - meshes.clear(); - inwork.clear(); - threadPool.clearQueue(); -} - -const Mesh* ChunksRenderer::getOrRender( - const std::shared_ptr& chunk, bool important -) { - auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); - if (found == meshes.end()) { - return render(chunk, important); - } - if (chunk->flags.modified && chunk->flags.lighted) { - render(chunk, important); - } - return found->second.mesh.get(); -} - -void ChunksRenderer::update() { - threadPool.update(); -} - -const Mesh* ChunksRenderer::retrieveChunk( - size_t index, const Camera& camera, bool culling -) { - auto chunk = chunks.getChunks()[index]; - 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( - (chunk->x + 0.5f) * CHUNK_W, - camera.position.y, - (chunk->z + 0.5f) * CHUNK_D - ) - ); - auto mesh = getOrRender(chunk, distance < CHUNK_W * 1.5f); - if (mesh == nullptr) { - return nullptr; - } - if (chunk->flags.dirtyHeights) { - chunk->updateHeights(); - } - if (culling) { - glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D); - glm::vec3 max( - chunk->x * CHUNK_W + CHUNK_W, - chunk->top, - chunk->z * CHUNK_D + CHUNK_D - ); - - if (!frustum.isBoxVisible(min, max)) return nullptr; - } - return mesh; -} - -void ChunksRenderer::drawShadowsPass( - const Camera& camera, Shader& shader, const Camera& playerCamera -) { - Frustum frustum; - frustum.update(camera.getProjView()); - - const auto& atlas = assets.require("blocks"); - - atlas.getTexture()->bind(); - - auto denseDistance = settings.graphics.denseRenderDistance.get(); - auto denseDistance2 = denseDistance * denseDistance; - - for (const auto& chunk : chunks.getChunks()) { - if (chunk == nullptr) { - continue; - } - glm::ivec2 pos {chunk->x, chunk->z}; - const auto& found = meshes.find({chunk->x, chunk->z}); - if (found == meshes.end()) { - continue; - } - - glm::vec3 coord( - pos.x * CHUNK_W + 0.5f, 0.5f, pos.y * CHUNK_D + 0.5f - ); - - glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D); - glm::vec3 max( - chunk->x * CHUNK_W + CHUNK_W, - chunk->top, - chunk->z * CHUNK_D + CHUNK_D - ); - - if (!frustum.isBoxVisible(min, max)) { - continue; - } - glm::mat4 model = glm::translate(glm::mat4(1.0f), coord); - shader.uniformMatrix("u_model", model); - found->second.mesh->draw(GL_TRIANGLES, - glm::distance2(playerCamera.position * glm::vec3(1, 0, 1), - (min + max) * 0.5f * glm::vec3(1, 0, 1)) < denseDistance2); - } -} - -void ChunksRenderer::drawChunks( - const Camera& camera, Shader& shader -) { - const auto& atlas = assets.require("blocks"); - - atlas.getTexture()->bind(); - - // [warning] this whole method is not thread-safe for chunks - - int chunksWidth = chunks.getWidth(); - int chunksOffsetX = chunks.getOffsetX(); - int chunksOffsetY = chunks.getOffsetY(); - - if (indices.size() != chunks.getVolume()) { - indices.clear(); - for (int i = 0; i < chunks.getVolume(); i++) { - indices.push_back(ChunksSortEntry {i, 0}); - } - } - float px = camera.position.x / static_cast(CHUNK_W) - 0.5f; - float pz = camera.position.z / static_cast(CHUNK_D) - 0.5f; - for (auto& index : indices) { - float x = index.index % chunksWidth + chunksOffsetX - px; - float z = index.index / chunksWidth + chunksOffsetY - pz; - index.d = (x * x + z * z) * 1024; - } - util::insertion_sort(indices.begin(), indices.end()); - - bool culling = settings.graphics.frustumCulling.get(); - - visibleChunks = 0; - shader.uniform1i("u_alphaClip", true); - - auto denseDistance = settings.graphics.denseRenderDistance.get(); - auto denseDistance2 = denseDistance * denseDistance; - - // TODO: minimize draw calls number - for (int i = indices.size()-1; i >= 0; i--) { - auto& chunk = chunks.getChunks()[indices[i].index]; - auto mesh = retrieveChunk(indices[i].index, camera, culling); - - if (mesh) { - glm::vec3 coord( - chunk->x * CHUNK_W + 0.5f, 0.5f, chunk->z * CHUNK_D + 0.5f - ); - glm::mat4 model = glm::translate(glm::mat4(1.0f), coord); - shader.uniformMatrix("u_model", model); - mesh->draw(GL_TRIANGLES, glm::distance2(camera.position * glm::vec3(1, 0, 1), - (coord + glm::vec3(CHUNK_W * 0.5f, 0.0f, CHUNK_D * 0.5f))) < denseDistance2); - visibleChunks++; - } - } -} - -static inline void write_sorting_mesh_entries( - ChunkVertex* buffer, const std::vector& chunkEntries -) { - for (const auto& entry : chunkEntries) { - const auto& vertexData = entry.vertexData; - std::memcpy( - buffer, - vertexData.data(), - vertexData.size() * sizeof(ChunkVertex) - ); - buffer += vertexData.size(); - } -} - -void ChunksRenderer::drawSortedMeshes(const Camera& camera, Shader& shader) { - const int sortInterval = TRANSLUCENT_BLOCKS_SORT_INTERVAL; - static int frameid = 0; - frameid++; - - bool culling = settings.graphics.frustumCulling.get(); - const auto& chunks = this->chunks.getChunks(); - const auto& cameraPos = camera.position; - const auto& atlas = assets.require("blocks"); - - shader.use(); - atlas.getTexture()->bind(); - shader.uniformMatrix("u_model", glm::mat4(1.0f)); - shader.uniform1i("u_alphaClip", false); - - for (const auto& index : indices) { - const auto& chunk = chunks[index.index]; - if (chunk == nullptr || !chunk->flags.lighted) { - continue; - } - const auto& found = meshes.find(glm::ivec2(chunk->x, chunk->z)); - if (found == meshes.end() || found->second.sortingMeshData.entries.empty()) { - continue; - } - - if (culling) { - glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D); - glm::vec3 max( - chunk->x * CHUNK_W + CHUNK_W, - chunk->top, - chunk->z * CHUNK_D + CHUNK_D - ); - - if (!frustum.isBoxVisible(min, max)) continue; - } - - auto& chunkEntries = found->second.sortingMeshData.entries; - - if (chunkEntries.size() == 1) { - auto& entry = chunkEntries.at(0); - if (found->second.sortedMesh == nullptr) { - found->second.sortedMesh = std::make_unique>( - entry.vertexData.data(), entry.vertexData.size() - ); - } - found->second.sortedMesh->draw(); - continue; - } - for (auto& entry : chunkEntries) { - entry.distance = static_cast( - glm::distance2(entry.position, cameraPos) - ); - } - if (found->second.sortedMesh == nullptr || - (frameid + chunk->x) % sortInterval == 0) { - std::sort(chunkEntries.begin(), chunkEntries.end()); - size_t size = 0; - for (const auto& entry : chunkEntries) { - size += entry.vertexData.size(); - } - - static util::Buffer buffer; - if (buffer.size() < size) { - buffer = util::Buffer(size); - } - write_sorting_mesh_entries(buffer.data(), chunkEntries); - found->second.sortedMesh = std::make_unique>( - buffer.data(), size - ); - } - found->second.sortedMesh->draw(); - } -} +#include "ChunksRenderer.hpp" +#include "BlocksRenderer.hpp" +#include "debug/Logger.hpp" +#include "assets/Assets.hpp" +#include "graphics/core/Mesh.hpp" +#include "graphics/core/Shader.hpp" +#include "graphics/core/Texture.hpp" +#include "graphics/core/Atlas.hpp" +#include "voxels/Chunk.hpp" +#include "voxels/Chunks.hpp" +#include "world/Level.hpp" +#include "window/Camera.hpp" +#include "maths/FrustumCulling.hpp" +#include "util/listutil.hpp" +#include "settings.hpp" +#include + +static debug::Logger logger("chunks-render"); + +size_t ChunksRenderer::visibleChunks = 0; + +namespace { +struct CullingBounds { glm::vec3 min; glm::vec3 max; }; +static constexpr float K_CHUNK_CENTER_BIAS = 0.5f; +// Minimal thickness to avoid culling flicker for geometry that forms 2D sheets +static constexpr float K_AABB_MIN_EXTENT = 1e-2f; + +static inline bool has_volume(const AABB& aabb) { + auto s = aabb.size(); + return s.x > 0.0f || s.y > 0.0f || s.z > 0.0f; +} + +static inline CullingBounds compute_chunk_culling_bounds( + const Chunk& chunk, + const std::unordered_map& meshes +) { + glm::vec3 min(chunk.x * CHUNK_W, chunk.bottom, chunk.z * CHUNK_D); + glm::vec3 max( + chunk.x * CHUNK_W + CHUNK_W, + chunk.top, + chunk.z * CHUNK_D + CHUNK_D + ); + auto it = meshes.find({chunk.x, chunk.z}); + if (it != meshes.end()) { + const auto& aabb = it->second.localAabb; + if (has_volume(aabb)) { + // Convert to world coords (same 0.5 bias as draw model matrix) + min = glm::vec3(chunk.x * CHUNK_W + aabb.min().x + K_CHUNK_CENTER_BIAS, + aabb.min().y + K_CHUNK_CENTER_BIAS, + chunk.z * CHUNK_D + aabb.min().z + K_CHUNK_CENTER_BIAS); + max = glm::vec3(chunk.x * CHUNK_W + aabb.max().x + K_CHUNK_CENTER_BIAS, + aabb.max().y + K_CHUNK_CENTER_BIAS, + chunk.z * CHUNK_D + aabb.max().z + K_CHUNK_CENTER_BIAS); + + // Clamp vertically to chunk vertical span to keep bounds tight and valid + min.y = (std::max)(static_cast(chunk.bottom), min.y); + max.y = (std::min)(static_cast(chunk.top), max.y); + + // Ensure non-degenerate extents to avoid view-dependent flicker + glm::vec3 size = max - min; + auto inflate_axis = [&](int axis) { + float c = (min[axis] + max[axis]) * 0.5f; + min[axis] = c - K_AABB_MIN_EXTENT * 0.5f; + max[axis] = c + K_AABB_MIN_EXTENT * 0.5f; + }; + if (size.x < K_AABB_MIN_EXTENT) inflate_axis(0); + if (size.y < K_AABB_MIN_EXTENT) inflate_axis(1); + if (size.z < K_AABB_MIN_EXTENT) inflate_axis(2); + } + } + return {min, max}; +} +} + +class RendererWorker : public util::Worker, RendererResult> { + const Chunks& chunks; + BlocksRenderer renderer; +public: + RendererWorker( + const Level& level, + const Chunks& chunks, + const ContentGfxCache& cache, + const EngineSettings& settings + ) + : chunks(chunks), + renderer( + settings.graphics.denseRender.get() + ? settings.graphics.chunkMaxVerticesDense.get() + : settings.graphics.chunkMaxVertices.get(), + level.content, + cache, + settings + ) { + } + + RendererResult operator()(const std::shared_ptr& chunk) override { + renderer.build(chunk.get(), &chunks); + if (renderer.isCancelled()) { + return RendererResult { + glm::ivec2(chunk->x, chunk->z), true, ChunkMeshData {}}; + } + auto meshData = renderer.createMesh(); + return RendererResult { + glm::ivec2(chunk->x, chunk->z), false, std::move(meshData)}; + } +}; + +ChunksRenderer::ChunksRenderer( + const Level* level, + const Chunks& chunks, + const Assets& assets, + const Frustum& frustum, + const ContentGfxCache& cache, + const EngineSettings& settings +) + : chunks(chunks), + assets(assets), + frustum(frustum), + settings(settings), + threadPool( + "chunks-render-pool", + [&]() { + return std::make_shared( + *level, chunks, cache, settings + ); + }, + [&](RendererResult& result) { + if (!result.cancelled) { + auto meshData = std::move(result.meshData); + meshes[result.key] = ChunkMesh { + std::make_unique>(meshData.mesh), + std::move(meshData.sortingMesh)}; + meshes[result.key].localAabb = meshData.localAabb; + } + inwork.erase(result.key); + }, + settings.graphics.chunkMaxRenderers.get() + ) { + threadPool.setStopOnFail(false); + renderer = std::make_unique( + settings.graphics.chunkMaxVertices.get(), + level->content, cache, settings + ); + logger.info() << "created " << threadPool.getWorkersCount() << " workers"; + logger.info() << "memory consumption is " + << renderer->getMemoryConsumption() * threadPool.getWorkersCount() + << " B"; +} + +ChunksRenderer::~ChunksRenderer() = default; + +const Mesh* ChunksRenderer::render( + const std::shared_ptr& chunk, bool important +) { + chunk->flags.modified = false; + if (important) { + auto mesh = renderer->render(chunk.get(), &chunks); + meshes[glm::ivec2(chunk->x, chunk->z)] = ChunkMesh { + std::move(mesh.mesh), std::move(mesh.sortingMeshData) + }; + // propagate local aabb from immediate path too + meshes[glm::ivec2(chunk->x, chunk->z)].localAabb = renderer->getLocalAabb(); + return meshes[glm::ivec2(chunk->x, chunk->z)].mesh.get(); + } + glm::ivec2 key(chunk->x, chunk->z); + if (inwork.find(key) != inwork.end()) { + return nullptr; + } + inwork[key] = true; + threadPool.enqueueJob(chunk); + return nullptr; +} + +void ChunksRenderer::unload(const Chunk* chunk) { + auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); + if (found != meshes.end()) { + meshes.erase(found); + } +} + +void ChunksRenderer::clear() { + meshes.clear(); + inwork.clear(); + threadPool.clearQueue(); +} + +const Mesh* ChunksRenderer::getOrRender( + const std::shared_ptr& chunk, bool important +) { + auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); + if (found == meshes.end()) { + return render(chunk, important); + } + if (chunk->flags.modified && chunk->flags.lighted) { + render(chunk, important); + } + return found->second.mesh.get(); +} + +void ChunksRenderer::update() { + threadPool.update(); +} + +const Mesh* ChunksRenderer::retrieveChunk( + size_t index, const Camera& camera, bool culling +) { + auto chunk = chunks.getChunks()[index]; + 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( + (chunk->x + 0.5f) * CHUNK_W, + camera.position.y, + (chunk->z + 0.5f) * CHUNK_D + ) + ); + auto mesh = getOrRender(chunk, distance < CHUNK_W * 1.5f); + if (mesh == nullptr) { + return nullptr; + } + if (chunk->flags.dirtyHeights) { + chunk->updateHeights(); + } + if (culling) { + const auto bounds = compute_chunk_culling_bounds(*chunk, meshes); + if (!frustum.isBoxVisible(bounds.min, bounds.max)) return nullptr; + } + return mesh; +} + +void ChunksRenderer::drawShadowsPass( + const Camera& camera, Shader& shader, const Camera& playerCamera +) { + Frustum frustum; + frustum.update(camera.getProjView()); + + const auto& atlas = assets.require("blocks"); + + atlas.getTexture()->bind(); + + auto denseDistance = settings.graphics.denseRenderDistance.get(); + auto denseDistance2 = denseDistance * denseDistance; + + for (const auto& chunk : chunks.getChunks()) { + if (chunk == nullptr) { + continue; + } + glm::ivec2 pos {chunk->x, chunk->z}; + const auto& found = meshes.find({chunk->x, chunk->z}); + if (found == meshes.end()) { + continue; + } + + glm::vec3 coord( + pos.x * CHUNK_W + K_CHUNK_CENTER_BIAS, K_CHUNK_CENTER_BIAS, pos.y * CHUNK_D + K_CHUNK_CENTER_BIAS + ); + + const auto bounds = compute_chunk_culling_bounds(*chunk, meshes); + if (!frustum.isBoxVisible(bounds.min, bounds.max)) { + continue; + } + glm::mat4 model = glm::translate(glm::mat4(1.0f), coord); + shader.uniformMatrix("u_model", model); + found->second.mesh->draw(GL_TRIANGLES, + glm::distance2(playerCamera.position * glm::vec3(1, 0, 1), + (bounds.min + bounds.max) * 0.5f * glm::vec3(1, 0, 1)) < denseDistance2); + } +} + +void ChunksRenderer::drawChunks( + const Camera& camera, Shader& shader +) { + const auto& atlas = assets.require("blocks"); + + atlas.getTexture()->bind(); + + // [warning] this whole method is not thread-safe for chunks + + int chunksWidth = chunks.getWidth(); + int chunksOffsetX = chunks.getOffsetX(); + int chunksOffsetY = chunks.getOffsetY(); + + if (indices.size() != chunks.getVolume()) { + indices.clear(); + for (int i = 0; i < chunks.getVolume(); i++) { + indices.push_back(ChunksSortEntry {i, 0}); + } + } + float px = camera.position.x / static_cast(CHUNK_W) - 0.5f; + float pz = camera.position.z / static_cast(CHUNK_D) - 0.5f; + for (auto& index : indices) { + float x = index.index % chunksWidth + chunksOffsetX - px; + float z = index.index / chunksWidth + chunksOffsetY - pz; + index.d = (x * x + z * z) * 1024; + } + util::insertion_sort(indices.begin(), indices.end()); + + bool culling = settings.graphics.frustumCulling.get(); + + visibleChunks = 0; + shader.uniform1i("u_alphaClip", true); + + auto denseDistance = settings.graphics.denseRenderDistance.get(); + auto denseDistance2 = denseDistance * denseDistance; + + // TODO: minimize draw calls number + for (int i = indices.size()-1; i >= 0; i--) { + auto& chunk = chunks.getChunks()[indices[i].index]; + auto mesh = retrieveChunk(indices[i].index, camera, culling); + + if (mesh) { + glm::vec3 coord( + chunk->x * CHUNK_W + K_CHUNK_CENTER_BIAS, K_CHUNK_CENTER_BIAS, chunk->z * CHUNK_D + K_CHUNK_CENTER_BIAS + ); + glm::mat4 model = glm::translate(glm::mat4(1.0f), coord); + shader.uniformMatrix("u_model", model); + mesh->draw(GL_TRIANGLES, glm::distance2(camera.position * glm::vec3(1, 0, 1), + (coord + glm::vec3(CHUNK_W * 0.5f, 0.0f, CHUNK_D * 0.5f))) < denseDistance2); + visibleChunks++; + } + } +} + +static inline void write_sorting_mesh_entries( + ChunkVertex* buffer, const std::vector& chunkEntries +) { + for (const auto& entry : chunkEntries) { + const auto& vertexData = entry.vertexData; + std::memcpy( + buffer, + vertexData.data(), + vertexData.size() * sizeof(ChunkVertex) + ); + buffer += vertexData.size(); + } +} + +void ChunksRenderer::drawSortedMeshes(const Camera& camera, Shader& shader) { + const int sortInterval = TRANSLUCENT_BLOCKS_SORT_INTERVAL; + static int frameid = 0; + frameid++; + + const bool culling = settings.graphics.frustumCulling.get(); + const auto& chunks = this->chunks.getChunks(); + const auto& cameraPos = camera.position; + const auto& atlas = assets.require("blocks"); + + shader.use(); + atlas.getTexture()->bind(); + shader.uniformMatrix("u_model", glm::mat4(1.0f)); + shader.uniform1i("u_alphaClip", false); + + struct VisibleChunkTrans { + glm::ivec2 key; + const std::shared_ptr* chunkPtr; + }; + std::vector order; + order.reserve(indices.size()); + + // Build list of visible translucent chunks in the same order as indices + for (const auto& index : indices) { + const auto& chunk = chunks[index.index]; + if (chunk == nullptr || !chunk->flags.lighted) { + continue; + } + const auto found = meshes.find(glm::ivec2(chunk->x, chunk->z)); + if (found == meshes.end()) { + continue; + } + const auto& entries = found->second.sortingMeshData.entries; + if (entries.empty()) { + continue; + } + + if (culling) { + const auto bounds = compute_chunk_culling_bounds(*chunk, meshes); + if (!frustum.isBoxVisible(bounds.min, bounds.max)) continue; + } + + order.push_back(VisibleChunkTrans{ + glm::ivec2(chunk->x, chunk->z), + &chunks[index.index] + }); + } + + // Draw per-chunk sorted mesh (keeps GPU buffers and avoids per-frame repack) + for (const auto& item : order) { + const auto& chunk = *item.chunkPtr; + const auto found = meshes.find(item.key); + if (found == meshes.end()) continue; + auto& chunkEntries = found->second.sortingMeshData.entries; + if (chunkEntries.empty()) continue; + + // Keep per-chunk internal order up-to-date occasionally + if (found->second.sortedMesh == nullptr || (frameid + chunk->x) % sortInterval == 0) { + for (auto& entry : chunkEntries) { + entry.distance = static_cast( + glm::distance2(entry.position, cameraPos) + ); + } + std::sort(chunkEntries.begin(), chunkEntries.end()); + size_t size = 0; + for (const auto& entry : chunkEntries) { + size += entry.vertexData.size(); + } + static util::Buffer buffer; + if (buffer.size() < size) { + buffer = util::Buffer(size); + } + write_sorting_mesh_entries(buffer.data(), chunkEntries); + found->second.sortedMesh = std::make_unique>( + buffer.data(), size + ); + } + found->second.sortedMesh->draw(); + } +} diff --git a/src/graphics/render/commons.hpp b/src/graphics/render/commons.hpp index 9e78d1f53..f7d30dc60 100644 --- a/src/graphics/render/commons.hpp +++ b/src/graphics/render/commons.hpp @@ -8,6 +8,7 @@ #include "graphics/core/MeshData.hpp" #include "util/Buffer.hpp" +#include "maths/aabb.hpp" /// @brief Chunk mesh vertex format struct ChunkVertex { @@ -44,10 +45,12 @@ struct SortingMeshData { struct ChunkMeshData { MeshData mesh; SortingMeshData sortingMesh; + AABB localAabb; // mesh-space (chunk-local) bounds for precise culling }; struct ChunkMesh { std::unique_ptr> mesh; SortingMeshData sortingMeshData; std::unique_ptr > sortedMesh = nullptr; + AABB localAabb {}; // mesh-space (chunk-local) bounds for precise culling }; diff --git a/src/logic/scripting/scripting_world_generation.cpp b/src/logic/scripting/scripting_world_generation.cpp index 3199b46c3..ffff47ebf 100644 --- a/src/logic/scripting/scripting_world_generation.cpp +++ b/src/logic/scripting/scripting_world_generation.cpp @@ -152,6 +152,32 @@ class LuaGeneratorScript : public GeneratorScript { placements.emplace_back(priority, LinePlacement {block, a, b, radius}); } + void perform_block(lua::State* L, std::vector& placements) { + rawgeti(L, 2); + blockid_t block = touinteger(L, -1); + pop(L); + + rawgeti(L, 3); + glm::ivec3 pos = tovec3(L, -1); + pop(L); + + uint8_t rotation = 0; + if (objlen(L, -1) >= 4) { + rawgeti(L, 4); + rotation = tointeger(L, -1) & 0b11; + pop(L); + } + + int priority = 0; + if (objlen(L, -1) >= 5) { + rawgeti(L, 5); + priority = tointeger(L, -1); + pop(L); + } + + placements.emplace_back(priority, BlockPlacement {block, pos, rotation}); + } + void perform_placement(lua::State* L, std::vector& placements) { rawgeti(L, 1); int structIndex = 0; @@ -162,6 +188,11 @@ class LuaGeneratorScript : public GeneratorScript { perform_line(L, placements); return; + } else if (!std::strcmp(name, ":block")) { + pop(L); + + perform_block(L, placements); + return; } const auto& found = def.structuresIndices.find(name); if (found != def.structuresIndices.end()) { diff --git a/src/world/generator/StructurePlacement.hpp b/src/world/generator/StructurePlacement.hpp index 2ec7185e8..d64b6d081 100644 --- a/src/world/generator/StructurePlacement.hpp +++ b/src/world/generator/StructurePlacement.hpp @@ -27,12 +27,23 @@ struct LinePlacement { } }; +struct BlockPlacement { + blockid_t block; + glm::ivec3 position; + uint8_t rotation; + bool mirror; + + BlockPlacement(blockid_t block, glm::ivec3 position, uint8_t rotation, bool mirror=false) + : block(block), position(std::move(position)), rotation(rotation), mirror(mirror) { + } +}; + struct Placement { int priority; - std::variant placement; + std::variant placement; Placement( int priority, - std::variant placement + std::variant placement ) : priority(priority), placement(std::move(placement)) {} }; diff --git a/src/world/generator/WorldGenerator.cpp b/src/world/generator/WorldGenerator.cpp index ccd52db1f..227b7d1de 100644 --- a/src/world/generator/WorldGenerator.cpp +++ b/src/world/generator/WorldGenerator.cpp @@ -204,6 +204,47 @@ void WorldGenerator::placeLine(const LinePlacement& line, int priority) { } } +void WorldGenerator::placeBlock(const BlockPlacement& block, int priority) { + // Compute world-space AABB of the extended block to distribute to prototypes + const auto& indices = content.getIndices()->blocks; + const auto& def = indices.require(block.block); + const auto& rot = def.rotations.variants[block.rotation & 0b11]; + + glm::ivec3 minp = block.position; + glm::ivec3 maxp = block.position; + const auto size = def.size; + for (int sy = 0; sy < size.y; sy++) { + for (int sz = 0; sz < size.z; sz++) { + for (int sx = 0; sx < size.x; sx++) { + glm::ivec3 p = block.position; + p += rot.axes[0] * sx; + p += rot.axes[1] * sy; + p += rot.axes[2] * sz; + minp = glm::min(minp, p); + maxp = glm::max(maxp, p); + } + } + } + // inclusive-exclusive for max; expand by 1 to compute chunk coverage + maxp += glm::ivec3(1, 1, 1); + AABB aabb(minp, maxp); + int cxa = floordiv(aabb.a.x); + int cza = floordiv(aabb.a.z); + int cxb = floordiv(aabb.b.x); + int czb = floordiv(aabb.b.z); + for (int cz = cza; cz <= czb; cz++) { + for (int cx = cxa; cx <= cxb; cx++) { + const auto& found = prototypes.find({cx, cz}); + if (found != prototypes.end()) { + // position becomes relative to prototype chunk + glm::ivec3 rel = block.position - glm::ivec3(cx * CHUNK_W, 0, cz * CHUNK_D); + bool owner = (cx == floordiv(block.position.x)) && (cz == floordiv(block.position.z)); + found->second->placements.emplace_back(priority, BlockPlacement{block.block, rel, block.rotation, !owner}); + } + } + } +} + void WorldGenerator::placeStructures( const std::vector& placements, ChunkPrototype& prototype, @@ -217,9 +258,11 @@ void WorldGenerator::placeStructures( continue; } placeStructure(*sp, placement.priority, chunkX, chunkZ); + } else if (auto lp = std::get_if(&placement.placement)) { + placeLine(*lp, placement.priority); } else { - const auto& line = std::get(placement.placement); - placeLine(line, placement.priority); + const auto& bp = std::get(placement.placement); + placeBlock(bp, placement.priority); } } } @@ -482,9 +525,10 @@ void WorldGenerator::generatePlacements( for (const auto& placement : placements) { if (auto structure = std::get_if(&placement.placement)) { generateStructure(prototype, *structure, voxels, chunkX, chunkZ); - } else { - const auto& line = std::get(placement.placement); - generateLine(prototype, line, voxels, chunkX, chunkZ); + } else if (auto line = std::get_if(&placement.placement)) { + generateLine(prototype, *line, voxels, chunkX, chunkZ); + } else if (auto block = std::get_if(&placement.placement)) { + generateBlock(prototype, *block, voxels, chunkX, chunkZ); } } } @@ -591,6 +635,61 @@ void WorldGenerator::generateLine( } } +void WorldGenerator::generateBlock( + const ChunkPrototype& prototype, + const BlockPlacement& placement, + voxel* voxels, + int chunkX, int chunkZ +) { + const auto& indices = content.getIndices()->blocks; + const auto& def = indices.require(placement.block); + + glm::ivec3 origin = placement.position; // relative; may be outside + int rotIndex = 0; + if (def.rotatable && def.rotations.variantsCount) { + rotIndex = placement.rotation % def.rotations.variantsCount; + } + + // write origin only for owner chunk (mirror==false) and if inside bounds + if (!placement.mirror && + origin.x >= 0 && origin.x < CHUNK_W && + origin.y >= 0 && origin.y < CHUNK_H && + origin.z >= 0 && origin.z < CHUNK_D) { + auto& vox = voxels[vox_index(origin.x, origin.y, origin.z)]; + vox.id = placement.block; + vox.state = {}; + vox.state.rotation = rotIndex; + } + + // expand extended blocks + if (def.rt.extended) { + const auto& rot = def.rotations.variants[rotIndex]; + const auto size = def.size; + for (int sy = 0; sy < size.y; sy++) { + for (int sz = 0; sz < size.z; sz++) { + for (int sx = 0; sx < size.x; sx++) { + if ((sx | sy | sz) == 0) continue; + glm::ivec3 pos = origin; + pos += rot.axes[0] * sx; + pos += rot.axes[1] * sy; + pos += rot.axes[2] * sz; + if (pos.x < 0 || pos.x >= CHUNK_W || + pos.y < 0 || pos.y >= CHUNK_H || + pos.z < 0 || pos.z >= CHUNK_D) { + continue; + } + struct voxel seg; + seg.id = placement.block; + seg.state = {}; + seg.state.rotation = rotIndex; + seg.state.segment = ((sx > 0) | ((sy > 0) << 1) | ((sz > 0) << 2)); + voxels[vox_index(pos.x, pos.y, pos.z)] = seg; + } + } + } + } +} + WorldGenDebugInfo WorldGenerator::createDebugInfo() const { const auto& area = surroundMap.getArea(); const auto& levels = area.getBuffer(); diff --git a/src/world/generator/WorldGenerator.hpp b/src/world/generator/WorldGenerator.hpp index 7b64b84d8..2dfa50b67 100644 --- a/src/world/generator/WorldGenerator.hpp +++ b/src/world/generator/WorldGenerator.hpp @@ -79,6 +79,7 @@ class WorldGenerator { ); void placeLine(const LinePlacement& line, int priority); + void placeBlock(const BlockPlacement& block, int priority); void generatePlacements( const ChunkPrototype& prototype, voxel* voxels, int x, int z @@ -89,6 +90,12 @@ class WorldGenerator { voxel* voxels, int x, int z ); + void generateBlock( + const ChunkPrototype& prototype, + const BlockPlacement& placement, + voxel* voxels, + int x, int z + ); void generateStructure( const ChunkPrototype& prototype, const StructurePlacement& placement,