diff --git a/tests/testLitMesh.cpp b/tests/testLitMesh.cpp new file mode 100644 index 0000000..5414104 --- /dev/null +++ b/tests/testLitMesh.cpp @@ -0,0 +1,83 @@ +#include "testHarness.h" +#include "vf/LitMesh.h" + +using namespace vf; + +namespace { + +LightField lightFor(const World& world) { + return computeLight(world, {0, 0, 0}, {chunkSize, chunkSize, chunkSize}); +} + +float minAo(const Mesh& mesh) { + float lo = 1.0f; + for (const Vertex& v : mesh.vertices) lo = v.ao < lo ? v.ao : lo; + return lo; +} + +// Average light of the top face of the block at (5,5,5): its four vertices sit +// at y = 6 with an upward normal. Isolating it ignores the roof's own lit faces. +float coveredBlockTopLight(const Mesh& mesh) { + float sum = 0.0f; + int n = 0; + for (const Vertex& v : mesh.vertices) { + if (std::fabs(v.py - 6.0f) < 1e-3f && v.ny > 0.5f) { + sum += v.light; + ++n; + } + } + return n > 0 ? sum / static_cast(n) : -1.0f; +} + +bool hasTile(const Mesh& mesh, int tile) { + for (const Vertex& v : mesh.vertices) + if (static_cast(v.tile) == tile) return true; + return false; +} + +} // namespace + +VF_TEST(litIsolatedBlockHasSixUnoccludedFaces) { + World world; + world.setBlock(5, 5, 5, Block::Stone); + ChunkMesh mesh = meshChunkLit(world, {0, 0, 0}, lightFor(world)); + CHECK_EQ(mesh.opaque.quadCount(), static_cast(6)); + CHECK(mesh.transparent.empty()); + CHECK_NEAR(minAo(mesh.opaque), 1.0f, 1e-4f); // nothing occludes an isolated block +} + +VF_TEST(litInnerCornerDarkensVertex) { + World world; + world.setBlock(5, 5, 5, Block::Stone); + world.setBlock(6, 6, 5, Block::Stone); // occludes one edge of the top face + world.setBlock(5, 6, 6, Block::Stone); // occludes the other edge + ChunkMesh mesh = meshChunkLit(world, {0, 0, 0}, lightFor(world)); + CHECK_NEAR(minAo(mesh.opaque), 0.0f, 1e-4f); // both sides set -> AO 0 at that corner +} + +VF_TEST(litTopIsBrightRoofIsNot) { + World open; + open.setBlock(5, 5, 5, Block::Stone); + ChunkMesh openMesh = meshChunkLit(open, {0, 0, 0}, lightFor(open)); + CHECK(coveredBlockTopLight(openMesh.opaque) > 0.99f); // top face sees open sky + + World roofed; + roofed.setBlock(5, 5, 5, Block::Stone); + for (int x = 3; x <= 7; ++x) + for (int z = 3; z <= 7; ++z) + roofed.setBlock(x, 8, z, Block::Stone); + ChunkMesh roofedMesh = meshChunkLit(roofed, {0, 0, 0}, lightFor(roofed)); + CHECK(coveredBlockTopLight(roofedMesh.opaque) < 0.99f); // roof shadows this face +} + +VF_TEST(litWaterGoesToTransparentPass) { + World world; + world.setBlock(2, 2, 2, Block::Stone); + world.setBlock(5, 5, 5, Block::Water); + ChunkMesh mesh = meshChunkLit(world, {0, 0, 0}, lightFor(world)); + CHECK(!mesh.opaque.empty()); + CHECK(!mesh.transparent.empty()); + CHECK(hasTile(mesh.opaque, faceTile(Block::Stone, Face::PosY))); + CHECK(hasTile(mesh.transparent, faceTile(Block::Water, Face::PosY))); + CHECK(!hasTile(mesh.opaque, faceTile(Block::Water, Face::PosY))); +}