Files
EVEngine/test/voxel_render.cpp
T

2726 lines
101 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "Fixtures.h"
#include "SceneColor.h"
#include "zeroerr/assert.h"
#include "zeroerr/unittest.h"
#include "procgen/heightmap/TerrainSampler.h"
#include <SDL2/SDL.h>
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <memory>
#include <optional>
#include <vector>
#include "RenderImageAudit.h"
#include "graphics/AmbientOcclusion.h"
#include "graphics/AntiAliasing.h"
#include "graphics/Canvas.h"
#include "graphics/ClipSpace.h"
#include "graphics/DrawItem2D.h"
#include "graphics/Font.h"
#include "graphics/GBuffer.h"
#include "graphics/GlobalIllumination.h"
#include "graphics/Graphics.h"
#include "graphics/Grass.h"
#include "graphics/Light.h"
#include "graphics/Material.h"
#include "graphics/Mesh.h"
#include "graphics/Outline.h"
#include "graphics/Quad.h"
#include "graphics/RenderControl.h"
#include "graphics/RenderSystem.h"
#include "graphics/RenderSystem3D.h"
#include "graphics/ScreenSpaceReflection.h"
#include "graphics/Shader.h"
#include "graphics/Texture.h"
#include "graphics/Volumetric.h"
#include "graphics/Water.h"
#include "graphics/Waterfall.h"
#include "image/ImageData.h"
#include "voxel/Chunk.h"
#include "voxel/CubeTypeRegistry.h"
#include "voxel/FaceDir.h"
#include "voxel/Voxel.h"
#include "voxel/VoxelPack.h"
#include "voxel/VoxelWorld.h"
#include "window/Window.h"
#include <cstdio>
#include <filesystem>
#include <string>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/glm.hpp>
// Color lives in eve::graphics (see graphics/Canvas.h); keep the unqualified form.
using eve::graphics::Color;
using namespace eve::graphics;
using namespace eve::voxel;
using eve::image::ImageData;
static float luma(const Color &c) { return (c.r + c.g + c.b) / 3.f; }
/** How many samples on a coarse grid differ from the 3D clear color. */
static int countNonBgSamples(Graphics *gfx, const Color &bg, float eps = 0.08f) {
const int w = gfx->getWidth();
const int h = gfx->getHeight();
const float bl = luma(bg);
int n = 0;
for (int y = h / 10; y < h * 9 / 10; y += std::max(1, h / 12)) {
for (int x = w / 10; x < w * 9 / 10; x += std::max(1, w / 12)) {
if (std::fabs(luma(gfx->getPixel(x, y)) - bl) > eps) ++n;
}
}
return n;
}
static Texture *makeSolid(Graphics *gfx, uint8_t r, uint8_t g, uint8_t b) {
uint8_t px[4] = {r, g, b, 255};
return gfx->newTexture(1, 1, px);
}
/** Deterministic high-contrast palette for atlas tile `id` (0..127). */
static void atlasTileRgb(int id, uint8_t &r, uint8_t &g, uint8_t &b) {
static const uint8_t kPal[][3] = {
{220, 40, 40}, {40, 220, 40}, {40, 40, 220}, {220, 220, 40},
{220, 40, 220}, {40, 220, 220}, {240, 240, 240}, {220, 120, 40},
{120, 40, 220}, {40, 160, 120}, {200, 80, 120}, {80, 200, 80},
{80, 80, 200}, {200, 160, 40}, {160, 40, 80}, {40, 200, 200},
};
const int n = int(sizeof(kPal) / sizeof(kPal[0]));
const int i = ((id % n) + n) % n;
r = kPal[i][0];
g = kPal[i][1];
b = kPal[i][2];
}
/** NxM tile atlas (1px per tile). `tilesPerRow` must match draw call. */
static Texture *makeTileAtlas(Graphics *gfx, int tilesPerRow, int rows) {
const int w = std::max(1, tilesPerRow);
const int h = std::max(1, rows);
std::vector<uint8_t> px(size_t(w * h * 4));
for (int ty = 0; ty < h; ++ty) {
for (int tx = 0; tx < w; ++tx) {
const int id = tx + ty * w;
uint8_t r, g, b;
atlasTileRgb(id, r, g, b);
const size_t o = size_t((ty * w + tx) * 4);
px[o + 0] = r;
px[o + 1] = g;
px[o + 2] = b;
px[o + 3] = 255;
}
}
return gfx->newTexture(w, h, px.data());
}
static void fillCube(VoxelWorld *world, int x0, int y0, int z0, int s, uint8_t tex) {
for (int z = 0; z < s; ++z)
for (int y = 0; y < s; ++y)
for (int x = 0; x < s; ++x) world->setVoxel(x0 + x, y0 + y, z0 + z, tex);
}
static void tinyHud(Graphics *gfx) {
(void)gfx;
// Keep 2D present path consistent with other 3D tests.
auto *hud = Renderable2D::create();
hud->transform()->x = 0;
hud->transform()->y = 0;
hud->sprite()->width = 2;
hud->sprite()->height = 2;
hud->sprite()->r = 0.f;
hud->sprite()->g = 0.f;
hud->sprite()->b = 0.f;
hud->sprite()->a = 0.f;
hud->sprite()->visible = true;
}
static void hideLeftover3D() {
if (ecs::current()->getManager<Renderable3D>() != nullptr) {
auto view = ecs::View<Renderable3D, Renderable3D::MeshRenderer>();
for (auto it = view.begin(); it != view.end(); ++it) {
auto [mr] = *it;
mr->visible = false;
}
}
if (ecs::current()->getManager<Camera3D>() != nullptr) {
auto camView = ecs::View<Camera3D, Camera3D::Data>();
for (auto it = camView.begin(); it != camView.end(); ++it) {
auto [data] = *it;
data->active = false;
}
}
}
static void renderVoxelFrame(Graphics *gfx, VoxelWorld *world, Texture *atlas, int tilesPerRow,
const glm::vec3 &eye, const glm::vec3 &target, float viewRange,
bool faceCull) {
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::mat4 view = glm::lookAtRH(eye, target, glm::vec3(0.f, 1.f, 0.f));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 500.f);
const glm::mat4 vp = proj * view;
world->selectVisible(&vp[0][0], eye.x, eye.y, eye.z, viewRange, faceCull);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (!gfx->had3DThisFrame()) return;
gfx->setMesh3DViewProj(vp);
gfx->setMesh3DCameraPos(eye);
world->drawVisible(gfx, atlas, tilesPerRow);
RenderSystem::render(*gfx); // closes / presents via 2D path
}
static Color renderVoxelAoSample(Graphics *gfx, Texture *atlas, uint32_t ao) {
const uint32_t packed = PackedRect::pack(0, 0, 0, 4, 4, 1).bits;
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(2.f, 8.f, 2.f);
const glm::mat4 view =
glm::lookAtRH(eye, glm::vec3(2.f, 0.f, 2.f), glm::vec3(0.f, 0.f, -1.f));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(proj * view);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "posY", atlas, 1, &ao);
}
RenderSystem::render(*gfx);
return gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
}
/** @brief Vertex AO must shade identically on Vulkan and WebGPU voxel pipelines. */
TEST_CASE("voxel.render.vertexAoDarkensPixels") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 300, 220);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeSolid(gfx, 220, 220, 220);
const Color bright = renderVoxelAoSample(gfx, atlas, 0xFFu);
const Color dark = renderVoxelAoSample(gfx, atlas, 0x00u);
REQUIRE(luma(bright) > 0.25f);
// AO multiplies linear radiance; ACES and sRGB compress the displayed ratio.
REQUIRE(luma(testSceneLinearColor(dark)) < luma(testSceneLinearColor(bright)) * 0.6f);
}
// NOTE: Graphics is a process-wide singleton — reuse one window for these cases.
TEST_CASE("voxel.render.smokeDrawVisibleProducesPixels") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
// Small solid block near origin so a +Z camera sees it.
for (int z = 0; z < 4; ++z)
for (int y = 0; y < 4; ++y)
for (int x = 0; x < 4; ++x) world->setVoxel(x, y, z, 1);
world->remeshDirty();
CHECK(world->getOrCreateChunk(0, 0, 0)->totalRectCount() > 0);
Texture *atlas = makeSolid(gfx, 230, 60, 50);
REQUIRE(atlas != nullptr);
const glm::vec3 eye(2.f, 2.f, 12.f);
const glm::vec3 target(2.f, 2.f, 2.f);
for (int i = 0; i < 3; ++i)
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
// Background is dark slate; a lit red face should raise luma / red channel.
CHECK(mid.r > 0.15f);
CHECK(luma(mid) > 0.08f);
CHECK(luma(mid) < 0.98f);
}
TEST_CASE("voxel.render.emptyInstancesNoCrash") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 160, 120);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::mat4 view = glm::lookAtRH(glm::vec3(0, 0, 5), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.1f, 0.1f, 0.12f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
// count == 0 must be a no-op.
gfx->drawVoxelFaceInstances(nullptr, 0, 0.f, 0.f, 0.f, "posZ", nullptr, 1);
uint32_t dummy = PackedRect::pack(0, 0, 0, 1, 1, 0).bits;
// Also tolerate a single instance draw.
gfx->drawVoxelFaceInstances(&dummy, 1, 0.f, 0.f, 0.f, "posZ", nullptr, 1);
}
RenderSystem::render(*gfx);
Color c = gfx->getPixel(1, 1);
CHECK(c.a >= 0.f);
}
TEST_CASE("voxel.render.invalidFaceDirThrows") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 160, 120);
const glm::mat4 vp(1.f);
gfx->begin3DFrame();
if (!gfx->had3DThisFrame()) {
RenderSystem::render(*gfx);
return;
}
gfx->setMesh3DViewProj(vp);
uint32_t dummy = PackedRect::pack(0, 0, 0, 1, 1, 0).bits;
bool threw = false;
try {
gfx->drawVoxelFaceInstances(&dummy, 1, 0.f, 0.f, 0.f, "sideways", nullptr, 1);
} catch (...) {
threw = true;
}
CHECK(threw);
// Recover present path.
RenderSystem::render(*gfx);
}
TEST_CASE("voxel.render.atlasTexIndexTintVisible") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
// Texture id 1 (air is 0); solid red 1×1 atlas.
world->setVoxel(0, 0, 0, 1);
world->remeshDirty();
Texture *red = makeSolid(gfx, 240, 40, 40);
const glm::vec3 eye(0.5f, 0.5f, 6.f);
const glm::vec3 target(0.5f, 0.5f, 0.5f);
for (int i = 0; i < 3; ++i)
renderVoxelFrame(gfx, world.get(), red, 1, eye, target, 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.r > mid.g);
CHECK(mid.r > mid.b);
CHECK(mid.r > 0.12f);
}
TEST_CASE("voxel.render.faceCullChangesCoverage") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 8; ++z)
for (int y = 0; y < 8; ++y)
for (int x = 0; x < 8; ++x) world->setVoxel(x, y, z, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 200, 200, 210);
// Camera on +X; with face cull, back faces dropped — still should see the +X wall.
const glm::vec3 eye(20.f, 4.f, 4.f);
const glm::vec3 target(4.f, 4.f, 4.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
CHECK(world->getVisibleBatchCount() >= 1);
bool sawPosX = false, sawNegX = false;
for (int i = 0; i < world->getVisibleBatchCount(); ++i) {
if (world->getVisibleBatch(i).dir == FaceDir::PosX) sawPosX = true;
if (world->getVisibleBatch(i).dir == FaceDir::NegX) sawNegX = true;
}
CHECK(sawPosX);
CHECK(!sawNegX);
Color withCull = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, false);
CHECK(world->getVisibleRectCount() >= 6);
Color noCull = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
// Both frames should show geometry (not pure background).
CHECK(luma(withCull) > 0.06f);
CHECK(luma(noCull) > 0.06f);
}
TEST_CASE("voxel.render.outOfRangeDrawsNothingBright") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
world->getOrCreateChunk(0, 0, 0)->fill(1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 255, 255, 255);
const glm::vec3 eye(16.f, 16.f, 80.f);
const glm::vec3 target(16.f, 16.f, 16.f);
// Tiny range → selectVisible yields no batches.
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 1.f, true);
CHECK_EQ(world->getVisibleBatchCount(), 0);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
// Should be near the dark clear color.
CHECK(luma(mid) < 0.2f);
}
TEST_CASE("voxel.render.multiBatchSameFrame") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
// Two separated pillars → multiple face batches.
for (int y = 0; y < 6; ++y) {
world->setVoxel(2, y, 2, 1);
world->setVoxel(10, y, 10, 1);
}
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 180, 170, 90);
const glm::vec3 eye(6.f, 8.f, 22.f);
const glm::vec3 target(6.f, 3.f, 6.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
CHECK(world->getVisibleBatchCount() >= 2);
CHECK(world->getVisibleRectCount() >= 2);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
TEST_CASE("voxel.render.drawVisibleMatchesManualBatches") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 3; ++z)
for (int y = 0; y < 3; ++y)
for (int x = 0; x < 3; ++x) world->setVoxel(x, y, z, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 90, 140, 220);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(1.5f, 1.5f, 10.f);
const glm::vec3 target(1.5f, 1.5f, 1.5f);
const glm::mat4 view = glm::lookAtRH(eye, target, glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 200.f);
const glm::mat4 vp = proj * view;
world->selectVisible(&vp[0][0], eye.x, eye.y, eye.z, 200.f, true);
const int batches = world->getVisibleBatchCount();
REQUIRE(batches > 0);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->setMesh3DCameraPos(eye);
for (int i = 0; i < batches; ++i) {
const DrawBatch &b = world->getVisibleBatch(i);
gfx->drawVoxelFaceInstances(b.packed, b.count, b.chunk->originX(), b.chunk->originY(),
b.chunk->originZ(), faceDirName(b.dir), atlas, 1);
}
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.06f);
}
TEST_CASE("voxel.render.requiresBegin3DFrame") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 160, 120);
uint32_t dummy = PackedRect::pack(0, 0, 0, 1, 1, 1).bits;
bool threw = false;
try {
gfx->drawVoxelFaceInstances(&dummy, 1, 0.f, 0.f, 0.f, "posZ", nullptr, 1);
} catch (...) {
threw = true;
}
CHECK(threw);
}
TEST_CASE("voxel.render.faceDirAliasesDraw") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(0.5f, 0.5f, 6.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(0.5f, 0.5f, 0.5f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
// One +Z face covering the voxel front.
uint32_t packed = PackedRect::pack(0, 0, 0, 1, 1, 1).bits;
Texture *atlas = makeSolid(gfx, 40, 180, 220);
const char *aliases[] = {"posZ", "+z"};
for (const char *name : aliases) {
gfx->setBackgroundColor(Color(0.04f, 0.05f, 0.06f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, name, atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
}
TEST_CASE("voxel.render.nullAtlasUsesWhite") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 2; ++z)
for (int y = 0; y < 2; ++y)
for (int x = 0; x < 2; ++x) world->setVoxel(x, y, z, 1);
world->remeshDirty();
const glm::vec3 eye(1.f, 1.f, 8.f);
const glm::vec3 target(1.f, 1.f, 1.f);
renderVoxelFrame(gfx, world.get(), nullptr, 1, eye, target, 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
TEST_CASE("voxel.render.largeInstanceCount") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
// Many non-merged top faces: sparse pillars.
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 16; ++z)
for (int x = 0; x < 16; ++x)
if (((x * 3 + z * 5) % 7) == 0) world->setVoxel(x, 0, z, 1);
world->remeshDirty();
CHECK(world->getOrCreateChunk(0, 0, 0)->totalRectCount() > 20);
Texture *atlas = makeSolid(gfx, 200, 180, 60);
const glm::vec3 eye(8.f, 10.f, 28.f);
const glm::vec3 target(8.f, 0.f, 8.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
CHECK(world->getVisibleRectCount() > 10);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.farChunkOrigin") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
// Chunk (2,0,0) origin at x=64.
for (int z = 0; z < 4; ++z)
for (int y = 0; y < 4; ++y)
for (int x = 0; x < 4; ++x) world->setVoxel(64 + x, y, z, 1);
world->remeshDirty();
CHECK(world->hasChunk(2, 0, 0));
Texture *atlas = makeSolid(gfx, 220, 80, 200);
const glm::vec3 eye(66.f, 2.f, 16.f);
const glm::vec3 target(66.f, 2.f, 2.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
CHECK(world->getVisibleChunkCount() >= 1);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
TEST_CASE("voxel.render.depthNearOccludesFar") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
// Near red wall at z=2, far green wall at z=0 — camera looks -Z from z=12.
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int y = 0; y < 4; ++y)
for (int x = 0; x < 4; ++x) {
world->setVoxel(x, y, 0, 2); // far (will use green atlas tile... single solid tex)
world->setVoxel(x, y, 2, 1); // near
}
world->remeshDirty();
// Use bright red atlas — both walls same color; verify something draws, then
// compare against far-only scene luma stability (near wall should dominate).
Texture *red = makeSolid(gfx, 240, 30, 30);
const glm::vec3 eye(2.f, 2.f, 12.f);
const glm::vec3 target(2.f, 2.f, 2.f);
renderVoxelFrame(gfx, world.get(), red, 1, eye, target, 200.f, true);
Color withNear = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(withNear.r > 0.12f);
// Remove near wall voxels.
for (int y = 0; y < 4; ++y)
for (int x = 0; x < 4; ++x) world->setVoxel(x, y, 2, 0);
world->remeshDirty();
renderVoxelFrame(gfx, world.get(), red, 1, eye, target, 200.f, true);
Color farOnly = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
// Far wall still visible (same color); just ensure render path still works.
CHECK(farOnly.r > 0.08f);
CHECK(luma(withNear) > 0.05f);
}
TEST_CASE("voxel.render.multiFrameSlotReuse") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
world->setVoxel(0, 0, 0, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 160, 160, 200);
const glm::vec3 eye(0.5f, 0.5f, 5.f);
const glm::vec3 target(0.5f, 0.5f, 0.5f);
for (int frame = 0; frame < 6; ++frame) {
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
}
TEST_CASE("voxel.render.allSixFacesFromOrbit") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 256, 192);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 6; ++z)
for (int y = 0; y < 6; ++y)
for (int x = 0; x < 6; ++x) world->setVoxel(x, y, z, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 210, 200, 190);
struct Cam {
glm::vec3 eye;
glm::vec3 target;
FaceDir expect;
};
const Cam cams[] = {
{{20.f, 3.f, 3.f}, {3.f, 3.f, 3.f}, FaceDir::PosX},
{{-8.f, 3.f, 3.f}, {3.f, 3.f, 3.f}, FaceDir::NegX},
{{3.f, 20.f, 3.f}, {3.f, 3.f, 3.f}, FaceDir::PosY},
{{3.f, -8.f, 3.f}, {3.f, 3.f, 3.f}, FaceDir::NegY},
{{3.f, 3.f, 20.f}, {3.f, 3.f, 3.f}, FaceDir::PosZ},
{{3.f, 3.f, -8.f}, {3.f, 3.f, 3.f}, FaceDir::NegZ},
};
for (const auto &c : cams) {
// For top/bottom views, renderVoxelFrame uses +Y up which is fine for ±X/±Z;
// for ±Y eye, lookAtRH with up=(0,1,0) degenerates — use custom path.
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
glm::vec3 up(0.f, 1.f, 0.f);
if (std::fabs(c.eye.y - c.target.y) > 10.f) up = glm::vec3(0.f, 0.f, 1.f);
const glm::mat4 view = glm::lookAtRH(c.eye, c.target, up);
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 200.f);
const glm::mat4 vp = proj * view;
world->selectVisible(&vp[0][0], c.eye.x, c.eye.y, c.eye.z, 200.f, true);
bool saw = false;
for (int i = 0; i < world->getVisibleBatchCount(); ++i)
if (world->getVisibleBatch(i).dir == c.expect) saw = true;
CHECK(saw);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->setMesh3DCameraPos(c.eye);
world->drawVisible(gfx, atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
}
TEST_CASE("voxel.render.tilesPerRowAtlasSample") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
// 2×2 atlas: index 1 (col1,row0) = pure green.
uint8_t px[16] = {
200, 40, 40, 255, // 0 red
40, 200, 40, 255, // 1 green
40, 40, 200, 255, // 2 blue
200, 200, 40, 255, // 3 yellow
};
Texture *atlas = gfx->newTexture(2, 2, px);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
// tex id 1 → green tile
for (int z = 0; z < 3; ++z)
for (int y = 0; y < 3; ++y)
for (int x = 0; x < 3; ++x) world->setVoxel(x, y, z, 1);
world->remeshDirty();
const glm::vec3 eye(1.5f, 1.5f, 10.f);
const glm::vec3 target(1.5f, 1.5f, 1.5f);
renderVoxelFrame(gfx, world.get(), atlas, 2, eye, target, 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.g > mid.r);
CHECK(mid.g > mid.b);
CHECK(mid.g > 0.1f);
}
TEST_CASE("voxel.render.editRemeshChangesPixels") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
Texture *atlas = makeSolid(gfx, 230, 230, 240);
const glm::vec3 eye(4.f, 4.f, 18.f);
const glm::vec3 target(4.f, 4.f, 4.f);
// Empty world → dark
world->remeshDirty();
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
CHECK_EQ(world->getVisibleRectCount(), 0);
Color empty = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(empty) < 0.2f);
for (int z = 0; z < 8; ++z)
for (int y = 0; y < 8; ++y)
for (int x = 0; x < 8; ++x) world->setVoxel(x, y, z, 1);
world->remeshDirty();
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
Color filled = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(filled) > luma(empty) + 0.03f);
}
TEST_CASE("voxel.render.emptyWorldDrawVisible") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 200, 150);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
Texture *atlas = makeSolid(gfx, 255, 255, 255);
const glm::vec3 eye(0.f, 0.f, 5.f);
const glm::vec3 target(0.f, 0.f, 0.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 100.f, true);
CHECK_EQ(world->getVisibleBatchCount(), 0);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) < 0.25f);
}
TEST_CASE("voxel.render.negFaceDirAliases") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
// Camera on -Z looking toward origin → see NegZ? Actually looking +Z direction from -Z.
// Eye at z=-4 looking at z=0.5 → see -Z face of voxel at origin (plane z=0).
const glm::vec3 eye(0.5f, 0.5f, -4.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(0.5f, 0.5f, 0.5f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
uint32_t packed = PackedRect::pack(0, 0, 0, 1, 1, 1).bits;
Texture *atlas = makeSolid(gfx, 180, 90, 40);
for (const char *name : {"negZ", "-z"}) {
gfx->setBackgroundColor(Color(0.04f, 0.05f, 0.06f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, name, atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
}
TEST_CASE("voxel.render.manyChunksBatched") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int cx = 0; cx < 3; ++cx)
for (int cz = 0; cz < 3; ++cz) {
for (int y = 0; y < 2; ++y)
for (int x = 0; x < 2; ++x)
for (int z = 0; z < 2; ++z)
world->setVoxel(cx * 32 + x + 8, y, cz * 32 + z + 8, 1);
}
world->remeshDirty();
CHECK_EQ(world->getChunkCount(), 9);
Texture *atlas = makeSolid(gfx, 120, 160, 200);
const glm::vec3 eye(48.f, 20.f, 120.f);
const glm::vec3 target(48.f, 1.f, 48.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 300.f, true);
CHECK(world->getVisibleChunkCount() >= 3);
CHECK(world->getVisibleBatchCount() >= 3);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.singleFaceManualDraw") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 256, 192);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
// Large +Z quad covering much of the view.
uint32_t packed = PackedRect::pack(0, 0, 0, 8, 8, 1).bits;
Texture *atlas = makeSolid(gfx, 50, 200, 80);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(4.f, 4.f, 20.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(4.f, 4.f, 1.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(45.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.05f, 0.07f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "posZ", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.g > mid.r);
CHECK(mid.g > 0.08f);
// Corners of screen may still be background depending on FOV; center must be lit.
Color corner = gfx->getPixel(2, 2);
CHECK(luma(mid) > luma(corner));
}
TEST_CASE("voxel.render.tilesPerRowBlueTile") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint8_t px[16] = {
200, 40, 40, 255, 40, 200, 40, 255, 40, 40, 220, 255, 200, 200, 40, 255,
};
Texture *atlas = gfx->newTexture(2, 2, px);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
// tex id 2 → blue (row1,col0)
for (int z = 0; z < 4; ++z)
for (int y = 0; y < 4; ++y)
for (int x = 0; x < 4; ++x) world->setVoxel(x, y, z, 2);
world->remeshDirty();
const glm::vec3 eye(2.f, 2.f, 12.f);
renderVoxelFrame(gfx, world.get(), atlas, 2, eye, glm::vec3(2.f, 2.f, 2.f), 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.b > mid.r);
CHECK(mid.b > mid.g);
CHECK(mid.b > 0.1f);
}
TEST_CASE("voxel.render.instanceCountGrowth") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeSolid(gfx, 200, 200, 210);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(8.f, 8.f, 40.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(8.f, 0.f, 8.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 200.f);
const glm::mat4 vp = proj * view;
// Grow instance buffer across draws in one frame: 1, then many.
std::vector<uint32_t> many;
many.reserve(64);
for (int z = 0; z < 8; ++z)
for (int x = 0; x < 8; ++x)
many.push_back(PackedRect::pack(x, 0, z, 1, 1, 1).bits);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
uint32_t one = many[0];
gfx->drawVoxelFaceInstances(&one, 1, 0.f, 0.f, 0.f, "posY", atlas, 1);
gfx->drawVoxelFaceInstances(many.data(), int(many.size()), 0.f, 0.f, 0.f, "posY", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.posXWallFromSide") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 8; ++z)
for (int y = 0; y < 8; ++y) world->setVoxel(0, y, z, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 230, 120, 50);
const glm::vec3 eye(12.f, 4.f, 4.f);
const glm::vec3 target(0.f, 4.f, 4.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 100.f, true);
bool sawPosX = false;
for (int i = 0; i < world->getVisibleBatchCount(); ++i)
if (world->getVisibleBatch(i).dir == FaceDir::PosX) sawPosX = true;
CHECK(sawPosX);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.r > 0.1f);
}
TEST_CASE("voxel.render.clearColorWhenCulled") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
world->getOrCreateChunk(0, 0, 0)->fill(1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 255, 255, 255);
// Camera behind looking away from chunk.
const glm::vec3 eye(16.f, 16.f, 16.f);
const glm::vec3 target(16.f, 16.f, 100.f); // look +Z, chunk is around same place but...
// Better: put eye far and look opposite.
renderVoxelFrame(gfx, world.get(), atlas, 1, glm::vec3(-40.f, 16.f, 16.f),
glm::vec3(-80.f, 16.f, 16.f), 200.f, true);
// Chunk at origin not in front of camera looking toward -X further.
CHECK_EQ(world->getVisibleChunkCount(), 0);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) < 0.22f);
}
TEST_CASE("voxel.render.posYFromAbove") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 6; ++z)
for (int x = 0; x < 6; ++x) world->setVoxel(x, 0, z, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 200, 180, 60);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(3.f, 18.f, 3.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(3.f, 0.f, 3.f), glm::vec3(0, 0, -1));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
world->selectVisible(&vp[0][0], eye.x, eye.y, eye.z, 100.f, true);
bool sawPosY = false;
for (int i = 0; i < world->getVisibleBatchCount(); ++i)
if (world->getVisibleBatch(i).dir == FaceDir::PosY) sawPosY = true;
CHECK(sawPosY);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
world->drawVisible(gfx, atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
TEST_CASE("voxel.render.twoOriginsSameFrame") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t a = PackedRect::pack(0, 0, 0, 2, 2, 1).bits;
uint32_t b = PackedRect::pack(0, 0, 0, 2, 2, 1).bits;
Texture *atlas = makeSolid(gfx, 90, 200, 120);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(4.f, 4.f, 20.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(4.f, 1.f, 4.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&a, 1, 0.f, 0.f, 0.f, "posY", atlas, 1);
gfx->drawVoxelFaceInstances(&b, 1, 6.f, 0.f, 6.f, "posY", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.faceCullRectCountAboutHalf") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
world->getOrCreateChunk(0, 0, 0)->fill(1);
world->remeshDirty();
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(40.f, 16.f, 16.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(16.f, 16.f, 16.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 200.f);
const glm::mat4 vp = proj * view;
world->selectVisible(&vp[0][0], eye.x, eye.y, eye.z, 200.f, false);
CHECK_EQ(world->getVisibleRectCount(), 6);
world->selectVisible(&vp[0][0], eye.x, eye.y, eye.z, 200.f, true);
CHECK(world->getVisibleRectCount() >= 1);
CHECK(world->getVisibleRectCount() <= 5);
CHECK(world->getVisibleRectCount() < 6);
}
TEST_CASE("voxel.render.removeVoxelsDarkens") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 6; ++z)
for (int y = 0; y < 6; ++y)
for (int x = 0; x < 6; ++x) world->setVoxel(x, y, z, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 240, 240, 250);
const glm::vec3 eye(3.f, 3.f, 16.f);
const glm::vec3 target(3.f, 3.f, 3.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 100.f, true);
Color filled = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(filled) > 0.08f);
world->clear();
world->remeshDirty();
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 100.f, true);
Color empty = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(empty) < luma(filled));
CHECK(luma(empty) < 0.22f);
}
TEST_CASE("voxel.render.posXAliasPlusX") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t packed = PackedRect::pack(0, 0, 0, 4, 4, 1).bits;
Texture *atlas = makeSolid(gfx, 220, 80, 40);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(10.f, 2.f, 2.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(1.f, 2.f, 2.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.05f, 0.07f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "+x", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.r > 0.08f);
}
TEST_CASE("voxel.render.stressHundredsOfInstances") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::vector<uint32_t> many;
many.reserve(256);
for (int z = 0; z < 16; ++z)
for (int x = 0; x < 16; ++x)
many.push_back(PackedRect::pack(x, 0, z, 1, 1, 1).bits);
CHECK_EQ(int(many.size()), 256);
Texture *atlas = makeSolid(gfx, 180, 190, 200);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(8.f, 12.f, 30.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(8.f, 0.f, 8.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 200.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(many.data(), int(many.size()), 0.f, 0.f, 0.f, "posY", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.alternatingAtlasFrames") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 4; ++z)
for (int y = 0; y < 4; ++y)
for (int x = 0; x < 4; ++x) world->setVoxel(x, y, z, 1);
world->remeshDirty();
Texture *red = makeSolid(gfx, 230, 40, 40);
Texture *blue = makeSolid(gfx, 40, 40, 230);
const glm::vec3 eye(2.f, 2.f, 12.f);
const glm::vec3 target(2.f, 2.f, 2.f);
renderVoxelFrame(gfx, world.get(), red, 1, eye, target, 100.f, true);
Color cRed = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(cRed.r > cRed.b);
renderVoxelFrame(gfx, world.get(), blue, 1, eye, target, 100.f, true);
Color cBlue = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(cBlue.b > cBlue.r);
}
TEST_CASE("voxel.render.chunkSeamDoubleFaceStillDraws") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
// Voxels on both sides of x=32 seam.
for (int y = 0; y < 4; ++y)
for (int z = 0; z < 4; ++z) {
world->setVoxel(31, y, z, 1);
world->setVoxel(32, y, z, 1);
}
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 200, 160, 100);
const glm::vec3 eye(48.f, 2.f, 2.f);
const glm::vec3 target(32.f, 2.f, 2.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
CHECK(world->getVisibleChunkCount() >= 1);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.negYFromBelow") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 6; ++z)
for (int x = 0; x < 6; ++x) world->setVoxel(x, 4, z, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 180, 120, 60);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(3.f, -10.f, 3.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(3.f, 4.f, 3.f), glm::vec3(0, 0, -1));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
world->selectVisible(&vp[0][0], eye.x, eye.y, eye.z, 100.f, true);
bool sawNegY = false;
for (int i = 0; i < world->getVisibleBatchCount(); ++i)
if (world->getVisibleBatch(i).dir == FaceDir::NegY) sawNegY = true;
CHECK(sawNegY);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
world->drawVisible(gfx, atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.posZWallFromFront") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t packed = PackedRect::pack(0, 0, 0, 6, 6, 1).bits;
Texture *atlas = makeSolid(gfx, 80, 200, 220);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(3.f, 3.f, 14.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(3.f, 3.f, 1.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.05f, 0.07f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "posZ", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
TEST_CASE("voxel.render.tilesPerRowZeroClamped") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t packed = PackedRect::pack(0, 0, 0, 4, 4, 1).bits;
Texture *atlas = makeSolid(gfx, 210, 90, 50);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(2.f, 8.f, 2.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(2.f, 0.f, 2.f), glm::vec3(0, 0, -1));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
// tilesPerRow <= 0 should clamp to 1 and still sample the solid atlas.
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "posY", atlas, 0);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.zeroCountAfterLargeDraw") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::vector<uint32_t> many;
for (int i = 0; i < 64; ++i) many.push_back(PackedRect::pack(i % 8, 0, i / 8, 1, 1, 1).bits);
Texture *atlas = makeSolid(gfx, 200, 200, 200);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(4.f, 10.f, 4.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(4.f, 0.f, 4.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(many.data(), int(many.size()), 0.f, 0.f, 0.f, "posY", atlas, 1);
gfx->drawVoxelFaceInstances(many.data(), 0, 0.f, 0.f, 0.f, "posY", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.manualAllSixDirsOneFrame") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t packed = PackedRect::pack(0, 0, 0, 2, 2, 1).bits;
Texture *atlas = makeSolid(gfx, 220, 220, 40);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
// Orbit-ish view so several faces of a unit cube-ish placement are on screen.
const glm::vec3 eye(8.f, 8.f, 8.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(1.f, 1.f, 1.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(55.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
const char *dirs[6] = {"posX", "negX", "posY", "negY", "posZ", "negZ"};
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
for (const char *d : dirs)
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, d, atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.03f);
}
TEST_CASE("voxel.render.rangeCullDarkensDistant") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 4; ++z)
for (int y = 0; y < 4; ++y)
for (int x = 0; x < 4; ++x) world->setVoxel(x + 64, y, z, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 240, 240, 250);
const glm::vec3 eye(66.f, 2.f, 20.f);
const glm::vec3 target(66.f, 2.f, 2.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 200.f, true);
Color nearEnough = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(nearEnough) > 0.06f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 5.f, true);
Color culled = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(culled) < luma(nearEnough));
CHECK(luma(culled) < 0.22f);
}
TEST_CASE("voxel.render.negZAliasMinusZ") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t packed = PackedRect::pack(0, 0, 5, 4, 4, 1).bits;
Texture *atlas = makeSolid(gfx, 40, 180, 90);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(2.f, 2.f, -6.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(2.f, 2.f, 5.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.05f, 0.07f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "-z", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.fullChunkSixFacesOrbitPixel") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
world->getOrCreateChunk(0, 0, 0)->fill(1);
world->remeshDirty();
CHECK_EQ(world->getChunk(0, 0, 0)->totalRectCount(), 6);
Texture *atlas = makeSolid(gfx, 160, 170, 180);
const glm::vec3 eye(48.f, 40.f, 48.f);
const glm::vec3 target(16.f, 16.f, 16.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 300.f, true);
CHECK(world->getVisibleBatchCount() >= 2);
CHECK(world->getVisibleBatchCount() <= 3);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
TEST_CASE("voxel.render.atlas4x4_tex1_green") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeTileAtlas(gfx, 4, 4);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
// Voxel storage 0 = air; solid tex starts at 1 → atlas tile 1 (green).
fillCube(world.get(), 0, 0, 0, 4, 1);
world->remeshDirty();
const glm::vec3 eye(2.f, 2.f, 14.f);
renderVoxelFrame(gfx, world.get(), atlas, 4, eye, glm::vec3(2.f, 2.f, 2.f), 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.g > mid.r);
CHECK(mid.g > mid.b);
CHECK(mid.g > 0.08f);
}
TEST_CASE("voxel.render.atlas4x4_eachPaletteChannel") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 280, 200);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeTileAtlas(gfx, 4, 4);
const glm::vec3 eye(2.f, 2.f, 14.f);
const glm::vec3 target(2.f, 2.f, 2.f);
// tex 1 green, 2 blue, 3 yellow, 4 magenta, 5 cyan
struct Expect {
uint8_t tex;
char channel; // 'r','g','b' dominant or 'y' r&g, 'm' r&b, 'c' g&b
};
const Expect cases[] = {{1, 'g'}, {2, 'b'}, {3, 'y'}, {4, 'm'}, {5, 'c'}};
for (const Expect &e : cases) {
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
fillCube(world.get(), 0, 0, 0, 4, e.tex);
world->remeshDirty();
renderVoxelFrame(gfx, world.get(), atlas, 4, eye, target, 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
if (e.channel == 'g') {
CHECK(mid.g > mid.r);
CHECK(mid.g > mid.b);
} else if (e.channel == 'b') {
CHECK(mid.b > mid.r);
CHECK(mid.b > mid.g);
} else if (e.channel == 'y') {
CHECK(mid.r > mid.b);
CHECK(mid.g > mid.b);
} else if (e.channel == 'm') {
CHECK(mid.r > mid.g);
CHECK(mid.b > mid.g);
} else if (e.channel == 'c') {
CHECK(mid.g > mid.r);
CHECK(mid.b > mid.r);
}
}
}
TEST_CASE("voxel.render.atlas8x8_highTexIndex") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
// 8×8 = 64 tiles; tex 15 uses palette slot 15 → cyan-ish (40,200,200)
Texture *atlas = makeTileAtlas(gfx, 8, 8);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
fillCube(world.get(), 0, 0, 0, 5, 15);
world->remeshDirty();
CHECK_EQ(world->getChunk(0, 0, 0)->faceRects(FaceDir::PosZ)[0].tex(), 15);
const glm::vec3 eye(2.5f, 2.5f, 16.f);
renderVoxelFrame(gfx, world.get(), atlas, 8, eye, glm::vec3(2.5f, 2.5f, 2.5f), 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.g > mid.r);
CHECK(mid.b > mid.r);
CHECK(luma(mid) > 0.05f);
}
TEST_CASE("voxel.render.atlas16x8_tex63") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
// tilesPerRow=16, 8 rows → indices 0..127
Texture *atlas = makeTileAtlas(gfx, 16, 8);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
fillCube(world.get(), 0, 0, 0, 4, 63);
world->remeshDirty();
REQUIRE(world->getChunk(0, 0, 0)->faceRectCount(FaceDir::PosY) >= 1);
CHECK_EQ(world->getChunk(0, 0, 0)->faceRects(FaceDir::PosY)[0].tex(), 63);
const glm::vec3 eye(2.f, 10.f, 2.f);
const glm::mat4 view =
glm::lookAtRH(eye, glm::vec3(2.f, 2.f, 2.f), glm::vec3(0, 0, -1));
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
world->selectVisible(&vp[0][0], eye.x, eye.y, eye.z, 100.f, true);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
world->drawVisible(gfx, atlas, 16);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.swapTexIdChangesAtlasColor") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeTileAtlas(gfx, 4, 4);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
fillCube(world.get(), 0, 0, 0, 4, 1); // green
world->remeshDirty();
const glm::vec3 eye(2.f, 2.f, 14.f);
const glm::vec3 target(2.f, 2.f, 2.f);
renderVoxelFrame(gfx, world.get(), atlas, 4, eye, target, 100.f, true);
Color green = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(green.g > green.r);
for (int z = 0; z < 4; ++z)
for (int y = 0; y < 4; ++y)
for (int x = 0; x < 4; ++x) world->setVoxel(x, y, z, 2); // blue
world->remeshDirty();
renderVoxelFrame(gfx, world.get(), atlas, 4, eye, target, 100.f, true);
Color blue = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(blue.b > blue.g);
CHECK(blue.b > green.b);
}
TEST_CASE("voxel.render.multiTexStripesDoNotMerge") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeTileAtlas(gfx, 4, 4);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
// Horizontal stripes of alternating tex 1 and 2 on a slab.
for (int z = 0; z < 8; ++z)
for (int x = 0; x < 8; ++x) world->setVoxel(x, 0, z, (z % 2) ? 1 : 2);
world->remeshDirty();
Chunk *c = world->getChunk(0, 0, 0);
REQUIRE(c != nullptr);
CHECK(c->faceRectCount(FaceDir::PosY) >= 4);
const glm::vec3 eye(4.f, 12.f, 4.f);
const glm::mat4 view =
glm::lookAtRH(eye, glm::vec3(4.f, 0.f, 4.f), glm::vec3(0, 0, -1));
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
world->selectVisible(&vp[0][0], eye.x, eye.y, eye.z, 100.f, true);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
world->drawVisible(gfx, atlas, 4);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.manualDrawDifferentTexIndices") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeTileAtlas(gfx, 4, 4);
// Two quads side by side: tex1 green @ x=0, tex2 blue @ origin shifted.
uint32_t green = PackedRect::pack(0, 0, 0, 3, 3, 1).bits;
uint32_t blue = PackedRect::pack(0, 0, 0, 3, 3, 2).bits;
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(1.5f, 8.f, 1.5f);
const glm::mat4 view =
glm::lookAtRH(eye, glm::vec3(1.5f, 0.f, 1.5f), glm::vec3(0, 0, -1));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&green, 1, 0.f, 0.f, 0.f, "posY", atlas, 4);
}
RenderSystem::render(*gfx);
Color cGreen = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(cGreen.g > cGreen.b);
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&blue, 1, 0.f, 0.f, 0.f, "posY", atlas, 4);
}
RenderSystem::render(*gfx);
Color cBlue = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(cBlue.b > cBlue.g);
}
TEST_CASE("voxel.render.maxTex127_on16WideAtlas") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeTileAtlas(gfx, 16, 8);
uint32_t packed = PackedRect::pack(0, 0, 0, 4, 4, 127).bits;
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(2.f, 10.f, 2.f);
const glm::mat4 view =
glm::lookAtRH(eye, glm::vec3(2.f, 0.f, 2.f), glm::vec3(0, 0, -1));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "posY", atlas, 16);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.fourTexBlocksSameFrame") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 360, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeTileAtlas(gfx, 4, 4);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
fillCube(world.get(), 0, 0, 0, 3, 1);
fillCube(world.get(), 5, 0, 0, 3, 2);
fillCube(world.get(), 0, 0, 5, 3, 3);
fillCube(world.get(), 5, 0, 5, 3, 4);
world->remeshDirty();
// Four different top-face textures → at least 4 PosY rects.
CHECK(world->getChunk(0, 0, 0)->faceRectCount(FaceDir::PosY) >= 4);
const glm::vec3 eye(4.f, 16.f, 4.f);
const glm::mat4 view =
glm::lookAtRH(eye, glm::vec3(4.f, 0.f, 4.f), glm::vec3(0, 0, -1));
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(55.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
world->selectVisible(&vp[0][0], eye.x, eye.y, eye.z, 100.f, true);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
world->drawVisible(gfx, atlas, 4);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.03f);
}
TEST_CASE("voxel.render.packedTex0_samplesFirstAtlasTile") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
// Packed tex 0 is a valid atlas index even though voxel air is also 0.
Texture *atlas = makeTileAtlas(gfx, 4, 4);
uint32_t packed = PackedRect::pack(0, 0, 0, 4, 4, 0).bits; // tile 0 = red
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(2.f, 10.f, 2.f);
const glm::mat4 view =
glm::lookAtRH(eye, glm::vec3(2.f, 0.f, 2.f), glm::vec3(0, 0, -1));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "posY", atlas, 4);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.r > mid.g);
CHECK(mid.r > mid.b);
CHECK(mid.r > 0.08f);
}
TEST_CASE("voxel.render.atlasYellowThenMagentaSwap") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 300, 220);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeTileAtlas(gfx, 4, 4);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
fillCube(world.get(), 0, 0, 0, 5, 3); // yellow
world->remeshDirty();
const glm::vec3 eye(2.5f, 2.5f, 16.f);
const glm::vec3 target(2.5f, 2.5f, 2.5f);
renderVoxelFrame(gfx, world.get(), atlas, 4, eye, target, 100.f, true);
Color yellow = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(yellow.r > yellow.b);
CHECK(yellow.g > yellow.b);
for (int z = 0; z < 5; ++z)
for (int y = 0; y < 5; ++y)
for (int x = 0; x < 5; ++x) world->setVoxel(x, y, z, 4); // magenta
world->remeshDirty();
renderVoxelFrame(gfx, world.get(), atlas, 4, eye, target, 100.f, true);
Color magenta = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(magenta.r > magenta.g);
CHECK(magenta.b > magenta.g);
}
TEST_CASE("voxel.render.plusY_alias") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t packed = PackedRect::pack(0, 0, 0, 4, 4, 1).bits;
Texture *atlas = makeSolid(gfx, 50, 210, 90);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(2.f, 12.f, 2.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(2.f, 0.f, 2.f), glm::vec3(0, 0, -1));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "+y", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.g > 0.08f);
}
TEST_CASE("voxel.render.plusZ_alias") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t packed = PackedRect::pack(0, 0, 0, 4, 4, 1).bits;
Texture *atlas = makeSolid(gfx, 40, 100, 230);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(2.f, 2.f, 12.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(2.f, 2.f, 1.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.05f, 0.07f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "+z", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.b > 0.08f);
}
TEST_CASE("voxel.render.wide32x1_rect") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 200);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t packed = PackedRect::pack(0, 0, 0, 32, 1, 1).bits;
Texture *atlas = makeSolid(gfx, 230, 180, 40);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(16.f, 10.f, 16.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(16.f, 0.f, 0.5f), glm::vec3(0, 0, -1));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "posY", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
TEST_CASE("voxel.render.tall1x32_side") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 280, 220);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t packed = PackedRect::pack(0, 0, 0, 1, 32, 1).bits; // ±X: w along Z, h along Y
Texture *atlas = makeSolid(gfx, 200, 60, 200);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(20.f, 16.f, 0.5f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(1.f, 16.f, 0.5f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.05f, 0.07f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "posX", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.negXWallFromLeft") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 300, 220);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 6; ++z)
for (int y = 0; y < 6; ++y) world->setVoxel(4, y, z, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 230, 100, 50);
const glm::vec3 eye(-6.f, 3.f, 3.f);
const glm::vec3 target(4.f, 3.f, 3.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 100.f, true);
bool sawNegX = false;
for (int i = 0; i < world->getVisibleBatchCount(); ++i)
if (world->getVisibleBatch(i).dir == FaceDir::NegX) sawNegX = true;
CHECK(sawNegX);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
TEST_CASE("voxel.render.frustumCulledChunkDark") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 280, 200);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
// Chunk far to the side — outside typical forward frustum when looking +Z.
fillCube(world.get(), 200, 0, 200, 4, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 255, 255, 255);
const glm::vec3 eye(0.f, 2.f, 0.f);
const glm::vec3 target(0.f, 2.f, 10.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 500.f, true);
CHECK_EQ(world->getVisibleChunkCount(), 0);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) < 0.22f);
}
TEST_CASE("voxel.render.carveHoleDarkensCenter") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
fillCube(world.get(), 0, 0, 0, 8, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 230, 230, 240);
const glm::vec3 eye(4.f, 4.f, 20.f);
const glm::vec3 target(4.f, 4.f, 4.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 100.f, true);
Color solid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(solid) > 0.08f);
// Carve a tunnel through the center along Z so the mid pixel looks through.
for (int z = 0; z < 8; ++z)
for (int y = 3; y <= 4; ++y)
for (int x = 3; x <= 4; ++x) world->setVoxel(x, y, z, 0);
world->remeshDirty();
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, target, 100.f, true);
Color carved = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(carved) < luma(solid));
}
TEST_CASE("voxel.render.twoChunksDifferentAtlasTiles") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeTileAtlas(gfx, 4, 4);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
fillCube(world.get(), 0, 0, 0, 4, 1); // green
fillCube(world.get(), 32, 0, 0, 4, 2); // blue, next chunk
world->remeshDirty();
CHECK_EQ(world->getChunkCount(), 2);
// Look at green chunk.
renderVoxelFrame(gfx, world.get(), atlas, 4, glm::vec3(2.f, 2.f, 14.f),
glm::vec3(2.f, 2.f, 2.f), 100.f, true);
Color green = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(green.g > green.b);
// Look at blue chunk.
renderVoxelFrame(gfx, world.get(), atlas, 4, glm::vec3(34.f, 2.f, 14.f),
glm::vec3(34.f, 2.f, 2.f), 100.f, true);
Color blue = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(blue.b > blue.g);
}
TEST_CASE("voxel.render.atlas32wide_tex31") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 300, 220);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeTileAtlas(gfx, 32, 2); // 64 tiles
uint32_t packed = PackedRect::pack(0, 0, 0, 3, 3, 31).bits;
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(1.5f, 8.f, 1.5f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(1.5f, 0.f, 1.5f), glm::vec3(0, 0, -1));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "posY", atlas, 32);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.minusX_alias") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t packed = PackedRect::pack(5, 0, 0, 4, 4, 1).bits;
Texture *atlas = makeSolid(gfx, 220, 70, 40);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(-4.f, 2.f, 2.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(5.f, 2.f, 2.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.05f, 0.07f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "-x", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.r > 0.08f);
}
TEST_CASE("voxel.render.nearPlaneOccludesFarDifferentColor") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *red = makeSolid(gfx, 240, 30, 30);
Texture *green = makeSolid(gfx, 30, 240, 30);
uint32_t nearR = PackedRect::pack(0, 0, 0, 6, 6, 1).bits;
uint32_t farG = PackedRect::pack(0, 0, 0, 6, 6, 1).bits;
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(3.f, 3.f, 20.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(3.f, 3.f, 0.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
// Far green at z≈1, near red at z≈8 — both posZ planes.
gfx->drawVoxelFaceInstances(&farG, 1, 0.f, 0.f, 0.f, "posZ", green, 1);
gfx->drawVoxelFaceInstances(&nearR, 1, 0.f, 0.f, 8.f, "posZ", red, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.r > mid.g);
CHECK(mid.r > 0.08f);
}
TEST_CASE("voxel.render.minusY_alias") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 240, 180);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
uint32_t packed = PackedRect::pack(0, 4, 0, 4, 4, 1).bits;
Texture *atlas = makeSolid(gfx, 180, 90, 40);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(2.f, -6.f, 2.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(2.f, 4.f, 2.f), glm::vec3(0, 0, -1));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "-y", atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.nullPackedPointerNoCrash") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 200, 150);
tinyHud(gfx);
Texture *atlas = makeSolid(gfx, 255, 255, 255);
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::mat4 vp = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f) *
glm::lookAtRH(glm::vec3(0, 0, 5), glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(nullptr, 10, 0.f, 0.f, 0.f, "posY", atlas, 1);
gfx->drawVoxelFaceInstances(nullptr, 0, 0.f, 0.f, 0.f, "posY", atlas, 1);
}
RenderSystem::render(*gfx);
}
TEST_CASE("voxel.render.drawVisibleNullAtlasWhite") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 280, 200);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
fillCube(world.get(), 0, 0, 0, 4, 1);
world->remeshDirty();
const glm::vec3 eye(2.f, 2.f, 12.f);
renderVoxelFrame(gfx, world.get(), nullptr, 1, eye, glm::vec3(2.f, 2.f, 2.f), 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
TEST_CASE("voxel.render.multiFrameAtlasSwapStable") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 300, 220);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlasA = makeTileAtlas(gfx, 4, 4);
Texture *atlasB = makeSolid(gfx, 40, 40, 230);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
fillCube(world.get(), 0, 0, 0, 4, 1);
world->remeshDirty();
const glm::vec3 eye(2.f, 2.f, 14.f);
const glm::vec3 target(2.f, 2.f, 2.f);
for (int i = 0; i < 4; ++i) {
renderVoxelFrame(gfx, world.get(), (i % 2) ? atlasB : atlasA, (i % 2) ? 1 : 4, eye, target,
100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
}
TEST_CASE("voxel.render.sparsePillarsMultiTex") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *atlas = makeTileAtlas(gfx, 4, 4);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 12; z += 3)
for (int x = 0; x < 12; x += 3)
for (int y = 0; y < 3; ++y) world->setVoxel(x, y, z, uint8_t(1 + ((x + z) % 4)));
world->remeshDirty();
CHECK(world->getChunk(0, 0, 0)->totalRectCount() > 10);
const glm::vec3 eye(6.f, 8.f, 22.f);
renderVoxelFrame(gfx, world.get(), atlas, 4, eye, glm::vec3(6.f, 1.f, 6.f), 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.03f);
}
TEST_CASE("voxel.render.fullLayerTopFromAbove") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
for (int z = 0; z < 32; ++z)
for (int x = 0; x < 32; ++x) world->setVoxel(x, 0, z, 1);
world->remeshDirty();
CHECK_EQ(world->getChunk(0, 0, 0)->faceRectCount(FaceDir::PosY), 1);
Texture *atlas = makeSolid(gfx, 200, 160, 80);
const glm::vec3 eye(16.f, 40.f, 16.f);
const glm::mat4 view =
glm::lookAtRH(eye, glm::vec3(16.f, 0.f, 16.f), glm::vec3(0, 0, -1));
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 200.f);
const glm::mat4 vp = proj * view;
world->selectVisible(&vp[0][0], eye.x, eye.y, eye.z, 200.f, true);
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
world->drawVisible(gfx, atlas, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
}
TEST_CASE("voxel.render.twoFacesStackedDepth") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 300, 220);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
Texture *nearTex = makeSolid(gfx, 40, 200, 60);
Texture *farTex = makeSolid(gfx, 200, 40, 40);
uint32_t a = PackedRect::pack(0, 0, 0, 5, 5, 1).bits;
uint32_t b = PackedRect::pack(0, 0, 0, 5, 5, 1).bits;
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(2.5f, 2.5f, 18.f);
const glm::mat4 view = glm::lookAtRH(eye, glm::vec3(2.5f, 2.5f, 0.f), glm::vec3(0, 1, 0));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&b, 1, 0.f, 0.f, 0.f, "posZ", farTex, 1);
gfx->drawVoxelFaceInstances(&a, 1, 0.f, 0.f, 6.f, "posZ", nearTex, 1);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(mid.g > mid.r);
}
TEST_CASE("voxel.render.chunkOriginNegativeDraw") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 300, 220);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
// Local (0,0,0) in chunk (-1,0,0) → world x in [-32,-1]
world->setVoxel(-16, 0, 0, 1);
world->setVoxel(-15, 0, 0, 1);
world->setVoxel(-16, 1, 0, 1);
world->setVoxel(-15, 1, 0, 1);
world->remeshDirty();
Texture *atlas = makeSolid(gfx, 220, 180, 60);
const glm::vec3 eye(-15.5f, 1.f, 12.f);
renderVoxelFrame(gfx, world.get(), atlas, 1, eye, glm::vec3(-15.5f, 1.f, 0.f), 100.f, true);
CHECK(world->getVisibleChunkCount() >= 1);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}
TEST_CASE("voxel.render.tilesPerRowMismatchStillDraws") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 280, 200);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
// 4×4 atlas but claim tilesPerRow=8 — UVs still sample something opaque.
Texture *atlas = makeTileAtlas(gfx, 4, 4);
uint32_t packed = PackedRect::pack(0, 0, 0, 3, 3, 1).bits;
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::vec3 eye(1.5f, 8.f, 1.5f);
const glm::mat4 view =
glm::lookAtRH(eye, glm::vec3(1.5f, 0.f, 1.5f), glm::vec3(0, 0, -1));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(50.f), aspect, 0.1f, 100.f);
const glm::mat4 vp = proj * view;
gfx->setBackgroundColor(Color(0.05f, 0.06f, 0.08f, 1.f));
gfx->begin3DFrame();
if (gfx->had3DThisFrame()) {
gfx->setMesh3DViewProj(vp);
gfx->drawVoxelFaceInstances(&packed, 1, 0.f, 0.f, 0.f, "posY", atlas, 8);
}
RenderSystem::render(*gfx);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.03f);
}
TEST_CASE("voxel.render.remeshIncreasesThenDecreasesRects") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 300, 220);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
world->setVoxel(2, 2, 2, 1);
world->remeshDirty();
const int one = world->getChunk(0, 0, 0)->totalRectCount();
CHECK_EQ(one, 6);
fillCube(world.get(), 0, 0, 0, 5, 1);
world->remeshDirty();
const int many = world->getChunk(0, 0, 0)->totalRectCount();
CHECK(many < one * 5); // greedy merges
CHECK(many >= 6);
Texture *atlas = makeSolid(gfx, 200, 200, 210);
renderVoxelFrame(gfx, world.get(), atlas, 1, glm::vec3(2.5f, 2.5f, 14.f),
glm::vec3(2.5f, 2.5f, 2.5f), 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.05f);
world->clear();
world->setVoxel(2, 2, 2, 1);
world->remeshDirty();
CHECK_EQ(world->getChunk(0, 0, 0)->totalRectCount(), 6);
}
// ---------------------------------------------------------------------------
// Large visual scenes (PNG under test/out/voxel_scenes/)
// ---------------------------------------------------------------------------
static uint32_t voxelHash(uint32_t x) {
x ^= x >> 16;
x *= 0x7feb352du;
x ^= x >> 15;
x *= 0x846ca68bu;
x ^= x >> 16;
return x;
}
static float voxelH21(int x, int z) {
return float(voxelHash(uint32_t(x) * 0x9E3779B9u ^ uint32_t(z) * 0x85EBCA6Bu) & 0xFFFFu) /
65535.f;
}
static float voxelValueNoise(float x, float z) {
const int x0 = int(std::floor(x));
const int z0 = int(std::floor(z));
const float tx = x - float(x0);
const float tz = z - float(z0);
const float sx = tx * tx * (3.f - 2.f * tx);
const float sz = tz * tz * (3.f - 2.f * tz);
const float a = voxelH21(x0, z0);
const float b = voxelH21(x0 + 1, z0);
const float c = voxelH21(x0, z0 + 1);
const float d = voxelH21(x0 + 1, z0 + 1);
return (a * (1.f - sx) + b * sx) * (1.f - sz) + (c * (1.f - sx) + d * sx) * sz;
}
static float voxelFbm(float x, float z) {
float sum = 0.f, amp = 1.f, freq = 1.f, norm = 0.f;
for (int i = 0; i < 4; ++i) {
sum += amp * voxelValueNoise(x * freq, z * freq);
norm += amp;
amp *= 0.5f;
freq *= 2.03f;
}
return sum / std::max(norm, 1e-4f);
}
static Texture *makeEarthAtlas(Graphics *gfx) {
static const uint8_t pal[16][3] = {
{20, 22, 28}, {86, 158, 62}, {145, 96, 55}, {128, 128, 132},
{210, 186, 120}, {48, 96, 168}, {118, 78, 42}, {46, 118, 48},
{236, 240, 245}, {96, 96, 100}, {168, 52, 42}, {210, 196, 168},
{72, 48, 32}, {148, 72, 56}, {212, 168, 64}, {64, 68, 76},
};
std::vector<uint8_t> px(16u * 4u);
for (int i = 0; i < 16; ++i) {
px[size_t(i) * 4u + 0] = pal[i][0];
px[size_t(i) * 4u + 1] = pal[i][1];
px[size_t(i) * 4u + 2] = pal[i][2];
px[size_t(i) * 4u + 3] = 255;
}
return gfx->newTexture(4, 4, px.data());
}
static void fillBox(VoxelWorld *w, int x0, int y0, int z0, int x1, int y1, int z1, uint8_t tex) {
for (int z = z0; z < z1; ++z)
for (int y = y0; y < y1; ++y)
for (int x = x0; x < x1; ++x) w->setVoxel(x, y, z, tex);
}
static void fillHollowBox(VoxelWorld *w, int x0, int y0, int z0, int x1, int y1, int z1, uint8_t tex) {
fillBox(w, x0, y0, z0, x1, y1, z1, tex);
if (x1 - x0 > 2 && y1 - y0 > 1 && z1 - z0 > 2)
fillBox(w, x0 + 1, y0, z0 + 1, x1 - 1, y1, z1 - 1, 0);
}
static void placeTree(VoxelWorld *w, int x, int groundY, int z) {
const int h = 3 + int(voxelH21(x, z) * 3.f);
for (int y = 1; y <= h; ++y) w->setVoxel(x, groundY + y, z, 6);
const int cy = groundY + h;
for (int dz = -2; dz <= 2; ++dz) {
for (int dx = -2; dx <= 2; ++dx) {
for (int dy = 0; dy <= 2; ++dy) {
if (dx * dx + dy * dy + dz * dz > 6) continue;
w->setVoxel(x + dx, cy + dy, z + dz, 7);
}
}
}
}
static void placeHouse(VoxelWorld *w, int x, int z, int wdt, int dpt, int hgt, uint8_t wall,
uint8_t roof) {
fillHollowBox(w, x, 1, z, x + wdt, 1 + hgt, z + dpt, wall);
fillBox(w, x, 1 + hgt, z, x + wdt, 1 + hgt + 1, z + dpt, roof);
const int ridgeZ = z + dpt / 2;
for (int i = 0; i < wdt; ++i) w->setVoxel(x + i, 2 + hgt, ridgeZ, roof);
}
static std::string voxelSceneOutDir() {
return std::string(EVENGINE_TEST_BINARY_DIR) + "/out/voxel_scenes";
}
static void renderVoxelScene(Graphics *gfx, VoxelWorld *world, Texture *atlas, const glm::vec3 &eye,
const glm::vec3 &target, float farZ, float viewRange) {
const float aspect =
float(std::max(1, gfx->getPixelWidth())) / float(std::max(1, gfx->getPixelHeight()));
const glm::mat4 view = glm::lookAtRH(eye, target, glm::vec3(0.f, 1.f, 0.f));
const glm::mat4 proj = perspectiveVulkanRH_ZO(glm::radians(48.f), aspect, 0.2f, farZ);
const glm::mat4 vp = proj * view;
world->selectVisible(&vp[0][0], eye.x, eye.y, eye.z, viewRange, true);
const Color sky(0.42f, 0.58f, 0.78f, 1.f);
gfx->setBackgroundColor(sky);
gfx->clear(sky, std::nullopt, std::nullopt);
gfx->begin3DFrame();
if (!gfx->had3DThisFrame()) return;
gfx->setMesh3DViewProj(vp);
gfx->setMesh3DCameraPos(eye);
world->drawVisible(gfx, atlas, 4);
RenderSystem::render(*gfx);
}
static void saveVoxelScenePng(Graphics *gfx, const std::string &name) {
std::unique_ptr<ImageData> img(gfx->newImageData());
REQUIRE(img.get() != nullptr);
const std::string dir = voxelSceneOutDir();
std::error_code ec;
std::filesystem::create_directories(dir, ec);
const std::string path = dir + "/" + name + ".png";
REQUIRE(saveImagePng(*img, path));
std::printf("voxel scene: %s\n", path.c_str());
}
TEST_CASE("voxel.render.sceneHillsIsland") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 960, 540);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
constexpr int kN = 160;
constexpr int kSea = 6;
for (int z = 0; z < kN; ++z) {
for (int x = 0; x < kN; ++x) {
const float n = voxelFbm(float(x) * 0.035f, float(z) * 0.035f);
const float ridge =
std::fabs(voxelFbm(float(x) * 0.018f + 40.f, float(z) * 0.018f) * 2.f - 1.f);
const float h01 = n * 0.62f + (1.f - ridge) * 0.38f;
const float cx = (float(x) - 80.f) / 88.f;
const float cz = (float(z) - 80.f) / 88.f;
float island = std::clamp(1.12f - std::sqrt(cx * cx + cz * cz), 0.f, 1.f);
island *= island;
int h = int(2.f + h01 * 20.f * island);
if (h < 1) h = 1;
if (h > 24) h = 24;
for (int y = 0; y <= h; ++y) {
uint8_t tex = 3;
if (y == h) {
if (h <= kSea + 1)
tex = 4;
else if (h >= 18)
tex = 8;
else
tex = 1;
} else if (y >= h - 2) {
tex = 2;
}
world->setVoxel(x, y, z, tex);
}
if (h < kSea) {
for (int y = h + 1; y <= kSea; ++y) world->setVoxel(x, y, z, 5);
}
}
}
for (int z = 8; z < kN - 8; z += 11) {
for (int x = 8; x < kN - 8; x += 13) {
if (voxelH21(x * 3, z * 5) < 0.78f) continue;
uint8_t top = 0;
int gy = 24;
while (gy > 0 && (top = world->getVoxel(x, gy, z)) == 0) --gy;
if (top != 1) continue;
placeTree(world.get(), x, gy, z);
}
}
const int remeshed = world->remeshDirty();
CHECK(remeshed >= 20);
CHECK(world->getChunkCount() >= 20);
Texture *atlas = makeEarthAtlas(gfx);
REQUIRE(atlas != nullptr);
const glm::vec3 eye(78.f, 52.f, 168.f);
const glm::vec3 target(80.f, 8.f, 78.f);
for (int i = 0; i < 2; ++i)
renderVoxelScene(gfx, world.get(), atlas, eye, target, 420.f, 360.f);
CHECK(world->getVisibleRectCount() > 80);
const Color sky(0.42f, 0.58f, 0.78f, 1.f);
CHECK(countNonBgSamples(gfx, sky) >= 20);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.06f);
saveVoxelScenePng(gfx, "hills_island");
}
TEST_CASE("voxel.render.sceneWalledTown") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 960, 540);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
std::unique_ptr<VoxelWorld> world(new VoxelWorld());
constexpr int kN = 96;
fillBox(world.get(), 0, 0, 0, kN, 1, kN, 2);
fillBox(world.get(), 2, 1, 2, kN - 2, 2, kN - 2, 9);
const int wallH = 8;
for (int i = 4; i < kN - 4; ++i) {
for (int y = 1; y <= wallH; ++y) {
world->setVoxel(i, y, 4, 3);
world->setVoxel(i, y, kN - 5, 3);
world->setVoxel(4, y, i, 3);
world->setVoxel(kN - 5, y, i, 3);
}
if ((i % 2) == 0) {
world->setVoxel(i, wallH + 1, 4, 3);
world->setVoxel(i, wallH + 1, kN - 5, 3);
world->setVoxel(4, wallH + 1, i, 3);
world->setVoxel(kN - 5, wallH + 1, i, 3);
}
}
// Gate on +Z wall
fillBox(world.get(), 44, 1, 4, 52, wallH + 2, 6, 0);
fillBox(world.get(), 43, 1, 3, 44, wallH + 2, 7, 12);
fillBox(world.get(), 52, 1, 3, 53, wallH + 2, 7, 12);
auto tower = [&](int x, int z) {
fillHollowBox(world.get(), x, 1, z, x + 7, 14, z + 7, 3);
fillBox(world.get(), x, 14, z, x + 7, 15, z + 7, 10);
fillBox(world.get(), x + 1, 15, z + 1, x + 6, 16, z + 6, 10);
fillBox(world.get(), x + 2, 16, z + 2, x + 5, 17, z + 5, 10);
world->setVoxel(x + 3, 17, z + 3, 14);
};
tower(2, 2);
tower(kN - 9, 2);
tower(2, kN - 9);
tower(kN - 9, kN - 9);
fillHollowBox(world.get(), 36, 1, 36, 60, 16, 60, 13);
fillBox(world.get(), 36, 16, 36, 60, 17, 60, 10);
fillBox(world.get(), 38, 17, 38, 58, 18, 58, 10);
fillBox(world.get(), 46, 18, 46, 50, 22, 50, 12);
world->setVoxel(48, 22, 48, 14);
fillBox(world.get(), 46, 1, 36, 50, 8, 37, 0);
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 4; ++col) {
const int x = 10 + col * 8;
const int z = 12 + row * 8;
if (x + 6 >= 34) continue;
placeHouse(world.get(), x, z, 6, 6, 4 + ((col + row) % 3), 11, 10);
}
}
for (int row = 0; row < 3; ++row) {
for (int col = 0; col < 3; ++col) {
placeHouse(world.get(), 64 + col * 8, 12 + row * 9, 7, 7, 3 + (row % 2), 11, 10);
}
}
fillBox(world.get(), 12, 1, 70, 30, 2, 88, 4);
for (int i = 0; i < 5; ++i) placeTree(world.get(), 16 + i * 3, 1, 74 + (i % 2) * 4);
const int remeshed = world->remeshDirty();
CHECK(remeshed >= 4);
CHECK(world->getChunkCount() >= 4);
Texture *atlas = makeEarthAtlas(gfx);
REQUIRE(atlas != nullptr);
const glm::vec3 eye(118.f, 68.f, 118.f);
const glm::vec3 target(48.f, 6.f, 48.f);
for (int i = 0; i < 2; ++i)
renderVoxelScene(gfx, world.get(), atlas, eye, target, 320.f, 280.f);
CHECK(world->getVisibleRectCount() > 40);
const Color sky(0.42f, 0.58f, 0.78f, 1.f);
CHECK(countNonBgSamples(gfx, sky) >= 20);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.06f);
saveVoxelScenePng(gfx, "walled_town");
}
TEST_CASE("voxel.render.registryMultiFaceTex") {
hideLeftover3D();
eve::window::Window *win = nullptr;
Graphics *gfx = nullptr;
openGfxWindow(win, gfx, 320, 240);
tinyHud(gfx);
gfx->setScreenReadbackEnabled(true);
// grass:顶面绿色 tile(1),其余面红色 tile(0);方向性炉子:前脸蓝色 tile(2)
CubeTypeRegistry reg;
CubeType grass;
grass.name = "grass";
for (int i = 0; i < 6; ++i) grass.faceTex[i] = 0;
grass.faceTex[2] = 1; // 顶 = green
reg.add(grass);
CubeType furnace;
furnace.name = "furnace";
for (int i = 0; i < 6; ++i) furnace.faceTex[i] = 0;
furnace.faceTex[0] = 2; // +X = blue
furnace.directional = true;
reg.add(furnace);
VoxelWorld world(reg);
for (int z = 0; z < 3; ++z)
for (int y = 0; y < 3; ++y)
for (int x = 0; x < 3; ++x) world.setVoxelByName(x, y, z, "grass");
world.remeshDirty();
Chunk *c = world.getChunk(0, 0, 0);
REQUIRE(c != nullptr);
CHECK_EQ(c->faceRects(FaceDir::PosY)[0].tex(), 1); // 顶面解析为 green
CHECK_EQ(c->faceRects(FaceDir::PosX)[0].tex(), 0); // 侧面 red
// 4×4 图集:tile0=red, tile1=green, tile2=blue
Texture *atlas = makeTileAtlas(gfx, 4, 4);
const glm::vec3 eye(1.5f, 1.5f, 10.f);
const glm::vec3 target(1.5f, 1.5f, 1.5f);
renderVoxelFrame(gfx, &world, atlas, 4, eye, target, 100.f, true);
Color mid = gfx->getPixel(gfx->getWidth() / 2, gfx->getHeight() / 2);
CHECK(luma(mid) > 0.04f);
}