307 lines
13 KiB
C++
307 lines
13 KiB
C++
#include <limits>
|
|
#include <simplesquirrel/simplesquirrel.hpp>
|
|
#include "procgen/PointSet.h"
|
|
#include "procgen/heightmap/Heightmap.h"
|
|
#include "procgen/heightmap/TerrainDetailLayer.h"
|
|
#include "procgen/heightmap/TerrainDetailPlacement.h"
|
|
#include "procgen/heightmap/TerrainStampScript.h"
|
|
#include "zeroerr/unittest.h"
|
|
using namespace eve::procgen;
|
|
namespace {
|
|
int countAt(const TerrainDetailLayer& layer, int x, int z = 0) {
|
|
auto count = layer.sample(x, z);
|
|
REQUIRE(count.ok());
|
|
return count.value();
|
|
}
|
|
} // namespace
|
|
TEST_CASE("procgen.detail.modesThresholdAndNearestEven") {
|
|
Heightmap fitness(5, 1);
|
|
fitness.data() = {0, 0.5F, 0.625F, 0.875F, 1};
|
|
TerrainDetailLayer layer;
|
|
TerrainDetailSettings settings;
|
|
settings.fadeStart = 0.5F;
|
|
settings.density = 2;
|
|
REQUIRE(layer.reset(5, 1, 7).ok());
|
|
REQUIRE(layer.apply(fitness, settings, 1).ok());
|
|
// Density 0.5 ties to zero; 1.5 ties to two. Equal threshold is excluded.
|
|
const int expected[] = {0, 0, 0, 2, 2};
|
|
for (int x = 0; x < 5; ++x) CHECK(countAt(layer, x) == expected[x]);
|
|
REQUIRE(layer.reset(5, 1, 7).ok());
|
|
settings.mode = TerrainDetailMode::Add;
|
|
REQUIRE(layer.apply(fitness, settings, 1).ok());
|
|
CHECK(countAt(layer, 0) == 7);
|
|
CHECK(countAt(layer, 1) == 7);
|
|
CHECK(countAt(layer, 2) == 2);
|
|
settings.mode = TerrainDetailMode::Remove;
|
|
REQUIRE(layer.apply(fitness, settings, 1).ok());
|
|
CHECK(countAt(layer, 0) == 7);
|
|
CHECK(countAt(layer, 1) == 7);
|
|
CHECK(countAt(layer, 2) == 2);
|
|
CHECK(countAt(layer, 3) == 0);
|
|
CHECK(countAt(layer, 4) == 0);
|
|
}
|
|
TEST_CASE("procgen.detail.sourceConditionalRngAndColumnOrder") {
|
|
TerrainDetailLayer layer;
|
|
TerrainDetailSettings settings;
|
|
settings.minimumFitness = 0;
|
|
settings.fadeStart = 1;
|
|
settings.density = 8;
|
|
Heightmap fitness(3, 4);
|
|
std::fill(fitness.data().begin(), fitness.data().end(), 0.5F);
|
|
REQUIRE(layer.reset(3, 4).ok());
|
|
REQUIRE(layer.apply(fitness, settings, 1).ok());
|
|
// X-major order; source seed-one draws accept sequence 0,1,4,9 at threshold 0.5.
|
|
const int expected[] = {4, 4, 0, 4, 0, 4, 0, 0, 0, 0, 0, 0};
|
|
for (int z = 0; z < 4; ++z)
|
|
for (int x = 0; x < 3; ++x) CHECK(countAt(layer, x, z) == expected[z * 3 + x]);
|
|
REQUIRE(layer.apply(fitness, settings, 0).ok());
|
|
for (int z = 0; z < 4; ++z)
|
|
for (int x = 0; x < 3; ++x) CHECK(countAt(layer, x, z) == expected[z * 3 + x]);
|
|
// Neither below-threshold nor full-fitness cells consume a thinning draw.
|
|
Heightmap sparse(4, 1);
|
|
sparse.data() = {0, 1, 0.5F, 0.5F};
|
|
REQUIRE(layer.reset(4, 1).ok());
|
|
REQUIRE(layer.apply(sparse, settings, 1).ok());
|
|
CHECK(countAt(layer, 0) == 0);
|
|
CHECK(countAt(layer, 1) == 8);
|
|
CHECK(countAt(layer, 2) == 4);
|
|
CHECK(countAt(layer, 3) == 4);
|
|
}
|
|
TEST_CASE("procgen.detail.failedOperationAndMovePreserveCounts") {
|
|
TerrainDetailLayer layer;
|
|
TerrainDetailSettings settings;
|
|
Heightmap fitness(2, 1);
|
|
fitness.data() = {1, std::numeric_limits<float>::infinity()};
|
|
REQUIRE(layer.reset(2, 1, 3).ok());
|
|
CHECK(!layer.apply(fitness, settings, 1).ok());
|
|
CHECK(countAt(layer, 0) == 3);
|
|
CHECK(countAt(layer, 1) == 3);
|
|
fitness.data()[1] = 1;
|
|
settings.density = float(std::numeric_limits<int>::max());
|
|
CHECK(!layer.apply(fitness, settings, 1).ok());
|
|
CHECK(countAt(layer, 0) == 3);
|
|
CHECK(!layer.reset(std::numeric_limits<int>::max(), 2, 0).ok());
|
|
settings.density = 16;
|
|
settings.mode = static_cast<TerrainDetailMode>(99);
|
|
CHECK(!layer.apply(fitness, settings, 1).ok());
|
|
TerrainDetailLayer moved = std::move(layer);
|
|
CHECK(layer.getWidth() == 0);
|
|
CHECK(countAt(moved, 0) == 3);
|
|
CHECK(!layer.sample(0, 0).ok());
|
|
REQUIRE(layer.reset(1, 1).ok());
|
|
}
|
|
TEST_CASE("procgen.detail.scriptOwnedLayer") {
|
|
Heightmap fitness(1, 1);
|
|
fitness.data() = {1};
|
|
ssq::VM vm(1024);
|
|
auto table = vm.addTable("eve");
|
|
exposeHeightmap(table);
|
|
vm.addFunc("fitness", [&]() { return &fitness; });
|
|
vm.run(vm.compileSource(R"(
|
|
local layer=eve.TerrainDetailLayer();assert(layer.reset(1,1,0).ok);
|
|
local settings=eve.TerrainDetailSettings();settings.density=9.0;
|
|
assert(layer.apply(fitness(),settings,0,17).ok);
|
|
assert(layer.sample(0,0).value==9);
|
|
assert(layer.getWidth()==1 && layer.getHeight()==1);
|
|
assert(layer.apply(fitness(),settings,2,17).ok);
|
|
assert(layer.sample(0,0).value==0);
|
|
assert(!layer.apply(fitness(),settings,99,17).ok);
|
|
)"));
|
|
}
|
|
|
|
TEST_CASE("procgen.detail.resourceLayersClearIndependently") {
|
|
TerrainDetailLayer layer;
|
|
Heightmap fitness(2, 1);
|
|
fitness.data() = {1, 1};
|
|
REQUIRE(layer.reset(2, 1).ok());
|
|
TerrainDetailSettings first;
|
|
first.minimumFitness = first.fadeStart = 0;
|
|
first.density = 3;
|
|
first.namespaceId = 101;
|
|
REQUIRE(layer.apply(fitness, first, 7).ok());
|
|
auto second = first;
|
|
second.density = 5;
|
|
second.namespaceId = 202;
|
|
REQUIRE(layer.apply(fitness, second, 9).ok());
|
|
CHECK(layer.sample(0, 0).value() == 8);
|
|
CHECK(layer.sampleResource(101, 0, 0).value() == 3);
|
|
CHECK(layer.sampleResource(202, 0, 0).value() == 5);
|
|
Heightmap heights(2, 2);
|
|
TerrainDetailPlacementSettings placement;
|
|
placement.asset = "flowers";
|
|
placement.maxPoints = 20;
|
|
placement.densityNamespaceId = 202;
|
|
placement.namespaceId = 303;
|
|
PointSet selected;
|
|
REQUIRE(exportTerrainDetailPoints(selected, layer, heights, placement).ok());
|
|
CHECK(selected.getCount() == 10);
|
|
for (int i = 0; i < selected.getCount(); ++i)
|
|
CHECK(selected.getStringAttribute(i, "spawnNamespace", "") == "303");
|
|
REQUIRE(layer.clearResource(101).ok());
|
|
CHECK(layer.sample(0, 0).value() == 5);
|
|
CHECK(layer.sampleResource(101, 0, 0).value() == 0);
|
|
|
|
ssq::VM vm(1024);
|
|
auto table = vm.addTable("eve");
|
|
exposeHeightmap(table);
|
|
vm.addFunc("layer", [&]() { return &layer; });
|
|
vm.run(vm.compileSource(R"(
|
|
assert(layer().sampleResource(202,0,0).value==5);
|
|
assert(layer().clearResource(202).value==2);
|
|
assert(layer().sample(0,0).value==0);
|
|
)"));
|
|
}
|
|
|
|
TEST_CASE("procgen.detail.pointsResourceProjectionAndStableIdentity") {
|
|
TerrainDetailLayer layer;
|
|
REQUIRE(layer.reset(2, 1, 2).ok());
|
|
Heightmap heights(2, 2);
|
|
heights.data() = {0, 2, 4, 6};
|
|
TerrainDetailPlacementSettings settings;
|
|
settings.asset = "grass/test";
|
|
settings.width = 2;
|
|
settings.depth = 2;
|
|
settings.heightScale = 3;
|
|
PointSet points;
|
|
REQUIRE(exportTerrainDetailPoints(points, layer, heights, settings).ok());
|
|
REQUIRE(points.getCount() == 4);
|
|
for (int i = 0; i < 4; ++i) {
|
|
const auto& p = points.points()[i];
|
|
CHECK(p.id != 0);
|
|
CHECK(p.x >= 0);
|
|
CHECK(p.x < 2);
|
|
CHECK(p.z >= 0);
|
|
CHECK(p.z < 2);
|
|
CHECK(std::abs(p.y - (p.x + 2 * p.z) * 3) < 0.00001F);
|
|
CHECK(points.getStringAttribute(i, "asset", "") == "grass/test");
|
|
CHECK(points.getIntAttribute(i, "detailCell", -1) == i / 2);
|
|
CHECK(points.getIntAttribute(i, "detailOrdinal", -1) == i % 2);
|
|
}
|
|
Heightmap fitness(2, 1);
|
|
fitness.data() = {1, 0};
|
|
TerrainDetailSettings density;
|
|
density.mode = TerrainDetailMode::Add;
|
|
density.density = 3;
|
|
REQUIRE(layer.apply(fitness, density, 1).ok());
|
|
PointSet changed;
|
|
REQUIRE(exportTerrainDetailPoints(changed, layer, heights, settings).ok());
|
|
REQUIRE(changed.getCount() == 5);
|
|
CHECK(changed.points()[3].id == points.points()[2].id);
|
|
CHECK(changed.points()[3].x == points.points()[2].x);
|
|
CHECK(changed.points()[3].z == points.points()[2].z);
|
|
CHECK(changed.points()[3].yaw == points.points()[2].yaw);
|
|
settings.maxPoints = 4;
|
|
CHECK(!exportTerrainDetailPoints(changed, layer, heights, settings).ok());
|
|
CHECK(changed.getCount() == 5);
|
|
settings.maxPoints = 10;
|
|
heights.data()[3] = std::numeric_limits<float>::infinity();
|
|
CHECK(!exportTerrainDetailPoints(changed, layer, heights, settings).ok());
|
|
CHECK(changed.getCount() == 5);
|
|
}
|
|
TEST_CASE("procgen.detail.scriptPointExport") {
|
|
TerrainDetailLayer layer;
|
|
REQUIRE(layer.reset(1, 1, 2).ok());
|
|
Heightmap heights(1, 1);
|
|
heights.data() = {3};
|
|
PointSet output;
|
|
ssq::VM vm(1024);
|
|
auto table = vm.addTable("eve");
|
|
table.addClass("PointSet", ssq::Class::Ctor<PointSet()>());
|
|
exposeHeightmap(table);
|
|
vm.addFunc("layer", [&]() { return &layer; });
|
|
vm.addFunc("heights", [&]() { return &heights; });
|
|
vm.addFunc("output", [&]() { return &output; });
|
|
vm.run(vm.compileSource(R"(
|
|
local settings=eve.TerrainDetailPlacementSettings();settings.asset="grass/test";
|
|
settings.namespaceId=17;settings.seed=4;
|
|
settings.minimumWidth=0.5;settings.maximumWidth=0.5;settings.minimumHeight=2.0;settings.maximumHeight=2.0;
|
|
settings.healthyR=0.8;settings.dryR=0.2;settings.noiseSpread=0.0;settings.noiseSeed=-4;
|
|
assert(eve.exportTerrainDetailPoints(output(),layer(),heights(),settings).ok);
|
|
)"));
|
|
CHECK(output.getCount() == 2);
|
|
CHECK(output.getY(0) == 3);
|
|
CHECK(output.getScaleX(0) == 0.5F);
|
|
CHECK(output.getScaleY(0) == 2);
|
|
CHECK(std::abs(output.getColorR(0) - 0.5F) < 0.000001F);
|
|
}
|
|
|
|
TEST_CASE("procgen.detail.independentDimensionsPreservePlacement") {
|
|
TerrainDetailLayer layer;
|
|
REQUIRE(layer.reset(1, 1, 12).ok());
|
|
Heightmap heights(1, 1);
|
|
TerrainDetailPlacementSettings settings;
|
|
settings.asset = "grass/test";
|
|
PointSet before, after;
|
|
REQUIRE(exportTerrainDetailPoints(before, layer, heights, settings).ok());
|
|
settings.minimumWidth = 0.2F;
|
|
settings.maximumWidth = 0.4F;
|
|
settings.minimumHeight = 2;
|
|
settings.maximumHeight = 3;
|
|
REQUIRE(exportTerrainDetailPoints(after, layer, heights, settings).ok());
|
|
for (int i = 0; i < 12; ++i) {
|
|
CHECK(after.getPointId(i) == before.getPointId(i));
|
|
CHECK(after.getX(i) == before.getX(i));
|
|
CHECK(after.getZ(i) == before.getZ(i));
|
|
CHECK(after.getScaleX(i) >= 0.2F);
|
|
CHECK(after.getScaleX(i) <= 0.4F);
|
|
CHECK(after.getScaleZ(i) == after.getScaleX(i));
|
|
CHECK(after.getScaleY(i) >= 2);
|
|
CHECK(after.getScaleY(i) <= 3);
|
|
}
|
|
settings.maximumWidth = 0.1F;
|
|
CHECK(!exportTerrainDetailPoints(after, layer, heights, settings).ok());
|
|
CHECK(after.getCount() == 12);
|
|
settings.minimumWidth = std::numeric_limits<float>::max();
|
|
settings.maximumWidth = settings.minimumWidth;
|
|
settings.minimumScale = 2;
|
|
settings.maximumScale = 2;
|
|
CHECK(!exportTerrainDetailPoints(after, layer, heights, settings).ok());
|
|
CHECK(after.getScaleX(0) < 1);
|
|
}
|
|
|
|
TEST_CASE("procgen.detail.healthyDryColorNoiseIsStableAndAtomic") {
|
|
TerrainDetailLayer layer;
|
|
REQUIRE(layer.reset(2, 2, 3).ok());
|
|
Heightmap heights(1, 1);
|
|
TerrainDetailPlacementSettings settings;
|
|
settings.asset = "grass/color";
|
|
settings.healthyR = 0.8F;
|
|
settings.healthyG = 1.0F;
|
|
settings.healthyB = 0.4F;
|
|
settings.dryR = 0.2F;
|
|
settings.dryG = 0.3F;
|
|
settings.dryB = 0.1F;
|
|
settings.noiseSpread = 0.7F;
|
|
settings.noiseSeed = -17;
|
|
PointSet first, repeat;
|
|
REQUIRE(exportTerrainDetailPoints(first, layer, heights, settings).ok());
|
|
REQUIRE(exportTerrainDetailPoints(repeat, layer, heights, settings).ok());
|
|
REQUIRE(first.getCount() == 12);
|
|
bool varied = false;
|
|
for (int i = 0; i < first.getCount(); ++i) {
|
|
CHECK(first.getPointId(i) == repeat.getPointId(i));
|
|
CHECK(first.getColorR(i) == repeat.getColorR(i));
|
|
CHECK(first.getColorG(i) == repeat.getColorG(i));
|
|
CHECK(first.getColorB(i) == repeat.getColorB(i));
|
|
CHECK(first.getColorR(i) >= settings.dryR);
|
|
CHECK(first.getColorR(i) <= settings.healthyR);
|
|
if (i > 0 && first.getColorR(i) != first.getColorR(0)) varied = true;
|
|
}
|
|
CHECK(varied);
|
|
PointSet recolored;
|
|
settings.noiseSpread = 0;
|
|
REQUIRE(exportTerrainDetailPoints(recolored, layer, heights, settings).ok());
|
|
for (int i = 0; i < recolored.getCount(); ++i) {
|
|
CHECK(recolored.getPointId(i) == first.getPointId(i));
|
|
CHECK(recolored.getX(i) == first.getX(i));
|
|
CHECK(recolored.getZ(i) == first.getZ(i));
|
|
CHECK(std::abs(recolored.getColorR(i) - 0.5F) < 0.000001F);
|
|
CHECK(std::abs(recolored.getColorG(i) - 0.65F) < 0.000001F);
|
|
}
|
|
const auto before = recolored.getColorR(0);
|
|
settings.healthyR = 1.1F;
|
|
CHECK(!exportTerrainDetailPoints(recolored, layer, heights, settings).ok());
|
|
CHECK(recolored.getCount() == 12);
|
|
CHECK(recolored.getColorR(0) == before);
|
|
}
|