Files
EVEngine/test/terrain_world_workspace.cpp

227 lines
9.5 KiB
C++

#include "zeroerr/unittest.h"
#include <simplesquirrel/simplesquirrel.hpp>
#include "procgen/PointSet.h"
#include "procgen/heightmap/Heightmap.h"
#include "procgen/heightmap/TerrainDetailLayer.h"
#include "procgen/heightmap/TerrainObjectPlacement.h"
#include "procgen/heightmap/TerrainSplatmap.h"
#include "procgen/heightmap/TerrainStamp.h"
#include "procgen/heightmap/TerrainStampScript.h"
#include "procgen/heightmap/TerrainTreePlacement.h"
#include "procgen/heightmap/TerrainWorldWorkspace.h"
using namespace eve::procgen;
namespace {
Heightmap filled(int width, int height, float value) {
Heightmap result(width, height);
for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) result.setHeight(x, y, value);
return result;
}
TerrainWorldCreationSettings smallWorld() {
TerrainWorldCreationSettings s;
s.tilesX = 2; s.tilesZ = 1; s.tileSize = 2; s.tileHeight = 8;
s.heightmapResolution = 3; s.controlTextureResolution = 2; s.detailResolution = 2;
s.treeResolution = 2; s.objectResolution = 2; s.splatLayers = 2; s.nameSuffix = "stable";
return s;
}
TerrainStampSettings wholeWorld() {
TerrainStampSettings s; s.centerX = 0; s.centerZ = 0; s.width = 4; s.depth = 2; return s;
}
} // namespace
TEST_CASE("procgen.terrainWorld.createsCenteredPcgTopologyAtomically") {
TerrainWorldWorkspace world;
auto settings = smallWorld();
REQUIRE(world.create(settings).ok());
CHECK(world.getTileCount() == 2);
CHECK(world.getTileName(0).value() == "Terrain_0_0-stable");
CHECK(world.getTileName(1).value() == "Terrain_1_0-stable");
CHECK(world.getTileOriginX(0).value() == -2);
CHECK(world.getTileOriginX(1).value() == 0);
CHECK(world.getTileOriginZ(0).value() == -1);
Heightmap height;
REQUIRE(world.copyHeightmap(0, height).ok());
CHECK(height.getWidth() == 3);
CHECK(height.getHeight() == 3);
settings.heightmapResolution = 4;
CHECK(!world.create(settings).ok());
CHECK(world.getTileCount() == 2);
CHECK(world.getTileName(0).value() == "Terrain_0_0-stable");
settings = smallWorld();
settings.worldMap = true;
REQUIRE(world.create(settings).ok());
CHECK(world.getTileName(0).value() == "World Map_0_0-stable");
}
TEST_CASE("procgen.terrainWorld.ownsCrossDomainHistory") {
TerrainWorldWorkspace world;
REQUIRE(world.create(smallWorld()).ok());
auto operation = wholeWorld();
auto paint = filled(4, 2, 0.75F);
REQUIRE(world.paintSplat(paint, 1, operation).ok());
TerrainSplatmap splat;
REQUIRE(world.copySplatmap(0, splat).ok());
CHECK(splat.sample(1, 0, 0).value() == 0.75F);
TerrainObjectPlacementSettings objects;
objects.spacing = objects.spawnDensity = 1; objects.jitterPercent = 0;
objects.failureRate = objects.minimumFitness = objects.minimumInstanceFitness = 0;
objects.boundsRadius = 0.2F; objects.prototype = "rocks"; objects.namespaceId = 501; objects.maxPoints = 100;
TerrainObjectInstanceSettings child; child.asset = "rock-a"; REQUIRE(objects.addInstance(child).ok());
REQUIRE(world.applyObjects(paint, objects, operation, TerrainObjectOperationMode::Add).ok());
PointSet points;
REQUIRE(world.copyObjects(0, points).ok());
CHECK(!points.empty());
REQUIRE(world.undo().ok());
REQUIRE(world.copyObjects(0, points).ok());
CHECK(points.empty());
REQUIRE(world.copySplatmap(0, splat).ok());
CHECK(splat.sample(1, 0, 0).value() == 0.75F);
REQUIRE(world.undo().ok());
REQUIRE(world.copySplatmap(0, splat).ok());
CHECK(splat.sample(1, 0, 0).value() == 0);
REQUIRE(world.redo().ok()); REQUIRE(world.redo().ok());
CHECK(world.getOperationCount() == 2);
CHECK(world.getAppliedCount() == 2);
auto invalidPaint = filled(3, 3, 1);
CHECK(!world.paintSplat(invalidPaint, 1, operation).ok());
CHECK(world.getOperationCount() == 2);
CHECK(world.getAppliedCount() == 2);
REQUIRE(world.copyObjects(0, points).ok());
CHECK(!points.empty());
REQUIRE(world.copySplatmap(0, splat).ok());
CHECK(splat.sample(1, 0, 0).value() == 0.75F);
}
TEST_CASE("procgen.terrainWorld.executesCreationAndHistoryThroughRealVm") {
TerrainWorldWorkspace world;
Heightmap output;
ssq::VM vm(1024);
auto table = vm.addTable("eve");
exposeHeightmap(table);
vm.addFunc("world", [&]() { return &world; });
vm.addFunc("output", [&]() { return &output; });
vm.run(vm.compileSource(R"(
local s=eve.TerrainWorldCreationSettings();s.tilesX=2;s.tilesZ=1;s.tileSize=2.0;s.tileHeight=8.0;
s.heightmapResolution=3;s.controlTextureResolution=2;s.detailResolution=2;
s.treeResolution=2;s.objectResolution=2;s.splatLayers=2;s.nameSuffix="vm";
assert(world().create(s).value==2);
assert(world().getTileName(0).value=="Terrain_0_0-vm");
assert(world().getTileOriginX(0).value==-2.0 && world().getTileOriginX(1).value==0.0);
assert(world().copyHeightmap(0,output()).value==9);
assert(world().getTileCount()==2 && world().getOperationCount()==0);
)"));
}
TEST_CASE("procgen.terrainHeightAdjuster.setsWorldUnitsAtomicallyAndBinds") {
TerrainWorldWorkspace world;
REQUIRE(world.create(smallWorld()).ok());
auto adjusted = world.setHeightWorldUnits(250, 1000);
REQUIRE(adjusted.ok());
CHECK(adjusted.value() == 18);
Heightmap output;
REQUIRE(world.copyHeightmap(0, output).ok());
for (float value : output.data()) CHECK(value == 0.25F);
REQUIRE(world.setHeightWorldUnits(2000, 1000).ok());
REQUIRE(world.copyHeightmap(1, output).ok());
for (float value : output.data()) CHECK(value == 1);
REQUIRE(world.undo().ok());
REQUIRE(world.copyHeightmap(1, output).ok());
CHECK(output.height(1, 1) == 0.25F);
CHECK(!world.setHeightWorldUnits(1, 0).ok());
CHECK(!world.setHeightWorldUnits(std::numeric_limits<float>::quiet_NaN(), 1000).ok());
REQUIRE(world.copyHeightmap(1, output).ok());
CHECK(output.height(1, 1) == 0.25F);
ssq::VM vm(1024);
auto table = vm.addTable("eve");
exposeHeightmap(table);
vm.addFunc("world", [&]() { return &world; });
vm.addFunc("output", [&]() { return &output; });
vm.run(vm.compileSource(R"(
assert(world().setHeightWorldUnits(400.0,1000.0).value==18);
assert(world().copyHeightmap(0,output()).ok && output().height(1,1)==0.4);
assert(world().undo().ok && world().copyHeightmap(0,output()).ok);
assert(output().height(1,1)==0.25);
)"));
}
TEST_CASE("procgen.terrainWorld.routesHeightDetailAndTreeDomains") {
TerrainWorldWorkspace world;
REQUIRE(world.create(smallWorld()).ok());
auto operation = wholeWorld();
auto one = filled(1, 1, 1), stamp = filled(1, 1, 0.5F);
REQUIRE(world.stamp(stamp, operation, one, one).ok());
Heightmap heights;
REQUIRE(world.copyHeightmap(0, heights).ok());
CHECK(heights.height(1, 1) > 0);
auto fitness = filled(4, 2, 1);
TerrainDetailSettings detail;
detail.minimumFitness = detail.fadeStart = 0; detail.density = 4; detail.namespaceId = 701;
REQUIRE(world.applyDetail(fitness, detail, operation, TerrainDetailMode::Replace, 17).ok());
TerrainDetailLayer details;
REQUIRE(world.copyDetail(1, details).ok());
CHECK(details.sample(0, 0).value() == 4);
auto flowers = detail;
flowers.density = 2;
flowers.namespaceId = 702;
REQUIRE(world.applyDetail(fitness, flowers, operation, TerrainDetailMode::Add, 19).ok());
REQUIRE(world.copyDetail(1, details).ok());
CHECK(details.sample(0, 0).value() == 6);
TerrainWorldClearSettings detailSourceClear;
detailSourceClear.trees = detailSourceClear.objects = false;
detailSourceClear.sourceNamespace = 701;
REQUIRE(world.clearSpawns(detailSourceClear).ok());
REQUIRE(world.copyDetail(1, details).ok());
CHECK(details.sample(0, 0).value() == 2);
REQUIRE(world.undo().ok());
TerrainTreePlacementSettings trees;
trees.spacing = trees.spawnDensity = 1; trees.jitterPercent = trees.failureRate = trees.minimumFitness = 0;
trees.minimumWidth = trees.maximumWidth = trees.minimumHeight = trees.maximumHeight = 1;
trees.asset = "oak"; trees.namespaceId = 601; trees.maxPoints = 100;
REQUIRE(world.applyTrees(fitness, trees, operation, TerrainTreeOperationMode::Add).ok());
PointSet points;
REQUIRE(world.copyTrees(0, points).ok());
CHECK(!points.empty());
CHECK(world.getOperationCount() == 4);
auto pines = trees;
pines.asset = "pine";
pines.namespaceId = 602;
REQUIRE(world.applyTrees(fitness, pines, operation, TerrainTreeOperationMode::Add).ok());
TerrainWorldClearSettings sourceClear;
sourceClear.details = sourceClear.objects = false;
sourceClear.sourceNamespace = 601;
REQUIRE(world.clearSpawns(sourceClear).ok());
REQUIRE(world.copyTrees(0, points).ok());
REQUIRE(!points.empty());
for (int i = 0; i < points.getCount(); ++i)
CHECK(points.getStringAttribute(i, "spawnNamespace", "") == "602");
REQUIRE(world.undo().ok());
TerrainWorldClearSettings clear;
clear.objects = false;
REQUIRE(world.clearSpawns(clear).ok());
REQUIRE(world.copyDetail(1, details).ok());
CHECK(details.sample(0, 0).value() == 0);
REQUIRE(world.copyTrees(0, points).ok());
CHECK(points.empty());
REQUIRE(world.undo().ok());
REQUIRE(world.copyTrees(0, points).ok());
CHECK(!points.empty());
REQUIRE(world.flatten().ok());
REQUIRE(world.copyHeightmap(0, heights).ok());
for (float value : heights.data()) CHECK(value == 0);
REQUIRE(world.undo().ok());
REQUIRE(world.copyHeightmap(0, heights).ok());
CHECK(heights.height(1, 1) > 0);
}