Block Land 7

From DaveWiki

Jump to: navigation, search

Last Time we added multiple block types and performed a little optimization to get them to display quickly. In this article we'll implement a basic world block type layering.

World Layering

For our multiple block type test we just randomly choose a block type from 1-4 to display. A more realistic, and interesting world, will have to have some sort of block type layer generation. For example, in our simple 4-block type world we'll want a layer of grass covering a thin layer of soil, followed by a thick layer of rock and finally some lava.

We'll define a very simple structure to help us define layers:

struct layer_t
{
	block_t BlockID;
	int MinLevel;
	int MaxLevel;
	int SeedOffset;
};
 
layer_t g_Layers[] = 
{
	{ 1, 0, 2, 1 },		//Grass
	{ 2, 0, 10, 2 },	//Soil
	{ 3, 20, 200, 3 },	//Rock
	{ 4, 100, 300, 4 },     //Lava
	{ 255, 0, 0, 0 }        //Must be last
};

Once again this is hard coded but eventually we can easily move this to a text configuration file that is loaded and parsed at run time. The MinLevel/MaxLevel define the minimum and maximum thickness we wish the layer to be. Our layer generation will use the same Perlin noise generation that our landscape height map uses. The base Perlin noise will be then scaled based on the MinLevel/MaxLevel parameters to give us the thickness of each layer at each point in the map.

The SeedOffset parameter determines the random number seed used for each level as offset from the height map. This makes each level have a different looking height map but still be deterministic: identical seeds and offsets will result in identical maps.

void TestBlockLandApplication::initWorldBlocksLayers()
{
	int HeightMap[WORLD_SIZE][WORLD_SIZE];
	infland::CLandscape LayerMaps[10];  //Hard coded because I'm lazy!
	int	NumLayerMaps = 0;
	int BaseSeed;
 
	m_Landscape.SetSize(WORLD_SIZE, WORLD_SIZE);
	m_Landscape.CreateAltitude();
	BaseSeed = m_Landscape.GetSeed();
 
	infland::CMap& Map = m_Landscape.GetAltitudeMap();
 
              //Initialize our temporary height map array
	for (int z = 0; z < WORLD_SIZE; ++z)
	{
		for (int x = 0; x < WORLD_SIZE; ++x)
		{
			float Height = Map.GetValue(x, z) * WORLD_SIZE/4 + WORLD_SIZE/2;
			HeightMap[x][z] = Height;
		}
	}
 
              //Create height maps for each layer
	for (int i = 0; g_Layers[i].BlockID != BLOCK_NULL && i < 10; ++i)
	{
		LayerMaps[i].SetSeed(BaseSeed + g_Layers[i].SeedOffset);
		LayerMaps[i].SetSize(WORLD_SIZE, WORLD_SIZE);
		LayerMaps[i].CreateAltitude();
		++NumLayerMaps;
	}
 
	int Layer = 0;
 
               //Fill in the blocks from all layers
	for (int layer = 0; layer < NumLayerMaps; ++layer)
	{
		infland::CMap& Map = LayerMaps[layer].GetAltitudeMap();
 
		for (int z = 0; z < WORLD_SIZE; ++z)
		{
			for (int x = 0; x < WORLD_SIZE; ++x)
			{
				if (HeightMap[x][z] <= 0) continue;
				int Height = (Map.GetValue(x, z) + 1)/2.0f*(g_Layers[layer].MaxLevel - g_Layers[layer].MinLevel) + g_Layers[layer].MinLevel;
 
                                      //Don't fill the map below 0 height
				if (HeightMap[x][z] - Height < 0) Height = HeightMap[x][z];
				HeightMap[x][z] -= Height;	
 
				int MaxHeight = HeightMap[x][z] + Height;
 
				for (int y = HeightMap[x][z]; y <= MaxHeight; ++y)
				{
					GetBlock(x, y, z) = g_Layers[layer].BlockID;
				}
 
			}
		}
	}
 
}

And our result is (chunk sides are shown and fog disabled to clearly see the layers):

256 Block World With Basic Layering

To get a different layer distribution or shapes we just have to edit the g_Layers array.

Caves

Caves are the next thing to add to our landscape generation. For this we will use the RidgedMulti noise module from libnoise, although we could also investigate other modules like Perlin, Billow, or Voronoi. With the proper parameters RidgedMulti appears to offer a good chance of being capable of creating a natural like 3D cave formation throughout our block world.

Our cave formation will happen after our current landscape creation phase and uses the RidgedMulti noise to determine which blocks to remove from the world.

void TestBlockLandApplication::initWorldBlocksCaves()
{
	noise::module::RidgedMulti NoiseSource;
	float nx, ny, nz;
	int x, y, z;
 
                // Change these to increase/decrease the cave size scale
	float delta = 0.01f;
        float ValueLimit = 0.80f;
 
		// Initialize the noise module
	NoiseSource.SetSeed(0);
	NoiseSource.SetOctaveCount(4);
 
	for (y = 0, ny = 0; y < WORLD_SIZE; ++y, ny += delta)
	{
		for (z = 0, nz = 0; z < WORLD_SIZE; ++z, nz += delta)
		{
			for (x = 0, nx = 0; x < WORLD_SIZE; ++x, nx += delta)
			{
				float Value = NoiseSource.GetValue(nx, ny, nz);
				if (Value > ValueLimit ) GetBlock(x,y,z) = 0;
			}
		}
	}
}

There are a three parameters we can play with to get different types of caves:

  • delta -- Changes the scale of the caves. A larger value results in more, smaller cave systems.
  • ValueLimit -- Also changes the scale of the caves. A larger value results in narrow caves, a smaller value in larger/wider caves.
  • SetOctaveCount() -- A higher value results in more complex cave systems but also increases the generation time.

With the above values we get some decent looking cave systems:

256 Block World With Caves

One important thing to note is that adding this landscape creation step has greatly increased the generation time. For example, on my relatively low end laptop for a 256x256x256 block world the block generation takes just 2 secs but the cave generation takes 60 sec. For a static type world like Dwarf Fortress this is acceptable but for a dynamic/infinite world like MineCraft where landscape generation has to be done in real time it may be too slow.

Next Time

Next Time we'll work on getting a better lighting system to get some dark caves.

Personal tools