summaryrefslogtreecommitdiffstats
path: root/src/Generating/CompoGenBiomal.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/Generating/CompoGenBiomal.cpp')
-rw-r--r--src/Generating/CompoGenBiomal.cpp586
1 files changed, 586 insertions, 0 deletions
diff --git a/src/Generating/CompoGenBiomal.cpp b/src/Generating/CompoGenBiomal.cpp
new file mode 100644
index 000000000..995851c95
--- /dev/null
+++ b/src/Generating/CompoGenBiomal.cpp
@@ -0,0 +1,586 @@
+
+// CompoGenBiomal.cpp
+
+// Implements the cCompoGenBiomal class representing the biome-aware composition generator
+
+#include "Globals.h"
+#include "ComposableGenerator.h"
+#include "../IniFile.h"
+#include "../Noise/Noise.h"
+#include "../LinearUpscale.h"
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cPattern:
+
+/** This class is used to store a column pattern initialized at runtime,
+so that the program doesn't need to explicitly set 256 values for each pattern
+Each pattern has 256 blocks so that there's no need to check pattern bounds when assigning the
+pattern - there will always be enough pattern left, even for the whole-chunk-height columns. */
+class cPattern
+{
+public:
+ struct BlockInfo
+ {
+ BLOCKTYPE m_BlockType;
+ NIBBLETYPE m_BlockMeta;
+ };
+
+ cPattern(BlockInfo * a_TopBlocks, size_t a_Count)
+ {
+ // Copy the pattern into the top:
+ for (size_t i = 0; i < a_Count; i++)
+ {
+ m_Pattern[i] = a_TopBlocks[i];
+ }
+
+ // Fill the rest with stone:
+ static BlockInfo Stone = {E_BLOCK_STONE, 0};
+ for (size_t i = a_Count; i < cChunkDef::Height; i++)
+ {
+ m_Pattern[i] = Stone;
+ }
+ }
+
+ const BlockInfo * Get(void) const { return m_Pattern; }
+
+protected:
+ BlockInfo m_Pattern[cChunkDef::Height];
+} ;
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// The arrays to use for the top block pattern definitions:
+
+static cPattern::BlockInfo tbGrass[] =
+{
+ {E_BLOCK_GRASS, 0},
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+} ;
+
+static cPattern::BlockInfo tbSand[] =
+{
+ { E_BLOCK_SAND, 0},
+ { E_BLOCK_SAND, 0},
+ { E_BLOCK_SAND, 0},
+ { E_BLOCK_SANDSTONE, 0},
+} ;
+
+static cPattern::BlockInfo tbDirt[] =
+{
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+} ;
+
+static cPattern::BlockInfo tbPodzol[] =
+{
+ {E_BLOCK_DIRT, E_META_DIRT_PODZOL},
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+} ;
+
+static cPattern::BlockInfo tbGrassLess[] =
+{
+ {E_BLOCK_DIRT, E_META_DIRT_GRASSLESS},
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+ {E_BLOCK_DIRT, E_META_DIRT_NORMAL},
+} ;
+
+static cPattern::BlockInfo tbMycelium[] =
+{
+ {E_BLOCK_MYCELIUM, 0},
+ {E_BLOCK_DIRT, 0},
+ {E_BLOCK_DIRT, 0},
+ {E_BLOCK_DIRT, 0},
+} ;
+
+static cPattern::BlockInfo tbGravel[] =
+{
+ {E_BLOCK_GRAVEL, 0},
+ {E_BLOCK_GRAVEL, 0},
+ {E_BLOCK_GRAVEL, 0},
+ {E_BLOCK_STONE, 0},
+} ;
+
+static cPattern::BlockInfo tbStone[] =
+{
+ {E_BLOCK_STONE, 0},
+ {E_BLOCK_STONE, 0},
+ {E_BLOCK_STONE, 0},
+ {E_BLOCK_STONE, 0},
+} ;
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Ocean floor pattern top-block definitions:
+
+static cPattern::BlockInfo tbOFSand[] =
+{
+ {E_BLOCK_SAND, 0},
+ {E_BLOCK_SAND, 0},
+ {E_BLOCK_SAND, 0},
+ {E_BLOCK_SANDSTONE, 0}
+} ;
+
+static cPattern::BlockInfo tbOFClay[] =
+{
+ { E_BLOCK_CLAY, 0},
+ { E_BLOCK_CLAY, 0},
+ { E_BLOCK_SAND, 0},
+ { E_BLOCK_SAND, 0},
+} ;
+
+static cPattern::BlockInfo tbOFOrangeClay[] =
+{
+ { E_BLOCK_STAINED_CLAY, E_META_STAINED_GLASS_ORANGE},
+ { E_BLOCK_STAINED_CLAY, E_META_STAINED_GLASS_ORANGE},
+ { E_BLOCK_STAINED_CLAY, E_META_STAINED_GLASS_ORANGE},
+} ;
+
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Individual patterns to use:
+
+static cPattern patGrass (tbGrass, ARRAYCOUNT(tbGrass));
+static cPattern patSand (tbSand, ARRAYCOUNT(tbSand));
+static cPattern patDirt (tbDirt, ARRAYCOUNT(tbDirt));
+static cPattern patPodzol (tbPodzol, ARRAYCOUNT(tbPodzol));
+static cPattern patGrassLess(tbGrassLess, ARRAYCOUNT(tbGrassLess));
+static cPattern patMycelium (tbMycelium, ARRAYCOUNT(tbMycelium));
+static cPattern patGravel (tbGravel, ARRAYCOUNT(tbGravel));
+static cPattern patStone (tbStone, ARRAYCOUNT(tbStone));
+
+static cPattern patOFSand (tbOFSand, ARRAYCOUNT(tbOFSand));
+static cPattern patOFClay (tbOFClay, ARRAYCOUNT(tbOFClay));
+static cPattern patOFOrangeClay(tbOFOrangeClay, ARRAYCOUNT(tbOFOrangeClay));
+
+
+
+
+
+////////////////////////////////////////////////////////////////////////////////
+// cCompoGenBiomal:
+
+class cCompoGenBiomal :
+ public cTerrainCompositionGen
+{
+public:
+ cCompoGenBiomal(int a_Seed) :
+ m_SeaLevel(62),
+ m_OceanFloorSelect(a_Seed + 1),
+ m_MesaFloor(a_Seed + 2)
+ {
+ initMesaPattern(a_Seed);
+ }
+
+protected:
+ /** The block height at which water is generated instead of air. */
+ int m_SeaLevel;
+
+ /** The pattern used for mesa biomes. Initialized by seed on generator creation. */
+ cPattern::BlockInfo m_MesaPattern[2 * cChunkDef::Height];
+
+ /** Noise used for selecting between dirt and sand on the ocean floor. */
+ cNoise m_OceanFloorSelect;
+
+ /** Noise used for the floor of the clay blocks in mesa biomes. */
+ cNoise m_MesaFloor;
+
+
+ // cTerrainCompositionGen overrides:
+ virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc, const cChunkDesc::Shape & a_Shape) override
+ {
+ a_ChunkDesc.FillBlocks(E_BLOCK_AIR, 0);
+ for (int z = 0; z < cChunkDef::Width; z++)
+ {
+ for (int x = 0; x < cChunkDef::Width; x++)
+ {
+ ComposeColumn(a_ChunkDesc, x, z, &(a_Shape[x * 256 + z * 16 * 256]));
+ } // for x
+ } // for z
+ }
+
+
+
+ virtual void InitializeCompoGen(cIniFile & a_IniFile) override
+ {
+ m_SeaLevel = a_IniFile.GetValueSetI("Generator", "SeaLevel", m_SeaLevel);
+ }
+
+
+
+ /** Initializes the m_MesaPattern with a pattern based on the generator's seed. */
+ void initMesaPattern(int a_Seed)
+ {
+ // In a loop, choose whether to use one, two or three layers of stained clay, then choose a color and width for each layer
+ // Separate each group with another layer of hardened clay
+ cNoise patternNoise((unsigned)a_Seed);
+ static NIBBLETYPE allowedColors[] =
+ {
+ E_META_STAINED_CLAY_YELLOW,
+ E_META_STAINED_CLAY_YELLOW,
+ E_META_STAINED_CLAY_RED,
+ E_META_STAINED_CLAY_RED,
+ E_META_STAINED_CLAY_WHITE,
+ E_META_STAINED_CLAY_BROWN,
+ E_META_STAINED_CLAY_BROWN,
+ E_META_STAINED_CLAY_BROWN,
+ E_META_STAINED_CLAY_ORANGE,
+ E_META_STAINED_CLAY_ORANGE,
+ E_META_STAINED_CLAY_ORANGE,
+ E_META_STAINED_CLAY_ORANGE,
+ E_META_STAINED_CLAY_ORANGE,
+ E_META_STAINED_CLAY_ORANGE,
+ E_META_STAINED_CLAY_LIGHTGRAY,
+ } ;
+ static int layerSizes[] = // Adjust the chance so that thinner layers occur more commonly
+ {
+ 1, 1, 1, 1, 1, 1,
+ 2, 2, 2, 2,
+ 3, 3,
+ } ;
+ int idx = ARRAYCOUNT(m_MesaPattern) - 1;
+ while (idx >= 0)
+ {
+ // A layer group of 1 - 2 color stained clay:
+ int rnd = patternNoise.IntNoise1DInt(idx) / 7;
+ int numLayers = (rnd % 2) + 1;
+ rnd /= 2;
+ for (int lay = 0; lay < numLayers; lay++)
+ {
+ int numBlocks = layerSizes[(rnd % ARRAYCOUNT(layerSizes))];
+ NIBBLETYPE Color = allowedColors[(rnd / 4) % ARRAYCOUNT(allowedColors)];
+ if (
+ ((numBlocks == 3) && (numLayers == 2)) || // In two-layer mode disallow the 3-high layers:
+ (Color == E_META_STAINED_CLAY_WHITE)) // White stained clay can ever be only 1 block high
+ {
+ numBlocks = 1;
+ }
+ numBlocks = std::min(idx + 1, numBlocks); // Limit by idx so that we don't have to check inside the loop
+ rnd /= 32;
+ for (int block = 0; block < numBlocks; block++, idx--)
+ {
+ m_MesaPattern[idx].m_BlockMeta = Color;
+ m_MesaPattern[idx].m_BlockType = E_BLOCK_STAINED_CLAY;
+ } // for block
+ } // for lay
+
+ // A layer of hardened clay in between the layer group:
+ int numBlocks = (rnd % 4) + 1; // All heights the same probability
+ if ((numLayers == 2) && (numBlocks < 4))
+ {
+ // For two layers of stained clay, add an extra block of hardened clay:
+ numBlocks++;
+ }
+ numBlocks = std::min(idx + 1, numBlocks); // Limit by idx so that we don't have to check inside the loop
+ for (int block = 0; block < numBlocks; block++, idx--)
+ {
+ m_MesaPattern[idx].m_BlockMeta = 0;
+ m_MesaPattern[idx].m_BlockType = E_BLOCK_HARDENED_CLAY;
+ } // for block
+ } // while (idx >= 0)
+ }
+
+
+
+ /** Composes a single column in a_ChunkDesc. Chooses what to do based on the biome in that column. */
+ void ComposeColumn(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelZ, const Byte * a_ShapeColumn)
+ {
+ // Frequencies for the podzol floor selecting noise:
+ const NOISE_DATATYPE FrequencyX = 8;
+ const NOISE_DATATYPE FrequencyZ = 8;
+
+ EMCSBiome Biome = a_ChunkDesc.GetBiome(a_RelX, a_RelZ);
+ switch (Biome)
+ {
+ case biOcean:
+ case biPlains:
+ case biForest:
+ case biTaiga:
+ case biSwampland:
+ case biRiver:
+ case biFrozenOcean:
+ case biFrozenRiver:
+ case biIcePlains:
+ case biIceMountains:
+ case biForestHills:
+ case biTaigaHills:
+ case biExtremeHillsEdge:
+ case biExtremeHillsPlus:
+ case biExtremeHills:
+ case biJungle:
+ case biJungleHills:
+ case biJungleEdge:
+ case biDeepOcean:
+ case biStoneBeach:
+ case biColdBeach:
+ case biBirchForest:
+ case biBirchForestHills:
+ case biRoofedForest:
+ case biColdTaiga:
+ case biColdTaigaHills:
+ case biSavanna:
+ case biSavannaPlateau:
+ case biSunflowerPlains:
+ case biFlowerForest:
+ case biTaigaM:
+ case biSwamplandM:
+ case biIcePlainsSpikes:
+ case biJungleM:
+ case biJungleEdgeM:
+ case biBirchForestM:
+ case biBirchForestHillsM:
+ case biRoofedForestM:
+ case biColdTaigaM:
+ case biSavannaM:
+ case biSavannaPlateauM:
+ {
+ FillColumnPattern(a_ChunkDesc, a_RelX, a_RelZ, patGrass.Get(), a_ShapeColumn);
+ return;
+ }
+
+ case biMegaTaiga:
+ case biMegaTaigaHills:
+ case biMegaSpruceTaiga:
+ case biMegaSpruceTaigaHills:
+ {
+ // Select the pattern to use - podzol, grass or grassless dirt:
+ NOISE_DATATYPE NoiseX = ((NOISE_DATATYPE)(a_ChunkDesc.GetChunkX() * cChunkDef::Width + a_RelX)) / FrequencyX;
+ NOISE_DATATYPE NoiseY = ((NOISE_DATATYPE)(a_ChunkDesc.GetChunkZ() * cChunkDef::Width + a_RelZ)) / FrequencyZ;
+ NOISE_DATATYPE Val = m_OceanFloorSelect.CubicNoise2D(NoiseX, NoiseY);
+ const cPattern::BlockInfo * Pattern = (Val < -0.9) ? patGrassLess.Get() : ((Val > 0) ? patPodzol.Get() : patGrass.Get());
+ FillColumnPattern(a_ChunkDesc, a_RelX, a_RelZ, Pattern, a_ShapeColumn);
+ return;
+ }
+
+ case biDesertHills:
+ case biDesert:
+ case biDesertM:
+ case biBeach:
+ {
+ FillColumnPattern(a_ChunkDesc, a_RelX, a_RelZ, patSand.Get(), a_ShapeColumn);
+ return;
+ }
+
+ case biMushroomIsland:
+ case biMushroomShore:
+ {
+ FillColumnPattern(a_ChunkDesc, a_RelX, a_RelZ, patMycelium.Get(), a_ShapeColumn);
+ return;
+ }
+
+ case biMesa:
+ case biMesaPlateauF:
+ case biMesaPlateau:
+ case biMesaBryce:
+ case biMesaPlateauFM:
+ case biMesaPlateauM:
+ {
+ // Mesa biomes need special handling, because they don't follow the usual "4 blocks from top pattern",
+ // instead, they provide a "from bottom" pattern with varying base height,
+ // usually 4 blocks below the ocean level
+ FillColumnMesa(a_ChunkDesc, a_RelX, a_RelZ, a_ShapeColumn);
+ return;
+ }
+
+ case biExtremeHillsPlusM:
+ case biExtremeHillsM:
+ {
+ // Select the pattern to use - gravel, stone or grass:
+ NOISE_DATATYPE NoiseX = ((NOISE_DATATYPE)(a_ChunkDesc.GetChunkX() * cChunkDef::Width + a_RelX)) / FrequencyX;
+ NOISE_DATATYPE NoiseY = ((NOISE_DATATYPE)(a_ChunkDesc.GetChunkZ() * cChunkDef::Width + a_RelZ)) / FrequencyZ;
+ NOISE_DATATYPE Val = m_OceanFloorSelect.CubicNoise2D(NoiseX, NoiseY);
+ const cPattern::BlockInfo * Pattern = (Val < 0.0) ? patStone.Get() : patGrass.Get();
+ FillColumnPattern(a_ChunkDesc, a_RelX, a_RelZ, Pattern, a_ShapeColumn);
+ return;
+ }
+ default:
+ {
+ ASSERT(!"Unhandled biome");
+ return;
+ }
+ } // switch (Biome)
+ }
+
+
+
+ /** Fills the specified column with the specified pattern; restarts the pattern when air is reached,
+ switches to ocean floor pattern if ocean is reached. Always adds bedrock at the very bottom. */
+ void FillColumnPattern(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelZ, const cPattern::BlockInfo * a_Pattern, const Byte * a_ShapeColumn)
+ {
+ bool HasHadWater = false;
+ int PatternIdx = 0;
+ int top = std::max(m_SeaLevel, a_ChunkDesc.GetHeight(a_RelX, a_RelZ));
+ for (int y = top; y > 0; y--)
+ {
+ if (a_ShapeColumn[y] > 0)
+ {
+ // "ground" part, use the pattern:
+ a_ChunkDesc.SetBlockTypeMeta(a_RelX, y, a_RelZ, a_Pattern[PatternIdx].m_BlockType, a_Pattern[PatternIdx].m_BlockMeta);
+ PatternIdx++;
+ continue;
+ }
+
+ // "air" or "water" part:
+ // Reset the pattern index to zero, so that the pattern is repeated from the top again:
+ PatternIdx = 0;
+
+ if (y >= m_SeaLevel)
+ {
+ // "air" part, do nothing
+ continue;
+ }
+
+ a_ChunkDesc.SetBlockType(a_RelX, y, a_RelZ, E_BLOCK_STATIONARY_WATER);
+ if (HasHadWater)
+ {
+ continue;
+ }
+
+ // Select the ocean-floor pattern to use:
+ if (a_ChunkDesc.GetBiome(a_RelX, a_RelZ) == biDeepOcean)
+ {
+ a_Pattern = patGravel.Get();
+ }
+ else
+ {
+ a_Pattern = ChooseOceanFloorPattern(a_ChunkDesc.GetChunkX(), a_ChunkDesc.GetChunkZ(), a_RelX, a_RelZ);
+ }
+ HasHadWater = true;
+ } // for y
+ a_ChunkDesc.SetBlockType(a_RelX, 0, a_RelZ, E_BLOCK_BEDROCK);
+ }
+
+
+
+ /** Fills the specified column with mesa pattern, based on the column height */
+ void FillColumnMesa(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelZ, const Byte * a_ShapeColumn)
+ {
+ // Frequencies for the clay floor noise:
+ const NOISE_DATATYPE FrequencyX = 50;
+ const NOISE_DATATYPE FrequencyZ = 50;
+
+ int Top = a_ChunkDesc.GetHeight(a_RelX, a_RelZ);
+ if (Top < m_SeaLevel)
+ {
+ // The terrain is below sealevel, handle as regular ocean with red sand floor:
+ FillColumnPattern(a_ChunkDesc, a_RelX, a_RelZ, patOFOrangeClay.Get(), a_ShapeColumn);
+ return;
+ }
+
+ NOISE_DATATYPE NoiseX = ((NOISE_DATATYPE)(a_ChunkDesc.GetChunkX() * cChunkDef::Width + a_RelX)) / FrequencyX;
+ NOISE_DATATYPE NoiseY = ((NOISE_DATATYPE)(a_ChunkDesc.GetChunkZ() * cChunkDef::Width + a_RelZ)) / FrequencyZ;
+ int ClayFloor = m_SeaLevel - 6 + (int)(4.f * m_MesaFloor.CubicNoise2D(NoiseX, NoiseY));
+ if (ClayFloor >= Top)
+ {
+ ClayFloor = Top - 1;
+ }
+
+ if (Top - m_SeaLevel < 5)
+ {
+ // Simple case: top is red sand, then hardened clay down to ClayFloor, then stone:
+ a_ChunkDesc.SetBlockTypeMeta(a_RelX, Top, a_RelZ, E_BLOCK_SAND, E_META_SAND_RED);
+ for (int y = Top - 1; y >= ClayFloor; y--)
+ {
+ a_ChunkDesc.SetBlockType(a_RelX, y, a_RelZ, E_BLOCK_HARDENED_CLAY);
+ }
+ for (int y = ClayFloor - 1; y > 0; y--)
+ {
+ a_ChunkDesc.SetBlockType(a_RelX, y, a_RelZ, E_BLOCK_STONE);
+ }
+ a_ChunkDesc.SetBlockType(a_RelX, 0, a_RelZ, E_BLOCK_BEDROCK);
+ return;
+ }
+
+ // Difficult case: use the mesa pattern and watch for overhangs:
+ int PatternIdx = cChunkDef::Height - (Top - ClayFloor); // We want the block at index ClayFloor to be pattern's 256th block (first stone)
+ const cPattern::BlockInfo * Pattern = m_MesaPattern;
+ bool HasHadWater = false;
+ for (int y = Top; y > 0; y--)
+ {
+ if (a_ShapeColumn[y] > 0)
+ {
+ // "ground" part, use the pattern:
+ a_ChunkDesc.SetBlockTypeMeta(a_RelX, y, a_RelZ, Pattern[PatternIdx].m_BlockType, Pattern[PatternIdx].m_BlockMeta);
+ PatternIdx++;
+ continue;
+ }
+
+ if (y >= m_SeaLevel)
+ {
+ // "air" part, do nothing
+ continue;
+ }
+
+ // "water" part, fill with water and choose new pattern for ocean floor, if not chosen already:
+ PatternIdx = 0;
+ a_ChunkDesc.SetBlockType(a_RelX, y, a_RelZ, E_BLOCK_STATIONARY_WATER);
+ if (HasHadWater)
+ {
+ continue;
+ }
+
+ // Select the ocean-floor pattern to use:
+ Pattern = ChooseOceanFloorPattern(a_ChunkDesc.GetChunkX(), a_ChunkDesc.GetChunkZ(), a_RelX, a_RelZ);
+ HasHadWater = true;
+ } // for y
+ a_ChunkDesc.SetBlockType(a_RelX, 0, a_RelZ, E_BLOCK_BEDROCK);
+ }
+
+
+
+ /** Returns the pattern to use for an ocean floor in the specified column.
+ The returned pattern is guaranteed to be 256 blocks long. */
+ const cPattern::BlockInfo * ChooseOceanFloorPattern(int a_ChunkX, int a_ChunkZ, int a_RelX, int a_RelZ)
+ {
+ // Frequencies for the ocean floor selecting noise:
+ const NOISE_DATATYPE FrequencyX = 3;
+ const NOISE_DATATYPE FrequencyZ = 3;
+
+ // Select the ocean-floor pattern to use:
+ NOISE_DATATYPE NoiseX = ((NOISE_DATATYPE)(a_ChunkX * cChunkDef::Width + a_RelX)) / FrequencyX;
+ NOISE_DATATYPE NoiseY = ((NOISE_DATATYPE)(a_ChunkZ * cChunkDef::Width + a_RelZ)) / FrequencyZ;
+ NOISE_DATATYPE Val = m_OceanFloorSelect.CubicNoise2D(NoiseX, NoiseY);
+ if (Val < -0.95)
+ {
+ return patOFClay.Get();
+ }
+ else if (Val < 0)
+ {
+ return patOFSand.Get();
+ }
+ else
+ {
+ return patDirt.Get();
+ }
+ }
+} ;
+
+
+
+
+
+cTerrainCompositionGenPtr CreateCompoGenBiomal(int a_Seed)
+{
+ return std::make_shared<cCompoGenBiomal>(a_Seed);
+}
+
+
+