Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c815419
fix correct line generation algorithm and bounds calculation (#657)
eliotbyte Oct 23, 2025
c7259d4
fix enable per-variant custom model caching
eliotbyte Oct 28, 2025
772a282
docs: add custom model variant examples to block-propertie
eliotbyte Oct 28, 2025
3644857
add :block placement for single blocks
eliotbyte Oct 24, 2025
70d19a9
docs: add :block placement to world generator
eliotbyte Oct 24, 2025
1eeb59d
fix extended block placement across chunk borders
eliotbyte Oct 24, 2025
27ce16d
fix extended blocks culling and lighting at chunk boundaries
eliotbyte Oct 24, 2025
4dc1fdb
fix cross-chunk culling for extended blocks with precise mesh AABBs
eliotbyte Oct 25, 2025
1bea182
fix AABB bounds for opaque custom block models
eliotbyte Oct 25, 2025
b201151
fix translucent rendering order across chunk boundaries
eliotbyte Oct 25, 2025
dbee196
refactor: consolidate duplicated AABB and texture code
eliotbyte Oct 25, 2025
1dfb415
refactor: constexpr constants and minor optimizations
eliotbyte Oct 25, 2025
34cb2bf
fix function naming
Nov 1, 2025
3a78d5f
fix constant naming
eliotbyte Nov 1, 2025
0eec977
fix missing AABB expansion for non-AO faces
eliotbyte Nov 2, 2025
17f6bd4
refactor: extract compute_face_points and expand_aabb_4_if_needed hel…
eliotbyte Nov 2, 2025
626a08b
Merge pull request #659 from eliotbyte/feature/block-placement
MihailRis Nov 12, 2025
1772f0b
Merge branch 'main' into blocks-renderer-and-generator-update-review
MihailRis Nov 22, 2025
a07c161
fix: remove redundant sorting
eliotbyte Nov 23, 2025
95188ef
refactor: simplify translucent chunks ordering code
eliotbyte Nov 23, 2025
9982566
Merge pull request #699 from eliotbyte/blocks-renderer-and-generator-…
MihailRis Nov 23, 2025
4c2a85a
Merge branch 'main' into blocks-renderer-and-generator-update-review
MihailRis Nov 24, 2025
69f995b
fix clang warnings
MihailRis Nov 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions doc/en/world-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions doc/ru/world-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,22 @@ end
- точка_а, точка_б - vec3, vec3 позиции начала и конца тоннеля.
- радиус - радиус тоннеля в блоках

Одиночный блок:
```lua
{":block", id_блока, позиция, [поворот], [приоритет]}
```

Где:
- id_блока: числовой runtime‑id блока, который нужно поставить.
- позиция: vec3 позиция в блоках относительно начала текущего чанка.
- поворот: 0–3, поворот вокруг оси Y. По умолчанию: 0. Для расширенных блоков (размер > 1) этот поворот применяется ко всем сегментам.
- приоритет: целое число. Бóльший приоритет ставится позже и перезаписывает более низкий.

Примечания:
- `:block` автоматически раскладывает расширенные блоки на сегменты и заменяет любые блоки в занимаемых ячейках.
- Размещение корректно работает на границах чанков: движок сам разносит плейсмент по затрагиваемым прототипам на основе размера/AABB блока.
- `:block` используйте для точечных блоков; `:line` — для туннелей/линий.


### Расстановка малых структур

Expand Down
159 changes: 114 additions & 45 deletions src/graphics/render/BlocksRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand All @@ -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);
}
Expand All @@ -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);
}

Expand Down Expand Up @@ -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++) {
Expand All @@ -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++;
}
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)}
};
}

Expand Down
9 changes: 8 additions & 1 deletion src/graphics/render/BlocksRenderer.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "maths/util.hpp"
#include "commons.hpp"
#include "settings.hpp"
#include "maths/aabb.hpp"

template<typename VertexStructure> class Mesh;
class Content;
Expand All @@ -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;
Expand All @@ -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,
Expand All @@ -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
Expand Down Expand Up @@ -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;

Expand Down
Loading
Loading