Skip to content

Commit bdf1612

Browse files
authored
Merge pull request #54 from klaussilveira/refactor/terrain-generator
Refactored terrain generator
2 parents cdb7773 + 51d9920 commit bdf1612

File tree

6 files changed

+195
-69
lines changed

6 files changed

+195
-69
lines changed

radiant/ui/terrain/TerrainGeneratorDialog.cpp

Lines changed: 18 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,13 @@
22

33
#include "i18n.h"
44
#include "ui/imainframe.h"
5-
#include "imap.h"
6-
#include "ipatch.h"
5+
#include "icommandsystem.h"
76
#include "iselection.h"
87
#include "icameraview.h"
9-
#include "iscenegraph.h"
108
#include "ishaderclipboard.h"
11-
#include "iundo.h"
129

1310
#include "string/convert.h"
1411
#include "selectionlib.h"
15-
#include "scenelib.h"
1612
#include "shaderlib.h"
1713

1814
#include <random>
@@ -246,72 +242,25 @@ void TerrainGeneratorDialog::Show(const cmd::ArgumentList& args)
246242
return;
247243
}
248244

249-
noise::NoiseParameters params;
250-
params.algorithm = dialog.getAlgorithm();
251-
params.seed = dialog.getSeed();
252-
params.frequency = dialog.getFrequency();
253-
params.amplitude = dialog.getAmplitude();
254-
params.octaves = dialog.getOctaves();
255-
params.persistence = dialog.getPersistence();
256-
params.lacunarity = dialog.getLacunarity();
257-
params.offset = dialog.getOffset();
258-
259-
std::size_t columns = dialog.getColumns();
260-
std::size_t rows = dialog.getRows();
261-
float physicalWidth = dialog.getPhysicalWidth();
262-
float physicalHeight = dialog.getPhysicalHeight();
263-
std::string material = dialog.getMaterial();
264-
265245
Vector3 spawnPos = getSpawnPosition();
266246

267-
UndoableCommand undo("terrainGeneratorCreate");
268-
269-
GlobalSelectionSystem().setSelectedAll(false);
270-
271-
scene::INodePtr node = GlobalPatchModule().createPatch(patch::PatchDefType::Def2);
272-
273-
GlobalMapModule().findOrInsertWorldspawn()->addChildNode(node);
274-
275-
IPatch* patch = Node_getIPatch(node);
276-
if (!patch)
277-
{
278-
return;
279-
}
280-
281-
patch->setDims(columns, rows);
282-
283-
// Create noise generator
284-
noise::NoiseGenerator noiseGen(params);
285-
float spacingX = physicalWidth / static_cast<float>(columns - 1);
286-
float spacingY = physicalHeight / static_cast<float>(rows - 1);
287-
float offsetX = spawnPos.x() - physicalWidth / 2.0f;
288-
float offsetY = spawnPos.y() - physicalHeight / 2.0f;
289-
float baseZ = spawnPos.z();
290-
291-
// Fill in points based on thr noise
292-
for (std::size_t row = 0; row < rows; ++row)
293-
{
294-
for (std::size_t col = 0; col < columns; ++col)
295-
{
296-
PatchControl& ctrl = patch->ctrlAt(row, col);
297-
298-
float worldX = offsetX + col * spacingX;
299-
float worldY = offsetY + row * spacingY;
300-
float noiseValue = static_cast<float>(noiseGen.sample(worldX, worldY));
301-
302-
ctrl.vertex.x() = worldX;
303-
ctrl.vertex.y() = worldY;
304-
ctrl.vertex.z() = baseZ + noiseValue;
305-
ctrl.texcoord.x() = static_cast<float>(col) / static_cast<float>(columns - 1);
306-
ctrl.texcoord.y() = static_cast<float>(row) / static_cast<float>(rows - 1);
307-
}
308-
}
309-
310-
patch->controlPointsChanged();
311-
patch->setShader(material);
312-
patch->scaleTextureNaturally();
313-
314-
Node_setSelected(node, true);
247+
GlobalCommandSystem().executeCommand("GenerateTerrain",
248+
{ cmd::Argument(static_cast<int>(dialog.getAlgorithm())),
249+
cmd::Argument(static_cast<int>(dialog.getSeed())),
250+
cmd::Argument(static_cast<double>(dialog.getFrequency())),
251+
cmd::Argument(static_cast<double>(dialog.getAmplitude())),
252+
cmd::Argument(dialog.getOctaves()),
253+
cmd::Argument(static_cast<double>(dialog.getPersistence())),
254+
cmd::Argument(static_cast<double>(dialog.getLacunarity())),
255+
cmd::Argument(static_cast<double>(dialog.getOffset())),
256+
cmd::Argument(static_cast<int>(dialog.getColumns())),
257+
cmd::Argument(static_cast<int>(dialog.getRows())),
258+
cmd::Argument(static_cast<double>(dialog.getPhysicalWidth())),
259+
cmd::Argument(static_cast<double>(dialog.getPhysicalHeight())),
260+
cmd::Argument(spawnPos.x()),
261+
cmd::Argument(spawnPos.y()),
262+
cmd::Argument(spawnPos.z()),
263+
cmd::Argument(dialog.getMaterial()) });
315264
}
316265

317266
} // namespace ui

radiantcore/selection/algorithm/General.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,6 +1048,15 @@ void registerCommands()
10481048
cmd::ARGTYPE_INT, cmd::ARGTYPE_DOUBLE, cmd::ARGTYPE_INT,
10491049
cmd::ARGTYPE_INT, cmd::ARGTYPE_DOUBLE, cmd::ARGTYPE_INT
10501050
});
1051+
1052+
GlobalCommandSystem().addCommand("GenerateTerrain", generateTerrainCmd,
1053+
{
1054+
cmd::ARGTYPE_INT, cmd::ARGTYPE_INT, cmd::ARGTYPE_DOUBLE, cmd::ARGTYPE_DOUBLE,
1055+
cmd::ARGTYPE_INT, cmd::ARGTYPE_DOUBLE, cmd::ARGTYPE_DOUBLE, cmd::ARGTYPE_DOUBLE,
1056+
cmd::ARGTYPE_INT, cmd::ARGTYPE_INT, cmd::ARGTYPE_DOUBLE, cmd::ARGTYPE_DOUBLE,
1057+
cmd::ARGTYPE_DOUBLE, cmd::ARGTYPE_DOUBLE, cmd::ARGTYPE_DOUBLE,
1058+
cmd::ARGTYPE_STRING
1059+
});
10511060
}
10521061

10531062
} // namespace algorithm

radiantcore/selection/algorithm/Transformation.cpp

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
#include "iarray.h"
2424
#include "ibrush.h"
2525
#include "icameraview.h"
26+
#include "ipatch.h"
27+
28+
#include "noise/Noise.h"
2629

2730
#include "scenelib.h"
2831
#include "selectionlib.h"
@@ -1572,6 +1575,83 @@ void scatterObjectsCmd(const cmd::ArgumentList &args) {
15721575
seed, faceDirection, rotationRange, alignToNormal);
15731576
}
15741577

1578+
void generateTerrainCmd(const cmd::ArgumentList& args)
1579+
{
1580+
if (args.size() != 16)
1581+
{
1582+
rWarning() << "Usage: GenerateTerrain <algorithm:int> <seed:int> <frequency:double> <amplitude:double> "
1583+
<< "<octaves:int> <persistence:double> <lacunarity:double> <offset:double> "
1584+
<< "<columns:int> <rows:int> <physicalWidth:double> <physicalHeight:double> "
1585+
<< "<spawnX:double> <spawnY:double> <spawnZ:double> <material:string>" << std::endl;
1586+
return;
1587+
}
1588+
1589+
noise::NoiseParameters params;
1590+
params.algorithm = static_cast<noise::Algorithm>(args[0].getInt());
1591+
params.seed = static_cast<unsigned int>(args[1].getInt());
1592+
params.frequency = args[2].getDouble();
1593+
params.amplitude = args[3].getDouble();
1594+
params.octaves = args[4].getInt();
1595+
params.persistence = args[5].getDouble();
1596+
params.lacunarity = args[6].getDouble();
1597+
params.offset = args[7].getDouble();
1598+
1599+
std::size_t columns = static_cast<std::size_t>(args[8].getInt());
1600+
std::size_t rows = static_cast<std::size_t>(args[9].getInt());
1601+
float physicalWidth = static_cast<float>(args[10].getDouble());
1602+
float physicalHeight = static_cast<float>(args[11].getDouble());
1603+
1604+
Vector3 spawnPos(args[12].getDouble(), args[13].getDouble(), args[14].getDouble());
1605+
std::string material = args[15].getString();
1606+
1607+
UndoableCommand undo("terrainGeneratorCreate");
1608+
1609+
GlobalSelectionSystem().setSelectedAll(false);
1610+
1611+
scene::INodePtr node = GlobalPatchModule().createPatch(patch::PatchDefType::Def2);
1612+
1613+
GlobalMapModule().findOrInsertWorldspawn()->addChildNode(node);
1614+
1615+
IPatch* patch = Node_getIPatch(node);
1616+
if (!patch)
1617+
{
1618+
return;
1619+
}
1620+
1621+
patch->setDims(columns, rows);
1622+
1623+
noise::NoiseGenerator noiseGen(params);
1624+
float spacingX = physicalWidth / static_cast<float>(columns - 1);
1625+
float spacingY = physicalHeight / static_cast<float>(rows - 1);
1626+
float offsetX = static_cast<float>(spawnPos.x()) - physicalWidth / 2.0f;
1627+
float offsetY = static_cast<float>(spawnPos.y()) - physicalHeight / 2.0f;
1628+
float baseZ = static_cast<float>(spawnPos.z());
1629+
1630+
for (std::size_t row = 0; row < rows; ++row)
1631+
{
1632+
for (std::size_t col = 0; col < columns; ++col)
1633+
{
1634+
PatchControl& ctrl = patch->ctrlAt(row, col);
1635+
1636+
float worldX = offsetX + col * spacingX;
1637+
float worldY = offsetY + row * spacingY;
1638+
float noiseValue = static_cast<float>(noiseGen.sample(worldX, worldY));
1639+
1640+
ctrl.vertex.x() = worldX;
1641+
ctrl.vertex.y() = worldY;
1642+
ctrl.vertex.z() = baseZ + noiseValue;
1643+
ctrl.texcoord.x() = static_cast<float>(col) / static_cast<float>(columns - 1);
1644+
ctrl.texcoord.y() = static_cast<float>(row) / static_cast<float>(rows - 1);
1645+
}
1646+
}
1647+
1648+
patch->controlPointsChanged();
1649+
patch->setShader(material);
1650+
patch->scaleTextureNaturally();
1651+
1652+
Node_setSelected(node, true);
1653+
}
1654+
15751655
} // namespace algorithm
15761656

15771657
} // namespace selection

radiantcore/selection/algorithm/Transformation.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,11 @@ void arrayCloneSelectedSplineCmd(const cmd::ArgumentList& args);
170170
*/
171171
void scatterObjectsCmd(const cmd::ArgumentList& args);
172172

173+
/**
174+
* Generates a terrain patch using noise.
175+
*/
176+
void generateTerrainCmd(const cmd::ArgumentList& args);
177+
173178
} // namespace algorithm
174179

175180
} // namespace selection

test/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ add_executable(drtest
5454
Selection.cpp
5555
Settings.cpp
5656
SoundManager.cpp
57+
TerrainGenerator.cpp
5758
TextureManipulation.cpp
5859
TestOrthoViewManager.cpp
5960
TextureTool.cpp

test/TerrainGenerator.cpp

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include "RadiantTest.h"
2+
3+
#include "icommandsystem.h"
4+
#include "imap.h"
5+
#include "ipatch.h"
6+
#include "iundo.h"
7+
8+
#include "algorithm/Scene.h"
9+
10+
namespace test
11+
{
12+
13+
using TerrainGeneratorTest = RadiantTest;
14+
15+
void executeGenerateTerrain(int algorithm, int seed = 42, double frequency = 0.01,
16+
double amplitude = 64.0, int octaves = 4, double persistence = 0.5,
17+
double lacunarity = 2.0, double offset = 1.0, int columns = 5, int rows = 5,
18+
double width = 512.0, double height = 512.0, double spawnX = 0.0,
19+
double spawnY = 0.0, double spawnZ = 0.0,
20+
const std::string& material = "textures/common/caulk")
21+
{
22+
GlobalCommandSystem().executeCommand("GenerateTerrain",
23+
{ cmd::Argument(algorithm),
24+
cmd::Argument(seed),
25+
cmd::Argument(frequency),
26+
cmd::Argument(amplitude),
27+
cmd::Argument(octaves),
28+
cmd::Argument(persistence),
29+
cmd::Argument(lacunarity),
30+
cmd::Argument(offset),
31+
cmd::Argument(columns),
32+
cmd::Argument(rows),
33+
cmd::Argument(width),
34+
cmd::Argument(height),
35+
cmd::Argument(spawnX),
36+
cmd::Argument(spawnY),
37+
cmd::Argument(spawnZ),
38+
cmd::Argument(material) });
39+
}
40+
41+
TEST_F(TerrainGeneratorTest, BasicTerrainGeneration)
42+
{
43+
const int columns = 5;
44+
const int rows = 5;
45+
const std::string material = "textures/common/caulk";
46+
47+
executeGenerateTerrain(0, 42, 0.01, 64.0, 4, 0.5, 2.0, 1.0,
48+
columns, rows, 512.0, 512.0, 0.0, 0.0, 0.0, material);
49+
50+
auto worldspawn = GlobalMapModule().findOrInsertWorldspawn();
51+
52+
// Find the generated patch
53+
auto patchNode = algorithm::findFirstPatchWithMaterial(worldspawn, material);
54+
ASSERT_TRUE(patchNode) << "Terrain patch should have been created";
55+
56+
auto* patch = Node_getIPatch(patchNode);
57+
ASSERT_NE(patch, nullptr);
58+
59+
// Check that the resulting patch was created
60+
EXPECT_EQ(patch->getWidth(), columns);
61+
EXPECT_EQ(patch->getHeight(), rows);
62+
EXPECT_EQ(patch->getShader(), material);
63+
}
64+
65+
TEST_F(TerrainGeneratorTest, CanUndoTerrainGeneration)
66+
{
67+
auto worldspawn = GlobalMapModule().findOrInsertWorldspawn();
68+
auto childCountBefore = algorithm::getChildCount(worldspawn);
69+
70+
// Call terrain generator with default args
71+
executeGenerateTerrain(0);
72+
73+
EXPECT_EQ(algorithm::getChildCount(worldspawn), childCountBefore + 1)
74+
<< "One patch should have been added";
75+
76+
GlobalUndoSystem().undo();
77+
78+
EXPECT_EQ(algorithm::getChildCount(worldspawn), childCountBefore)
79+
<< "Undo should remove the generated terrain patch";
80+
}
81+
82+
} // namespace test

0 commit comments

Comments
 (0)