Files
EVEngine/test/procgen_pointset.cpp
T

351 lines
12 KiB
C++

#include "procgen/PointSet.h"
#include "procgen/Procgen.h"
#include "procgen/heightmap/Heightmap.h"
#include <zeroerr.hpp>
using namespace eve::procgen;
TEST_CASE("procgen.pointSet.composableDeterministicPipeline") {
Procgen proc;
const uint32_t forestSeed = proc.deriveSeed(42, "forest");
CHECK_NE(forestSeed, proc.deriveSeed(42, "rocks"));
CHECK_EQ(forestSeed, proc.deriveSeed(42, "forest"));
PointSet* source = proc.sampleGrid(8, 6, 2.f, forestSeed, 0.f);
REQUIRE(bool(source));
CHECK_EQ(source->getCount(), 48);
CHECK_EQ(source->getX(1), 2.f);
CHECK_EQ(source->getZ(8), 2.f);
for (int i = 0; i < source->getCount(); ++i) {
source->setPosition(i, source->getX(i), float(i % 4), source->getZ(i));
source->setDensity(i, float(i % 5) / 4.f);
source->setStringAttribute(i, "biome", "forest");
}
PointSet* height = proc.filterHeight(source, 1.f, 2.f);
REQUIRE(bool(height));
CHECK_EQ(height->getCount(), 24);
PointSet* dense = proc.filterDensity(height, 0.5f, 1.f);
REQUIRE(bool(dense));
CHECK(dense->getCount() < height->getCount());
CHECK(dense->getCount() > 0);
CHECK_EQ(dense->getStringAttribute(0, "biome", ""), std::string("forest"));
PointSet* excluded = proc.excludeRadius(dense, 0.f, 0.f, 3.f);
REQUIRE(bool(excluded));
PointSet* pruned = proc.selfPrune(excluded, 2.1f);
REQUIRE(bool(pruned));
CHECK(pruned->getCount() <= excluded->getCount());
PointSet* jitterA = proc.jitterPoints(pruned, proc.deriveSeed(forestSeed, "jitter"), 0.5f, 0.5f);
PointSet* jitterB = proc.jitterPoints(pruned, proc.deriveSeed(forestSeed, "jitter"), 0.5f, 0.5f);
REQUIRE(bool(jitterA));
REQUIRE(bool(jitterB));
CHECK_EQ(jitterA->getCount(), jitterB->getCount());
for (int i = 0; i < jitterA->getCount(); ++i) {
CHECK_EQ(jitterA->getX(i), jitterB->getX(i));
CHECK_EQ(jitterA->getZ(i), jitterB->getZ(i));
}
delete jitterB;
delete jitterA;
delete pruned;
delete excluded;
delete dense;
delete height;
delete source;
}
TEST_CASE("procgen.pointSet.invalidInputIsReported") {
Procgen proc;
CHECK(!proc.filterHeight(nullptr, 0.f, 1.f));
CHECK_EQ(proc.lastError(), std::string("filterHeight: null input"));
}
TEST_CASE("procgen.pointSet.spatialFiltersAndHeightmapProjection") {
Procgen proc;
PointSet source;
source.add(0.f, 0.f, 0.f);
source.add(1.f, 0.f, 1.f);
source.add(2.f, 0.f, 2.f);
PointSet* inside = proc.filterBox(&source, 0.5f, -1.f, 0.5f, 1.5f, 1.f, 1.5f);
REQUIRE(bool(inside));
CHECK_EQ(inside->getCount(), 1);
PointSet* outside = proc.excludeBox(&source, 0.5f, -1.f, 0.5f, 1.5f, 1.f, 1.5f);
REQUIRE(bool(outside));
CHECK_EQ(outside->getCount(), 2);
Heightmap heightmap(3, 3);
for (int z = 0; z < 3; ++z)
for (int x = 0; x < 3; ++x) heightmap.setHeight(x, z, float(x));
PointSet* projected = proc.projectToHeightmap(&source, &heightmap, 0.f, 0.f, 1.f, 2.f);
REQUIRE(bool(projected));
CHECK_EQ(projected->getY(0), 0.f);
CHECK_EQ(projected->getY(1), 2.f);
CHECK_EQ(projected->getY(2), 4.f);
CHECK(projected->getNormalX(1) < 0.f);
CHECK(projected->getNormalY(1) > 0.f);
PointSet* gentle = proc.filterSlope(projected, 0.f, 80.f);
REQUIRE(bool(gentle));
CHECK_EQ(gentle->getCount(), 3);
PointSet* flat = proc.filterSlope(projected, 0.f, 1.f);
REQUIRE(bool(flat));
CHECK_EQ(flat->getCount(), 0);
delete flat;
delete gentle;
delete projected;
delete outside;
delete inside;
}
TEST_CASE("procgen.pointSet.polygonAndSplineQueries") {
Procgen proc;
PointSet polygon;
polygon.add(0.f, 0.f, 0.f);
polygon.add(10.f, 0.f, 0.f);
polygon.add(10.f, 0.f, 10.f);
polygon.add(0.f, 0.f, 10.f);
PointSet candidates;
candidates.add(5.f, 0.f, 5.f);
candidates.add(15.f, 0.f, 5.f);
PointSet* inside = proc.filterPolygon(&candidates, &polygon);
PointSet* outside = proc.excludePolygon(&candidates, &polygon);
REQUIRE(bool(inside));
REQUIRE(bool(outside));
CHECK_EQ(inside->getCount(), 1);
CHECK_EQ(outside->getCount(), 1);
CHECK_EQ(inside->getX(0), 5.f);
CHECK_EQ(outside->getX(0), 15.f);
PointSet control;
control.add(0.f, 0.f, 0.f);
control.add(10.f, 0.f, 0.f);
control.add(10.f, 0.f, 10.f);
PointSet* samples = proc.sampleSpline(&control, 5.f, 42, 0.f);
REQUIRE(bool(samples));
CHECK_EQ(samples->getCount(), 5);
CHECK_EQ(samples->getX(1), 5.f);
CHECK_EQ(samples->getZ(3), 5.f);
CHECK_EQ(samples->getYaw(0), 0.f);
CHECK_EQ(samples->getYaw(3), 90.f);
PointSet nearby;
nearby.add(5.f, 0.f, 2.f);
nearby.add(5.f, 0.f, 5.f);
nearby.add(12.f, 0.f, 5.f);
PointSet* corridor = proc.filterSplineDistance(&nearby, &control, 0.f, 2.1f);
REQUIRE(bool(corridor));
CHECK_EQ(corridor->getCount(), 2);
delete corridor;
delete samples;
delete outside;
delete inside;
}
TEST_CASE("procgen.system.commitIsAtomicAndFailureKeepsPreviousSnapshot") {
Procgen proc;
PointSet first;
first.add(1.f, 2.f, 3.f);
ProcgenContext* initial = proc.beginSystem("forest", 42);
REQUIRE(bool(initial));
CHECK(initial->publish("trees", &first));
CHECK(initial->captureDebug("candidates", &first));
CHECK_EQ(initial->getDebugStageCount(), 1);
CHECK_EQ(initial->getDebugStageName(0), std::string("candidates"));
initial->trace("sample", 0, 1, 0.25f);
CHECK(proc.commitSystem(initial));
CHECK(proc.hasSystem("forest"));
CHECK_EQ(proc.getSystemRevision("forest"), uint64_t(1));
CHECK_EQ(proc.getSystemDebugStageCount("forest"), 1);
CHECK_EQ(proc.getSystemDebugStageName("forest", 0), std::string("candidates"));
PointSet* debug = proc.getSystemDebugStage("forest", "candidates");
REQUIRE(bool(debug));
CHECK_EQ(debug->getCount(), 1);
delete debug;
PointSet* committed = proc.getSystemOutput("forest", "trees");
REQUIRE(bool(committed));
CHECK_EQ(committed->getCount(), 1);
delete committed;
PointSet replacement;
replacement.add(9.f, 9.f, 9.f);
replacement.add(8.f, 8.f, 8.f);
ProcgenContext* failed = proc.beginSystem("forest", 99);
REQUIRE(bool(failed));
CHECK(failed->publish("trees", &replacement));
failed->fail("script error");
CHECK(!proc.commitSystem(failed));
CHECK_EQ(proc.getSystemRevision("forest"), uint64_t(1));
CHECK_EQ(proc.getSystemSeed("forest"), uint32_t(42));
CHECK_EQ(proc.getSystemDebugStageCount("forest"), 1);
committed = proc.getSystemOutput("forest", "trees");
REQUIRE(bool(committed));
CHECK_EQ(committed->getCount(), 1);
CHECK(proc.getSystemDebugReport("forest").find("sample") != std::string::npos);
delete committed;
delete failed;
delete initial;
}
TEST_CASE("procgen.system.cachedBuildReusesCommittedSnapshot") {
Procgen proc;
PointSet points;
points.add(1.f, 0.f, 2.f);
ProcgenContext* miss = proc.beginCachedSystem("rocks", 7, "layout-v1:size=32");
REQUIRE(bool(miss));
CHECK(!miss->isCacheHit());
CHECK(miss->isActive());
CHECK_EQ(miss->getBuildKey(), std::string("layout-v1:size=32"));
CHECK(miss->publish("rocks", &points));
CHECK(proc.commitSystem(miss));
CHECK_EQ(proc.getSystemRevision("rocks"), uint64_t(1));
CHECK_EQ(proc.getSystemBuildKey("rocks"), std::string("layout-v1:size=32"));
ProcgenContext* hit = proc.beginCachedSystem("rocks", 7, "layout-v1:size=32");
REQUIRE(bool(hit));
CHECK(hit->isCacheHit());
CHECK(!hit->isActive());
CHECK_EQ(proc.getSystemRevision("rocks"), uint64_t(1));
ProcgenContext* changed = proc.beginCachedSystem("rocks", 7, "layout-v1:size=64");
REQUIRE(bool(changed));
CHECK(!changed->isCacheHit());
CHECK(changed->isActive());
delete changed;
delete hit;
delete miss;
}
TEST_CASE("procgen.system.stageCacheIsTransactional") {
Procgen proc;
PointSet first;
first.add(3.f, 0.f, 4.f);
ProcgenContext* initial = proc.beginSystem("village", 11);
REQUIRE(bool(initial));
CHECK(initial->cacheStage("lots", "lots-v1:size=16", &first));
CHECK(initial->publish("lots", &first));
CHECK(proc.commitSystem(initial));
ProcgenContext* rebuild = proc.beginSystem("village", 11);
REQUIRE(bool(rebuild));
PointSet* reused = rebuild->reuseStage("lots", "lots-v1:size=16");
REQUIRE(bool(reused));
CHECK_EQ(reused->getCount(), 1);
CHECK_EQ(rebuild->getStageCacheHitCount(), 1);
CHECK(!rebuild->reuseStage("lots", "lots-v1:size=32"));
CHECK_EQ(rebuild->getStageCacheMissCount(), 1);
PointSet replacement;
replacement.add(8.f, 0.f, 8.f);
replacement.add(9.f, 0.f, 9.f);
CHECK(rebuild->cacheStage("lots", "lots-v1:size=32", &replacement));
rebuild->fail("downstream failed");
CHECK(!proc.commitSystem(rebuild));
ProcgenContext* retry = proc.beginSystem("village", 11);
REQUIRE(bool(retry));
PointSet* preserved = retry->reuseStage("lots", "lots-v1:size=16");
REQUIRE(bool(preserved));
CHECK_EQ(preserved->getCount(), 1);
CHECK(!retry->reuseStage("lots", "lots-v1:size=32"));
delete preserved;
delete retry;
delete reused;
delete rebuild;
delete initial;
}
TEST_CASE("procgen.system.automaticTraceTimingIsBalanced") {
Procgen proc;
PointSet points;
points.add(1.f, 0.f, 1.f);
ProcgenContext* measured = proc.beginSystem("timed", 5);
REQUIRE(bool(measured));
CHECK(measured->beginTrace("outer", 0));
CHECK(measured->beginTrace("inner", 1));
CHECK_EQ(measured->getOpenTraceCount(), 2);
CHECK(measured->endTrace(1));
CHECK(measured->endTrace(2));
CHECK_EQ(measured->getOpenTraceCount(), 0);
CHECK_EQ(measured->getTraceCount(), 2);
CHECK_EQ(measured->getTraceName(0), std::string("inner"));
CHECK(measured->getTraceMilliseconds(0) >= 0.f);
CHECK(measured->publish("points", &points));
CHECK(proc.commitSystem(measured));
ProcgenContext* unfinished = proc.beginSystem("timed", 5);
REQUIRE(bool(unfinished));
CHECK(unfinished->beginTrace("forgotten", 1));
CHECK(unfinished->publish("points", &points));
CHECK(!proc.commitSystem(unfinished));
CHECK(proc.lastError().find("unfinished trace 'forgotten'") != std::string::npos);
CHECK_EQ(proc.getSystemRevision("timed"), uint64_t(1));
ProcgenContext* unmatched = proc.beginSystem("unmatched", 5);
REQUIRE(bool(unmatched));
CHECK(!unmatched->endTrace(0));
CHECK_EQ(unmatched->getError(), std::string("endTrace: no stage timer is active"));
delete unmatched;
delete unfinished;
delete measured;
}
TEST_CASE("procgen.system.previousRevisionSupportsHotReloadDiffs") {
Procgen proc;
PointSet first;
first.add(1.f, 0.f, 1.f);
ProcgenContext* revision1 = proc.beginSystem("diff", 1);
REQUIRE(bool(revision1));
CHECK(revision1->captureDebug("trees", &first));
CHECK(proc.commitSystem(revision1));
CHECK_EQ(proc.getPreviousSystemRevision("diff"), uint64_t(0));
CHECK(!proc.getPreviousSystemDebugStage("diff", "trees"));
PointSet second = first;
second.add(2.f, 0.f, 2.f);
ProcgenContext* revision2 = proc.beginSystem("diff", 2);
REQUIRE(bool(revision2));
CHECK(revision2->captureDebug("trees", &second));
CHECK(revision2->captureDebug("roads", &first));
CHECK(proc.commitSystem(revision2));
CHECK_EQ(proc.getPreviousSystemRevision("diff"), uint64_t(1));
PointSet* previousTrees = proc.getPreviousSystemDebugStage("diff", "trees");
REQUIRE(bool(previousTrees));
CHECK_EQ(previousTrees->getCount(), 1);
const std::string report = proc.getSystemDebugDiffReport("diff");
CHECK(report.find("revision=1 -> 2") != std::string::npos);
CHECK(report.find("debug trees points=2 delta=+1") != std::string::npos);
CHECK(report.find("debug roads points=1 delta=+1") != std::string::npos);
ProcgenContext* failed = proc.beginSystem("diff", 3);
REQUIRE(bool(failed));
failed->fail("broken reload");
CHECK(!proc.commitSystem(failed));
CHECK_EQ(proc.getPreviousSystemRevision("diff"), uint64_t(1));
CHECK(proc.removeSystem("diff"));
CHECK_EQ(proc.getPreviousSystemRevision("diff"), uint64_t(0));
delete failed;
delete previousTrees;
delete revision2;
delete revision1;
}