Merge branch 'GeneratorArticle'
|
@ -1,32 +1,53 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 10.00
|
||||
# Visual C++ Express 2008
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AnvilStats", "AnvilStats.vcproj", "{CF996A5E-0A86-4004-9710-682B06B5AEBA}"
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Express 2013 for Windows Desktop
|
||||
VisualStudioVersion = 12.0.21005.1
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AnvilStats", "AnvilStats.vcxproj", "{CF996A5E-0A86-4004-9710-682B06B5AEBA}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA} = {EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821} = {B61007AC-B557-4B67-A765-E468C0C3A821}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\..\VC2008\zlib.vcproj", "{EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}"
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\..\lib\zlib\zlib.vcxproj", "{B61007AC-B557-4B67-A765-E468C0C3A821}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
DebugProfile|Win32 = DebugProfile|Win32
|
||||
MinSizeRel|Win32 = MinSizeRel|Win32
|
||||
Release profiled|Win32 = Release profiled|Win32
|
||||
Release|Win32 = Release|Win32
|
||||
ReleaseProfile|Win32 = ReleaseProfile|Win32
|
||||
RelWithDebInfo|Win32 = RelWithDebInfo|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.DebugProfile|Win32.ActiveCfg = Debug|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.DebugProfile|Win32.Build.0 = Debug|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.MinSizeRel|Win32.ActiveCfg = Release|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.MinSizeRel|Win32.Build.0 = Release|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.Release profiled|Win32.ActiveCfg = Release profiled|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.Release profiled|Win32.Build.0 = Release profiled|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.Release|Win32.Build.0 = Release|Win32
|
||||
{EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release profiled|Win32.ActiveCfg = Release profiled|Win32
|
||||
{EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release profiled|Win32.Build.0 = Release profiled|Win32
|
||||
{EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{EA9D50FD-937A-4EF5-8C37-5F4175AF4FEA}.Release|Win32.Build.0 = Release|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.ReleaseProfile|Win32.ActiveCfg = Release|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.ReleaseProfile|Win32.Build.0 = Release|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.RelWithDebInfo|Win32.ActiveCfg = Release|Win32
|
||||
{CF996A5E-0A86-4004-9710-682B06B5AEBA}.RelWithDebInfo|Win32.Build.0 = Release|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.DebugProfile|Win32.ActiveCfg = DebugProfile|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.DebugProfile|Win32.Build.0 = DebugProfile|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.MinSizeRel|Win32.ActiveCfg = MinSizeRel|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.MinSizeRel|Win32.Build.0 = MinSizeRel|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.Release profiled|Win32.ActiveCfg = Release|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.Release profiled|Win32.Build.0 = Release|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.Release|Win32.Build.0 = Release|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.ReleaseProfile|Win32.ActiveCfg = ReleaseProfile|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.ReleaseProfile|Win32.Build.0 = ReleaseProfile|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.RelWithDebInfo|Win32.ActiveCfg = RelWithDebInfo|Win32
|
||||
{B61007AC-B557-4B67-A765-E468C0C3A821}.RelWithDebInfo|Win32.Build.0 = RelWithDebInfo|Win32
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -5,38 +5,7 @@
|
|||
|
||||
#include "Globals.h"
|
||||
#include "BiomeMap.h"
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
static const int g_BiomePalette[] =
|
||||
{
|
||||
// ARGB:
|
||||
0xff0000ff, /* Ocean */
|
||||
0xff00cf3f, /* Plains */
|
||||
0xffffff00, /* Desert */
|
||||
0xff7f7f7f, /* Extreme Hills */
|
||||
0xff00cf00, /* Forest */
|
||||
0xff007f3f, /* Taiga */
|
||||
0xff3f7f00, /* Swampland */
|
||||
0xff003fff, /* River */
|
||||
0xff7f0000, /* Hell */
|
||||
0xff007fff, /* Sky */
|
||||
0xff3f3fff, /* Frozen Ocean */
|
||||
0xff3f3fff, /* Frozen River */
|
||||
0xff7fffcf, /* Ice Plains */
|
||||
0xff3fcf7f, /* Ice Mountains */
|
||||
0xffcf00cf, /* Mushroom Island */
|
||||
0xff7f00ff, /* Mushroom Island Shore */
|
||||
0xffffff3f, /* Beach */
|
||||
0xffcfcf00, /* Desert Hills */
|
||||
0xff00cf3f, /* Forest Hills */
|
||||
0xff006f1f, /* Taiga Hills */
|
||||
0xff7f8f7f, /* Extreme Hills Edge */
|
||||
0xff004f00, /* Jungle */
|
||||
0xff003f00, /* Jungle Hills */
|
||||
} ;
|
||||
#include "../BiomeVisualiser/BiomeColors.h"
|
||||
|
||||
|
||||
|
||||
|
@ -139,7 +108,7 @@ void cBiomeMap::StartNewRegion(int a_RegionX, int a_RegionZ)
|
|||
unsigned char * BiomeRow = (unsigned char *)m_Biomes + z * 512;
|
||||
for (int x = 0; x < 512; x++)
|
||||
{
|
||||
RowData[x] = g_BiomePalette[BiomeRow[x]];
|
||||
RowData[x] = g_BiomeColors[BiomeRow[x]];
|
||||
}
|
||||
f.Write(RowData, sizeof(RowData));
|
||||
} // for z
|
||||
|
|
|
@ -41,7 +41,7 @@ protected:
|
|||
virtual bool OnDecompressedData(const char * a_DecompressedNBT, int a_DataSize) override { return false; }
|
||||
virtual bool OnRealCoords(int a_ChunkX, int a_ChunkZ) override { return false; }
|
||||
virtual bool OnLastUpdate(Int64 a_LastUpdate) override { return false; }
|
||||
virtual bool OnTerrainPopulated(bool a_Populated) override { return !a_Populated; } // If not populated, we don't want it!
|
||||
virtual bool OnTerrainPopulated(bool a_Populated) override { return false; } // We don't care about "populated", the biomes are the same
|
||||
virtual bool OnBiomes(const unsigned char * a_BiomeData) override;
|
||||
|
||||
void StartNewRegion(int a_RegionX, int a_RegionZ);
|
||||
|
|
|
@ -24,6 +24,15 @@
|
|||
#define ALIGN_8
|
||||
#define ALIGN_16
|
||||
|
||||
#define FORMATSTRING(formatIndex, va_argsIndex)
|
||||
|
||||
// MSVC has its own custom version of zu format
|
||||
#define SIZE_T_FMT "%Iu"
|
||||
#define SIZE_T_FMT_PRECISION(x) "%" #x "Iu"
|
||||
#define SIZE_T_FMT_HEX "%Ix"
|
||||
|
||||
#define NORETURN __declspec(noreturn)
|
||||
|
||||
#elif defined(__GNUC__)
|
||||
|
||||
// TODO: Can GCC explicitly mark classes as abstract (no instances can be created)?
|
||||
|
@ -40,6 +49,14 @@
|
|||
// Some portability macros :)
|
||||
#define stricmp strcasecmp
|
||||
|
||||
#define FORMATSTRING(formatIndex, va_argsIndex) __attribute__((format (printf, formatIndex, va_argsIndex)))
|
||||
|
||||
#define SIZE_T_FMT "%zu"
|
||||
#define SIZE_T_FMT_PRECISION(x) "%" #x "zu"
|
||||
#define SIZE_T_FMT_HEX "%zx"
|
||||
|
||||
#define NORETURN __attribute((__noreturn__))
|
||||
|
||||
#else
|
||||
|
||||
#error "You are using an unsupported compiler, you might need to #define some stuff here for your compiler"
|
||||
|
@ -194,6 +211,8 @@ typedef unsigned short UInt16;
|
|||
/// Faster than (int)floorf((float)x / (float)div)
|
||||
#define FAST_FLOOR_DIV( x, div ) ( (x) < 0 ? (((int)x / div) - 1) : ((int)x / div) )
|
||||
|
||||
#define TOLUA_TEMPLATE_BIND(...)
|
||||
|
||||
// Own version of assert() that writes failed assertions to the log for review
|
||||
#ifdef _DEBUG
|
||||
#define ASSERT( x ) ( !!(x) || ( LOGERROR("Assertion failed: %s, file %s, line %i", #x, __FILE__, __LINE__ ), assert(0), 0 ) )
|
||||
|
@ -204,6 +223,8 @@ typedef unsigned short UInt16;
|
|||
// Pretty much the same as ASSERT() but stays in Release builds
|
||||
#define VERIFY( x ) ( !!(x) || ( LOGERROR("Verification failed: %s, file %s, line %i", #x, __FILE__, __LINE__ ), exit(1), 0 ) )
|
||||
|
||||
typedef unsigned char Byte;
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -227,3 +248,4 @@ public:
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ bool cSpringStats::OnSectionsFinished(void)
|
|||
int Base = BaseY + z * 16;
|
||||
for (int x = 1; x < 15; x++)
|
||||
{
|
||||
if (cChunkDef::GetNibble(m_BlockMetas, Base + x) != 0)
|
||||
if (cChunkDef::GetNibble(m_BlockMetas, x, y, z) != 0)
|
||||
{
|
||||
// Not a source block
|
||||
continue;
|
||||
|
|
|
@ -0,0 +1,315 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Generating terrain in MCServer</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Generating terrain in MCServer</h1>
|
||||
<p>This article explains the principles behind the terrain generator in MCServer. It is not strictly
|
||||
specific to MCServer, though, it can be viewed as a generic guide to various terrain-generating algorithms,
|
||||
with specific implementation notes regarding MCServer.</p>
|
||||
|
||||
<p>Contents:
|
||||
<ul>
|
||||
<li><a href="#preface">Preface: How it's done in real life</a></li>
|
||||
<li><a href="#expectedprops">Expected properties</a></li>
|
||||
<li><a href="#reversingflow">Reversing the flow</a></li>
|
||||
<li><a href="#composablegen">The ComposableGenerator pipeline</a></li>
|
||||
<li><a href="#coherentnoise">Using coherent noise</a></li>
|
||||
<li><a href="#biomegen">Generating biomes</a></li>
|
||||
<li><a href="#heightgen">Terrain height</a></li>
|
||||
<li><a href="#compositiongen">Terrain composition</a></li>
|
||||
<li><a href="#finishgen">Finishers</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<a name="preface"><h2>Preface: How it's done in real life</h2></a>
|
||||
<p>The nature has many complicated geological, physical and biological processes working on all scales from
|
||||
microscopic to planet-wide scale, that have shaped the terrain into what we see today. The tectonic plates
|
||||
collide, push mountain ranges up and ocean trenches down. Erosion dulls the sharp shapes. Plantlife takes
|
||||
over to further change the overall look of the world.</p>
|
||||
|
||||
<p>Generally speaking, the processes take what's there and change it. Unlike computer generating, which
|
||||
usually creates a finished terrain from scratch, or maybe with only a few iterations. It would be unfeasible
|
||||
for software to emulate all the natural processes in enough detail to provide world generation for a game,
|
||||
mainly because in the nature everything interacts with everything. If a mountain range rises, it changes the
|
||||
way that the precipitation is carried by the wind to the lands beyond the mountains, thus changing the
|
||||
erosion rate there and the vegetation type. </p>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<a name="expectedprops"><h2>Expected properties</h2></a>
|
||||
<p>For a MineCraft-like game terrain generator we need the generator to have several properties:
|
||||
<ul>
|
||||
<li>The generator must be able to generate terrain in small chunks. This means it must be possible to
|
||||
generate each of the chunks separately, without dependencies on the neighboring chunks. Note that this
|
||||
doesn't mean chunks cannot coordinate together, it means that "a tree in one chunk cannot ask if there's
|
||||
a building in the neighbor chunk", simply because the neighbor chunk may not be generated yet.</li>
|
||||
<li>The generated chunk needs to be the same if re-generated. This property is not exactly required, but it
|
||||
makes available several techniques that wouldn't be possible otherwise.</li>
|
||||
<li>The generator needs to be reasonably fast. For a server application this means at least some 20 chunks
|
||||
per second for chunks close to each other, and 5 chunks per second for distant chunks. The reason for this
|
||||
distinction will be discussed later.</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<a name="reversingflow"><h2>Reversing the flow</h2></a>
|
||||
<p>As already mentioned, the nature works basically by generating raw terrain composition, then "applying"
|
||||
erosion, vegetation and finally this leads to biomes being formed. Let's now try a somewhat inverse
|
||||
approach: First generate biomes, then fit them with appropriate terrain, and finally cover in vegetation
|
||||
and all the other stuff.</p>
|
||||
|
||||
<p>Splitting the parts like this suddenly makes it possible to create a generator with the required
|
||||
properties. We can generate a reasonable biome map chunk-wise, independently of all the other data. Once we
|
||||
have the biomes, we can compose the terrain for the chunk by using the biome data for the chunk, and
|
||||
possibly even for neighboring chunks. Note that we're not breaking the first property, the biomes can be
|
||||
generated separately so a neighboring chunk's biome map can be generated without the need for the entire
|
||||
neighboring chunk to be present. Similarly, once we have the terrain composition for a chunk, we can
|
||||
generate all the vegetation and structures in it, and those can again use the terrain composition in
|
||||
neighboring chunks.</p>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<a name="composablegen"><h2>The ComposableGenerator pipeline</h2></a>
|
||||
<p>This leads us directly to the main pipeline that is used for generating terrain in MCServer. For
|
||||
technical reasons, the terrain composition step is further subdivided into Height generation and Composition
|
||||
generation, and the structures are really called Finishers. For each chunk the generator generates, in this
|
||||
sequence:
|
||||
<ul>
|
||||
<li>Biomes</li>
|
||||
<li>Terrain height</li>
|
||||
<li>Terrain composition</li>
|
||||
<li>Finishers</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<img src="img/biomes.jpg" />
|
||||
<img src="img/terrainheight.jpg" />
|
||||
<img src="img/terraincomposition.jpg" />
|
||||
<img src="img/finishers.jpg" />
|
||||
<p>The beautiful thing about this is that the individual components can be changed independently. You can
|
||||
have 5 biome generators and 3 height generators and you can let the users mix'n'match.
|
||||
</p>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<a name="coherentnoise"><h2>Using coherent noise for the generation</h2></a>
|
||||
<p>For a great tutorial on coherent noise, see the <a href="http://libnoise.sourceforge.net/">LibNoise
|
||||
documentation</a>.</p>
|
||||
<p>Coherent noise is a type of noise that has three important properties that we can use to our advantage:
|
||||
<ul>
|
||||
<li>The noise is smooth</li>
|
||||
<li>The noise is algorithmically generated, which means that the same data is generated when the same
|
||||
parameters are given to the noise functions.</li>
|
||||
<li>The noise can be seamlessly extended in any direction</li>
|
||||
</ul></p>
|
||||
|
||||
<p>We'll be mostly using Perlin noise in this article. It is the easiest one to visualise and use and is one
|
||||
of the most useful kinds of coherent noises. Here's an example of a Perlin noise generated in 2 dimensions:</p>
|
||||
<img src="img/perlin.jpg" />
|
||||
|
||||
<p>It comes only naturally that such a 2D noise can be used as a terrain height map directly:</p>
|
||||
<img src="img/perlinheightmap.jpg" />
|
||||
|
||||
<p>However, this is not the only use for this noise, and 2 dimensions is not the limit - this noise can be
|
||||
generated for any number of dimensions.</p>
|
||||
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<a name="biomegen"><h2>Generating biomes</h2></a>
|
||||
<p>The easiest way to generate biomes is to not generate them at all - simply assign a single constant biome
|
||||
to everywhere. And indeed there are times when this kind of "generator" is useful - for the MineCraft's Flat
|
||||
world type, or for testing purposes, or for tematic maps. In MCServer, this is exactly what the Constant
|
||||
biome generator does.</p>
|
||||
|
||||
<p>Of course, there are more interesting test scenarios for which multiple biomes must be generated as easy
|
||||
as possible. For these special needs, there's a CheckerBoard biome generator. As the name suggests, it
|
||||
generates a grid of alternating biomes.</p>
|
||||
|
||||
<h3>Voronoi diagram</h3>
|
||||
<p>Those two generators were more of a technicality, we need to make something more interesting if we're
|
||||
going for a natural look. The Voronoi generator is the first step towards such a change. Recall that a
|
||||
<a href="http://en.wikipedia.org/wiki/Voronoi_diagram">Voronoi diagram</a> is a construct that creates a
|
||||
set of areas where each point in an area is closer to the appropriate seed of the area than the seeds of any
|
||||
other area:</p>
|
||||
<img src="img/voronoi.png" />
|
||||
|
||||
<p>To generate biomes using this approach, you select random "seeds", assign a biome to each one, and then
|
||||
for each "column" of the world you find the seed that is the nearest to that column, and use that seed's
|
||||
biome.</p>
|
||||
|
||||
<p>The overall shape of a Voronoi diagram is governed by the placement of the seeds. In extreme cases, a
|
||||
seed could affect the entire diagram, which is what we don't want - we need our locality, so that we can
|
||||
generate a chunk's worth of biome data. We also don't want the too much irregular diagrams that are produced
|
||||
when the seeds are in small clusters. We need our seeds to come in random, yet somewhat uniform fashion.</p>
|
||||
|
||||
<p>Luckily, we have just the tool: Grid with jitter. Originally used in antialiasing techniques, they can be
|
||||
successfully applied as a source of the seeds for a Voronoi diagram. Simply take a regular 2D grid of seeds
|
||||
with the grid distance being N, and move each seed along the X and Y axis by a random distance, usually in
|
||||
the range [-N / 2, +N / 2]:</p>
|
||||
<img src="img/jittergrid.jpg" />
|
||||
|
||||
<p>Such a grid is the ideal seed source for a Voronoi biome generator, because not
|
||||
only are the Voronoi cells "reasonable", but the seed placement's effect on the diagram is localized - each
|
||||
pixel in the diagram depends on at most 4 x 4 seeds around it. In the following picture, the seed for the
|
||||
requested point (blue) must be within the indicated circle. Even the second-nearest seed, which we will need
|
||||
later, is inside that circle.</p>
|
||||
<img src="img/jittergridlocality.jpg" />
|
||||
|
||||
<p>Calculating the jitter for each cell can be done easily by using a 2D Perlin noise for each coord. We
|
||||
calculate the noise's value at [X, Z], which gives us a number in the range [-1; 1]. We then multiply the
|
||||
number by N / 2, this gives us the required range of [-N / 2, +N / 2]. Adding this number to the X coord
|
||||
gives us the seed's X position. We use another Perlin noise and the same calculation for the Z coord of the
|
||||
seed.</p>
|
||||
|
||||
<p>Here's an example of a biome map generated using the Voronoi + jitter grid, as implemented by the Voronoi
|
||||
biome generator in MCServer:</p>
|
||||
<img src="img/voronoijitterbiomes.png" />
|
||||
|
||||
<h3>Distorted Voronoi</h3>
|
||||
<p>The biomes are starting to look interesting, but now they have straight-line borders, which looks rather
|
||||
weird and the players will most likely notice very soon. We need to somehow distort the borders to make them
|
||||
look more natural. By far the easiest way to achieve that is to use a little trick: When the generator is
|
||||
asked for the biome at column [X, Z], instead of calculating the Voronoi biome for column [X, Z], we first
|
||||
calculate a random offset for each coord, and add it to the coordinates. So the generator actually responds
|
||||
with the biome for [X + rndX, Z + rndZ].</p>
|
||||
|
||||
<p>In order to keep the property that generating for the second time gives us the same result, we need the
|
||||
"random offset" to be replicatable - same output for the same input. This is where we use yet another Perlin
|
||||
noise - just like with the jitter for the Voronoi grid, we add a value from a separate noise to each
|
||||
coordinate before sending the coordinates down to the Voronoi generator:</p>
|
||||
<code>
|
||||
DistortedVoronoiBiome(X, Z) := VoronoiBiome(X + PerlinX(X, Z), Z + PerlinZ(X, Z))
|
||||
</code>
|
||||
|
||||
<p>The following image shows the effects of the change, as generated by MCServer's DistortedVoronoi biome
|
||||
generator. It is actually using the very same Voronoi map as the previous image, the only change has been
|
||||
the addition of the distortion:</p>
|
||||
<img src="img/distortedvoronoibiomes.png" />
|
||||
|
||||
<p>As you can see, this already looks reasonable enough, it could be considered natural biomes, if it
|
||||
weren't for several drawbacks:
|
||||
<ul>
|
||||
<li>There's no way to limit the neighbors. A desert biome can neighbor a tundra biome. </li>
|
||||
<li>All the biomes are considered equal. There's no way to make oceans larger. A mushroom biome is
|
||||
generated right next to other land biomes.</li>
|
||||
</ul></p>
|
||||
|
||||
<h3>Adding relativity</h3>
|
||||
<p>Our next goal is to remove the first defect of the distorted Voronoi generator: unrelated biomes
|
||||
generating next to each other. It is highly unlikely to find a jungle biome next to a desert biome, so we
|
||||
want to have as few of such borders as possible. We could further improve on the selection of
|
||||
biome-to-seed in the Voronoi generator. Or we can try a completely different idea altogether.</p>
|
||||
|
||||
<p>Recall how we talked about the nature, where the biomes are formed by the specific conditions of a place.
|
||||
What if we could make a similar dependency, but without the terrain? It turns out this is possible rather
|
||||
easily - instead of depending on the terrain, we choose two completely artificial measures. Let's call them
|
||||
Temperature and Humidity. If we knew the temperature of the place, we know what set of biomes are possible
|
||||
for such temperatures - we won't place deserts in the cold and tundra in the hot anymore. Similarly, the
|
||||
humidity will help us sort out the desert vs jungle issue. But how do we get a temperature and humidity?
|
||||
Once again, the Perlin noise comes to the rescue. We can use a simple 2D Perlin noise as the temperature
|
||||
map, and another one as the humidity map.</p>
|
||||
|
||||
<p>What we need next is a decision of what biome to generate in certain temperature and humidity
|
||||
combinations. The fastest way for a computer is to have a 2D array, where the temperature is one dimension
|
||||
and humidity the other, and the values in the array specify the biome to generate:</p>
|
||||
<img src="img/temperaturehumiditydecisionsimple.jpg" />
|
||||
|
||||
<p>We can even "misuse" the above diagram to include the hill variants of the biomes and have those hills
|
||||
neighbor each other properly, simply by declaring some of the decision diagram's parts as hills:</p>
|
||||
<img src="img/temperaturehumiditydecisionhills.jpg" />
|
||||
|
||||
<p>The problem with this approach is that there are biomes that should not depend on temperature or
|
||||
humidity, they generate across all of their values. Biomes like Oceans, Rivers and Mushroom. We could
|
||||
either add them somewhere into the decision diagram, or we can make the generator use a multi-step decision:
|
||||
<ul>
|
||||
<li>Decide whether the point is in the ocean, land or mushroom</li>
|
||||
<li>If it's land, decide if it's real land or river.</li>
|
||||
<li>If it's real land, use a TemperatureHumidity approach to generate land-biomes</li>
|
||||
</ul>
|
||||
</p>
|
||||
|
||||
<p>This is the approach implemented in MCServer's MultiStepMap biome generator. It generates biome maps like
|
||||
this:</p>
|
||||
<img src="img/multistepmapbiomes.png" />
|
||||
|
||||
<p>To decide whether the point is in the ocean, land or mushroom, the generator first chooses seeds in a grid
|
||||
that will be later fed to a DistortedVoronoi algorithm, the seeds get the "ocean" and "land" values. Then it
|
||||
considers all the "ocean" seeds that are surrounded by 8 other "ocean" seeds and turns a random few of them
|
||||
into "mushroom". This special seed processing makes the mushroom biomes mostly surrounded by ocean. The
|
||||
following image shows an example seeds grid that the generator might consider, only the two framed cells are
|
||||
allowed to change into mushroom. L = land, O = ocean:</p>
|
||||
<img src="img/multistepmapgrid.jpg" />
|
||||
|
||||
<p>Next, the generator calculates the DistortedVoronoi for the seeds. For the areas that are calculated as
|
||||
mushroom, the distance to the nearest-seed is used to further shrink the mushroom biome and then to
|
||||
distinguish between mushroom and mushroom-shore (image depicts a Voronoi cell for illustration purposes, it
|
||||
works similarly with DistortedVoronoi). O = ocean, M = mushroom, MS = mushroom shore:</p>
|
||||
<img src="img/multistepmapdistance.jpg" />
|
||||
|
||||
<p>The rivers are added only to the areas that have been previously marked as land. A simple 2D Perlin noise
|
||||
is used as the base, where its value is between 0 and a configured threshold value, a river is created. This
|
||||
creates the rivers in a closed-loop-like shapes, occasionally splitting two branches off:</p>
|
||||
<img src="img/perlinrivers.jpg" />
|
||||
|
||||
<p>For the leftover land biomes, the two Perlin noises, representing temperature and humidity, are used to
|
||||
generate the biomes, as described earlier. Additionally, the temperature map is used to turn the Ocean biome
|
||||
into FrozenOcean, and the River biome into FrozenRiver, wherever the temperature drops below a threshold.</p>
|
||||
|
||||
<h3>Two-level Voronoi</h3>
|
||||
<p>The 1.7 MineCraft update brought a completely new terrain generation, which has sparked renewed interest
|
||||
in the biome generation. A new, potentially simpler way of generating biomes was found, the two-level
|
||||
DistortedVoronoi generator.</p>
|
||||
|
||||
<p>The main idea behind it all is that we create large areas of similar biomes. There are several groups of
|
||||
related biomes that can be generated near each other: Desert biomes, Ice biomes, Forest biomes, Mesa biomes.
|
||||
Technically, the Ocean biomes were added as yet another group, so that the oceans will generate in
|
||||
approximately the size of the larger areas, too.</p>
|
||||
|
||||
<p>For each column a DistortedVoronoi is used to select, which large area to use. This in turn results in
|
||||
the list of biomes from which to choose. Another DistortedVoronoi, this time with a smaller grid size, is
|
||||
used to select one biome out of that list. Additionally, the smaller DistortedVoronoi calculates not only
|
||||
the nearest seed's distance, but also the distance to the second-nearest seed; the ratio between these two
|
||||
is used as an indicator whether the column is in the "inside" or on the "outskirt" of the smaller Voronoi
|
||||
cell. This allows us to give certain biomes an "edge" biome - the Mushroom biome has a MushroomShore edge,
|
||||
the ExtremeHills biome have an ExtremeHillsEdge biome on the edge, etc.</p>
|
||||
|
||||
<p>The images below illustrate the process with regular Voronoi diagrams, for clarity purposes. The real
|
||||
generator uses distortion before querying the small areas.</p>
|
||||
<img src="img/twolevellargeareas.jpg" /><br />
|
||||
<img src="img/twolevelsmallgrid.jpg" /><br />
|
||||
<img src="img/twolevelsmallareas.jpg" /><br />
|
||||
|
||||
<p>The following image shows an example output of a TwoLevel biome generator in MCServer:</p>
|
||||
<img src="img/twolevelbiomes.png" />
|
||||
|
||||
<p>Note that rivers are currently not implemented in this generator in MCServer, but they could be added
|
||||
using the same approach as in MultiStepMap - by using a thresholded 2D Perlin noise.</p>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<a name="heightgen"><h2>Terrain height</h2></a>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<a name="compositiongen"><h2>Terrain composition</h2></a>
|
||||
|
||||
|
||||
<hr />
|
||||
|
||||
<a name="finishgen"><h2>Finishers</h2></a>
|
||||
|
||||
</body>
|
||||
</html>
|
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 40 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 61 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 52 KiB |
After Width: | Height: | Size: 57 KiB |
After Width: | Height: | Size: 86 KiB |
After Width: | Height: | Size: 50 KiB |
After Width: | Height: | Size: 42 KiB |
After Width: | Height: | Size: 28 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 46 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 120 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 4.2 KiB |