diff --git a/core/include/vf/LitMesh.h b/core/include/vf/LitMesh.h new file mode 100644 index 0000000..92682d4 --- /dev/null +++ b/core/include/vf/LitMesh.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Mesh.h" +#include "Lighting.h" + +namespace vf { + +// Face-culled mesh for one chunk carrying per-vertex ambient occlusion and +// baked light sampled from `light`. Opaque and transparent (water) faces are +// written to separate sub-meshes. Faces are culled against neighbours across +// chunk borders exactly like the greedy mesher. +ChunkMesh meshChunkLit(const World& world, const Vec3i& chunkCoord, const LightField& light); + +} // namespace vf diff --git a/core/src/LitMesh.cpp b/core/src/LitMesh.cpp new file mode 100644 index 0000000..734abf4 --- /dev/null +++ b/core/src/LitMesh.cpp @@ -0,0 +1,125 @@ +#include "vf/LitMesh.h" + +namespace vf { + +namespace { + +Face faceForDir(int axis, int sign) { + switch (axis) { + case 0: return sign > 0 ? Face::PosX : Face::NegX; + case 1: return sign > 0 ? Face::PosY : Face::NegY; + default: return sign > 0 ? Face::PosZ : Face::NegZ; + } +} + +void setAxis(int v[3], int axis, int value) { v[axis] = value; } + +struct CornerSample { + float ao; + float light; +}; + +} // namespace + +ChunkMesh meshChunkLit(const World& world, const Vec3i& chunkCoord, const LightField& light) { + ChunkMesh out; + const int origin[3] = {chunkCoord.x * chunkSize, chunkCoord.y * chunkSize, + chunkCoord.z * chunkSize}; + + auto occludes = [&](int gx, int gy, int gz) { + return occludesLight(world.blockAt(gx, gy, gz)); + }; + + for (int lx = 0; lx < chunkSize; ++lx) { + for (int ly = 0; ly < chunkSize; ++ly) { + for (int lz = 0; lz < chunkSize; ++lz) { + int gx = origin[0] + lx, gy = origin[1] + ly, gz = origin[2] + lz; + Block self = world.blockAt(gx, gy, gz); + if (isAir(self)) continue; + + for (int axis = 0; axis < 3; ++axis) { + for (int sign = -1; sign <= 1; sign += 2) { + int normal[3] = {0, 0, 0}; + setAxis(normal, axis, sign); + Block neighbour = world.blockAt(gx + normal[0], gy + normal[1], + gz + normal[2]); + if (!faceVisible(self, neighbour)) continue; + + int b = (axis + 1) % 3; + int c = (axis + 2) % 3; + int airCell[3] = {gx + normal[0], gy + normal[1], gz + normal[2]}; + int tile = faceTile(self, faceForDir(axis, sign)); + + CornerSample corner[4]; + static const int cornerBc[4][2] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; + Vec3 pos[4]; + for (int k = 0; k < 4; ++k) { + int bb = cornerBc[k][0]; + int cc = cornerBc[k][1]; + int du = bb ? 1 : -1; + int dv = cc ? 1 : -1; + + int s1[3] = {airCell[0], airCell[1], airCell[2]}; + s1[b] += du; + int s2[3] = {airCell[0], airCell[1], airCell[2]}; + s2[c] += dv; + int diag[3] = {airCell[0], airCell[1], airCell[2]}; + diag[b] += du; + diag[c] += dv; + + bool o1 = occludes(s1[0], s1[1], s1[2]); + bool o2 = occludes(s2[0], s2[1], s2[2]); + bool od = occludes(diag[0], diag[1], diag[2]); + corner[k].ao = static_cast(vertexAo(o1, o2, od)) / 3.0f; + + float sum = light.combinedAtWorld(airCell[0], airCell[1], airCell[2]); + int contributors = 1; + if (!o1) { sum += light.combinedAtWorld(s1[0], s1[1], s1[2]); ++contributors; } + if (!o2) { sum += light.combinedAtWorld(s2[0], s2[1], s2[2]); ++contributors; } + if (!od) { sum += light.combinedAtWorld(diag[0], diag[1], diag[2]); ++contributors; } + corner[k].light = sum / static_cast(contributors); + + int vp[3] = {gx, gy, gz}; + vp[axis] += (sign > 0) ? 1 : 0; + vp[b] += bb; + vp[c] += cc; + pos[k] = Vec3{static_cast(vp[0]), static_cast(vp[1]), + static_cast(vp[2])}; + } + + Mesh& target = isTransparent(self) ? out.transparent : out.opaque; + uint32_t start = static_cast(target.vertices.size()); + Vec3 nrm{static_cast(normal[0]), static_cast(normal[1]), + static_cast(normal[2])}; + static const float uvBc[4][2] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; + for (int k = 0; k < 4; ++k) { + Vertex vert; + vert.px = pos[k].x; vert.py = pos[k].y; vert.pz = pos[k].z; + vert.nx = nrm.x; vert.ny = nrm.y; vert.nz = nrm.z; + vert.u = uvBc[k][0]; vert.v = uvBc[k][1]; + vert.tile = static_cast(tile); + vert.light = corner[k].light; + vert.ao = corner[k].ao; + target.vertices.push_back(vert); + } + + bool flip = (corner[0].ao + corner[2].ao) < (corner[1].ao + corner[3].ao); + uint32_t triA[6] = {start, start + 1, start + 2, start, start + 2, start + 3}; + uint32_t triB[6] = {start + 1, start + 2, start + 3, + start + 1, start + 3, start}; + const uint32_t* tri = flip ? triB : triA; + if (sign > 0) { + for (int k = 0; k < 6; ++k) target.indices.push_back(tri[k]); + } else { + for (int k = 0; k < 6; ++k) target.indices.push_back(tri[5 - k]); + } + } + } + } + } + } + + return out; +} + +} // namespace vf