142 lines
5.6 KiB
C++
142 lines
5.6 KiB
C++
#include <SDL2/SDL.h>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <memory>
|
|
#include <set>
|
|
#include <string>
|
|
#include "ManualPreview.h"
|
|
#include "graphics/Canvas.h"
|
|
#include "graphics/Graphics.h"
|
|
#include "image/ImageData.h"
|
|
#include "procgen/GeneratorRegistry.h"
|
|
#include "procgen/Semantic.h"
|
|
#include "window/Window.h"
|
|
#include "zeroerr/assert.h"
|
|
#include "zeroerr/unittest.h"
|
|
using namespace eve::procgen;
|
|
using namespace eve::graphics;
|
|
|
|
static Color colorForSemantic(int sem) {
|
|
switch (sem) {
|
|
case int(Semantic::Wall): return Color(0.22f, 0.24f, 0.30f, 1.f);
|
|
case int(Semantic::Floor): return Color(0.72f, 0.68f, 0.55f, 1.f);
|
|
case int(Semantic::Corridor): return Color(0.55f, 0.52f, 0.42f, 1.f);
|
|
case int(Semantic::Water): return Color(0.25f, 0.45f, 0.85f, 1.f);
|
|
case int(Semantic::Sand): return Color(0.85f, 0.78f, 0.45f, 1.f);
|
|
case int(Semantic::Grass): return Color(0.35f, 0.65f, 0.30f, 1.f);
|
|
case int(Semantic::Dirt): return Color(0.55f, 0.40f, 0.25f, 1.f);
|
|
case int(Semantic::Stone): return Color(0.55f, 0.58f, 0.62f, 1.f);
|
|
case int(Semantic::Snow): return Color(0.90f, 0.93f, 0.97f, 1.f);
|
|
case int(Semantic::Door): return Color(0.75f, 0.45f, 0.20f, 1.f);
|
|
default: return Color(0.05f, 0.05f, 0.07f, 1.f);
|
|
}
|
|
}
|
|
|
|
static void drawGrid(Graphics *gfx, const Grid2D &grid, float originX, float originY, float cell) {
|
|
const int w = grid.getWidth();
|
|
const int h = grid.getHeight();
|
|
for (int y = 0; y < h; ++y) {
|
|
for (int x = 0; x < w; ++x) {
|
|
const Color c = colorForSemantic(grid.getCell(x, y));
|
|
gfx->drawSolidRect(originX + float(x) * cell, originY + float(y) * cell, cell, cell, c);
|
|
}
|
|
}
|
|
// Markers for spawn / objects.
|
|
for (int i = 0; i < grid.getObjectCount(); ++i) {
|
|
const float ox = originX + grid.getObjectX(i) * cell;
|
|
const float oy = originY + grid.getObjectY(i) * cell;
|
|
gfx->drawSolidRect(ox - 2.f, oy - 2.f, 5.f, 5.f, Color(1.f, 0.3f, 0.3f, 1.f));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("procgen.render.dungeonCaveMazePreview") {
|
|
GeneratorRegistry::instance().registerBuiltins();
|
|
|
|
auto *win = eve::window::Window::create();
|
|
auto *gfx = Graphics::create();
|
|
REQUIRE(win != nullptr);
|
|
REQUIRE(gfx != nullptr);
|
|
eve::window::WindowSettings s;
|
|
s.width = 720;
|
|
s.height = 420;
|
|
s.centered = true;
|
|
REQUIRE(win->setWindowSettings(s));
|
|
|
|
struct Algo {
|
|
const char *id;
|
|
int w, h;
|
|
};
|
|
const Algo algos[] = {
|
|
{"dungeon.bsp", 36, 28},
|
|
{"cave.cellular", 36, 28},
|
|
{"maze.backtrack", 31, 23},
|
|
{"noise.terrain", 36, 28},
|
|
};
|
|
|
|
gfx->setBackgroundColorRGBA(0.06f, 0.07f, 0.09f, 1.f);
|
|
gfx->setScreenReadbackEnabled(true);
|
|
|
|
for (int ai = 0; ai < 4; ++ai) {
|
|
const Algo &algo = algos[ai];
|
|
Params p;
|
|
p.setSeed(42);
|
|
p.setSize(algo.w, algo.h);
|
|
if (std::string(algo.id) == "cave.cellular") {
|
|
p.setInt("loops", 4);
|
|
p.setFloat("fill", 0.45f);
|
|
} else if (std::string(algo.id) == "noise.terrain") {
|
|
p.setFloat("frequency", 5.f);
|
|
p.setInt("octaves", 3);
|
|
}
|
|
|
|
Grid2D grid;
|
|
std::string err;
|
|
REQUIRE(GeneratorRegistry::instance().generate(algo.id, p, grid, err));
|
|
|
|
const float cell = std::min(
|
|
16.f, std::min(float(gfx->getWidth() - 40) / float(algo.w), float(gfx->getHeight() - 40) / float(algo.h)));
|
|
const float originX = (float(gfx->getWidth()) - float(algo.w) * cell) * 0.5f;
|
|
const float originY = (float(gfx->getHeight()) - float(algo.h) * cell) * 0.5f;
|
|
|
|
for (int frame = 0; frame < (eve::test::manualPreviewEnabled() ? 45 : 1); ++frame) {
|
|
gfx->clearScreen();
|
|
drawGrid(gfx, grid, originX, originY, cell);
|
|
// Title bar strip so algorithm changes are readable without fonts.
|
|
const float barW = float(gfx->getWidth()) * (0.15f + 0.2f * float(ai));
|
|
gfx->drawSolidRect(12.f, 10.f, barW, 8.f, Color(0.9f, 0.85f, 0.4f, 1.f));
|
|
gfx->present();
|
|
|
|
|
|
SDL_Event e;
|
|
while (SDL_PollEvent(&e)) {
|
|
if (e.type == SDL_QUIT) break;
|
|
}
|
|
if (eve::test::manualPreviewEnabled()) SDL_Delay(16);
|
|
}
|
|
std::unique_ptr<eve::image::ImageData> image(gfx->newImageData());
|
|
REQUIRE(image != nullptr);
|
|
// ImageData uses framebuffer pixels; grid layout uses logical window units.
|
|
const float pixelScaleX = float(image->getWidth()) / float(gfx->getWidth());
|
|
const float pixelScaleY = float(image->getHeight()) / float(gfx->getHeight());
|
|
std::set<int> semantics;
|
|
int matchingCells = 0;
|
|
for (int y = 0; y < grid.getHeight(); ++y) {
|
|
for (int x = 0; x < grid.getWidth(); ++x) {
|
|
const int semantic = grid.getCell(x, y);
|
|
semantics.insert(semantic);
|
|
const auto expected = colorForSemantic(semantic);
|
|
const auto actual = image->getPixel(int((originX + (float(x) + 0.5f) * cell) * pixelScaleX),
|
|
int((originY + (float(y) + 0.5f) * cell) * pixelScaleY));
|
|
if (std::fabs(actual.r - expected.r) < 0.08f && std::fabs(actual.g - expected.g) < 0.08f &&
|
|
std::fabs(actual.b - expected.b) < 0.08f)
|
|
++matchingCells;
|
|
}
|
|
}
|
|
REQUIRE(semantics.size() >= 2);
|
|
// Object markers may cover a few cells; the grid must still be recognizable.
|
|
REQUIRE(matchingCells > grid.getWidth() * grid.getHeight() * 9 / 10);
|
|
}
|
|
|
|
win->close();
|
|
}
|