diff options
Diffstat (limited to 'src/Generating')
30 files changed, 12312 insertions, 0 deletions
diff --git a/src/Generating/BioGen.cpp b/src/Generating/BioGen.cpp new file mode 100644 index 000000000..a0407a145 --- /dev/null +++ b/src/Generating/BioGen.cpp @@ -0,0 +1,929 @@ + +// BioGen.cpp + +// Implements the various biome generators + +#include "Globals.h" +#include "BioGen.h" +#include "inifile/iniFile.h" +#include "../LinearUpscale.h" + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cBiomeGen: + +cBiomeGen * cBiomeGen::CreateBiomeGen(cIniFile & a_IniFile, int a_Seed, bool & a_CacheOffByDefault) +{ + AString BiomeGenName = a_IniFile.GetValueSet("Generator", "BiomeGen", ""); + if (BiomeGenName.empty()) + { + LOGWARN("[Generator] BiomeGen value not set in world.ini, using \"MultiStepMap\"."); + BiomeGenName = "MultiStepMap"; + } + + cBiomeGen * res = NULL; + a_CacheOffByDefault = false; + if (NoCaseCompare(BiomeGenName, "constant") == 0) + { + res = new cBioGenConstant; + a_CacheOffByDefault = true; // we're generating faster than a cache would retrieve data :) + } + else if (NoCaseCompare(BiomeGenName, "checkerboard") == 0) + { + res = new cBioGenCheckerboard; + a_CacheOffByDefault = true; // we're (probably) generating faster than a cache would retrieve data + } + else if (NoCaseCompare(BiomeGenName, "voronoi") == 0) + { + res = new cBioGenVoronoi(a_Seed); + } + else if (NoCaseCompare(BiomeGenName, "distortedvoronoi") == 0) + { + res = new cBioGenDistortedVoronoi(a_Seed); + } + else if (NoCaseCompare(BiomeGenName, "twolevel") == 0) + { + res = new cBioGenTwoLevel(a_Seed); + } + else + { + if (NoCaseCompare(BiomeGenName, "multistepmap") != 0) + { + LOGWARNING("Unknown BiomeGen \"%s\", using \"MultiStepMap\" instead.", BiomeGenName.c_str()); + } + res = new cBioGenMultiStepMap(a_Seed); + + /* + // Performance-testing: + LOGINFO("Measuring performance of cBioGenMultiStepMap..."); + clock_t BeginTick = clock(); + for (int x = 0; x < 5000; x++) + { + cChunkDef::BiomeMap Biomes; + res->GenBiomes(x * 5, x * 5, Biomes); + } + clock_t Duration = clock() - BeginTick; + LOGINFO("cBioGenMultiStepMap for 5000 chunks took %d ticks (%.02f sec)", Duration, (double)Duration / CLOCKS_PER_SEC); + //*/ + } + res->InitializeBiomeGen(a_IniFile); + + return res; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cBioGenConstant: + +void cBioGenConstant::GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + for (int i = 0; i < ARRAYCOUNT(a_BiomeMap); i++) + { + a_BiomeMap[i] = m_Biome; + } +} + + + + + +void cBioGenConstant::InitializeBiomeGen(cIniFile & a_IniFile) +{ + AString Biome = a_IniFile.GetValueSet("Generator", "ConstantBiome", "Plains"); + m_Biome = StringToBiome(Biome); + if (m_Biome == -1) + { + LOGWARN("[Generator]::ConstantBiome value \"%s\" not recognized, using \"Plains\".", Biome.c_str()); + m_Biome = biPlains; + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cBioGenCache: + +cBioGenCache::cBioGenCache(cBiomeGen * a_BioGenToCache, int a_CacheSize) : + m_BioGenToCache(a_BioGenToCache), + m_CacheSize(a_CacheSize), + m_CacheOrder(new int[a_CacheSize]), + m_CacheData(new sCacheData[a_CacheSize]), + m_NumHits(0), + m_NumMisses(0), + m_TotalChain(0) +{ + for (int i = 0; i < m_CacheSize; i++) + { + m_CacheOrder[i] = i; + m_CacheData[i].m_ChunkX = 0x7fffffff; + m_CacheData[i].m_ChunkZ = 0x7fffffff; + } +} + + + + + +cBioGenCache::~cBioGenCache() +{ + delete[] m_CacheData; + delete[] m_CacheOrder; +} + + + + + +void cBioGenCache::GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + if (((m_NumHits + m_NumMisses) % 1024) == 10) + { + LOGD("BioGenCache: %d hits, %d misses, saved %.2f %%", m_NumHits, m_NumMisses, 100.0 * m_NumHits / (m_NumHits + m_NumMisses)); + LOGD("BioGenCache: Avg cache chain length: %.2f", (float)m_TotalChain / m_NumHits); + } + + for (int i = 0; i < m_CacheSize; i++) + { + if ( + (m_CacheData[m_CacheOrder[i]].m_ChunkX != a_ChunkX) || + (m_CacheData[m_CacheOrder[i]].m_ChunkZ != a_ChunkZ) + ) + { + continue; + } + // Found it in the cache + int Idx = m_CacheOrder[i]; + + // Move to front: + for (int j = i; j > 0; j--) + { + m_CacheOrder[j] = m_CacheOrder[j - 1]; + } + m_CacheOrder[0] = Idx; + + // Use the cached data: + memcpy(a_BiomeMap, m_CacheData[Idx].m_BiomeMap, sizeof(a_BiomeMap)); + + m_NumHits++; + m_TotalChain += i; + return; + } // for i - cache + + // Not in the cache: + m_NumMisses++; + m_BioGenToCache->GenBiomes(a_ChunkX, a_ChunkZ, a_BiomeMap); + + // Insert it as the first item in the MRU order: + int Idx = m_CacheOrder[m_CacheSize - 1]; + for (int i = m_CacheSize - 1; i > 0; i--) + { + m_CacheOrder[i] = m_CacheOrder[i - 1]; + } // for i - m_CacheOrder[] + m_CacheOrder[0] = Idx; + memcpy(m_CacheData[Idx].m_BiomeMap, a_BiomeMap, sizeof(a_BiomeMap)); + m_CacheData[Idx].m_ChunkX = a_ChunkX; + m_CacheData[Idx].m_ChunkZ = a_ChunkZ; +} + + + + + +void cBioGenCache::InitializeBiomeGen(cIniFile & a_IniFile) +{ + super::InitializeBiomeGen(a_IniFile); + m_BioGenToCache->InitializeBiomeGen(a_IniFile); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cBiomeGenList: + +void cBiomeGenList::InitializeBiomes(const AString & a_Biomes) +{ + AStringVector Split = StringSplit(a_Biomes, ","); + + // Convert each string in the list into biome: + for (AStringVector::const_iterator itr = Split.begin(); itr != Split.end(); ++itr) + { + AStringVector Split2 = StringSplit(*itr, ":"); + if (Split2.size() < 1) + { + continue; + } + int Count = 1; + if (Split2.size() >= 2) + { + Count = atol(Split2[1].c_str()); + if (Count <= 0) + { + LOGWARNING("Cannot decode biome count: \"%s\"; using 1.", Split2[1].c_str()); + Count = 1; + } + } + EMCSBiome Biome = StringToBiome(Split2[0]); + if (Biome != -1) + { + for (int i = 0; i < Count; i++) + { + m_Biomes.push_back(Biome); + } + } + else + { + LOGWARNING("Cannot decode biome name: \"%s\"; skipping", Split2[0].c_str()); + } + } // for itr - Split[] + if (!m_Biomes.empty()) + { + m_BiomesCount = (int)m_Biomes.size(); + return; + } + + // There were no biomes, add default biomes: + static EMCSBiome Biomes[] = + { + biOcean, + biPlains, + biDesert, + biExtremeHills, + biForest, + biTaiga, + biSwampland, + biRiver, + biFrozenOcean, + biFrozenRiver, + biIcePlains, + biIceMountains, + biMushroomIsland, + biMushroomShore, + biBeach, + biDesertHills, + biForestHills, + biTaigaHills, + biExtremeHillsEdge, + biJungle, + biJungleHills, + } ; + m_Biomes.reserve(ARRAYCOUNT(Biomes)); + for (int i = 0; i < ARRAYCOUNT(Biomes); i++) + { + m_Biomes.push_back(Biomes[i]); + } + m_BiomesCount = (int)m_Biomes.size(); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cBioGenCheckerboard: + +void cBioGenCheckerboard::GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + for (int z = 0; z < cChunkDef::Width; z++) + { + int Base = (cChunkDef::Width * a_ChunkZ + z) / m_BiomeSize; + for (int x = 0; x < cChunkDef::Width; x++) + { + int Add = cChunkDef::Width * a_ChunkX + x; + int BiomeIdx = (((Base + Add / m_BiomeSize) % m_BiomesCount) + m_BiomesCount) % m_BiomesCount; // Need to add and modulo twice because of negative numbers + a_BiomeMap[x + cChunkDef::Width * z] = m_Biomes[BiomeIdx]; + } + } +} + + + + + +void cBioGenCheckerboard::InitializeBiomeGen(cIniFile & a_IniFile) +{ + super::InitializeBiomeGen(a_IniFile); + AString Biomes = a_IniFile.GetValueSet ("Generator", "CheckerBoardBiomes", ""); + m_BiomeSize = a_IniFile.GetValueSetI("Generator", "CheckerboardBiomeSize", 64); + m_BiomeSize = (m_BiomeSize < 8) ? 8 : m_BiomeSize; + InitializeBiomes(Biomes); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cBioGenVoronoi : + +void cBioGenVoronoi::GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + int BaseZ = cChunkDef::Width * a_ChunkZ; + int BaseX = cChunkDef::Width * a_ChunkX; + for (int z = 0; z < cChunkDef::Width; z++) + { + int AbsoluteZ = BaseZ + z; + for (int x = 0; x < cChunkDef::Width; x++) + { + int VoronoiCellValue = m_Voronoi.GetValueAt(BaseX + x, AbsoluteZ) / 8; + cChunkDef::SetBiome(a_BiomeMap, x, z, m_Biomes[VoronoiCellValue % m_BiomesCount]); + } // for x + } // for z +} + + + + + +void cBioGenVoronoi::InitializeBiomeGen(cIniFile & a_IniFile) +{ + super::InitializeBiomeGen(a_IniFile); + m_Voronoi.SetCellSize(a_IniFile.GetValueSetI("Generator", "VoronoiCellSize", 64)); + InitializeBiomes (a_IniFile.GetValueSet ("Generator", "VoronoiBiomes", "")); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cBioGenDistortedVoronoi: + +void cBioGenDistortedVoronoi::GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + int BaseZ = cChunkDef::Width * a_ChunkZ; + int BaseX = cChunkDef::Width * a_ChunkX; + + // Distortions for linear interpolation: + int DistortX[cChunkDef::Width + 1][cChunkDef::Width + 1]; + int DistortZ[cChunkDef::Width + 1][cChunkDef::Width + 1]; + for (int x = 0; x <= 4; x++) for (int z = 0; z <= 4; z++) + { + Distort(BaseX + x * 4, BaseZ + z * 4, DistortX[4 * x][4 * z], DistortZ[4 * x][4 * z]); + } + + LinearUpscale2DArrayInPlace(&DistortX[0][0], cChunkDef::Width + 1, cChunkDef::Width + 1, 4, 4); + LinearUpscale2DArrayInPlace(&DistortZ[0][0], cChunkDef::Width + 1, cChunkDef::Width + 1, 4, 4); + + for (int z = 0; z < cChunkDef::Width; z++) + { + int AbsoluteZ = BaseZ + z; + for (int x = 0; x < cChunkDef::Width; x++) + { + int VoronoiCellValue = m_Voronoi.GetValueAt(DistortX[x][z], DistortZ[x][z]) / 8; + cChunkDef::SetBiome(a_BiomeMap, x, z, m_Biomes[VoronoiCellValue % m_BiomesCount]); + } // for x + } // for z +} + + + + + +void cBioGenDistortedVoronoi::InitializeBiomeGen(cIniFile & a_IniFile) +{ + super::InitializeBiomeGen(a_IniFile); + m_CellSize = a_IniFile.GetValueSetI("Generator", "DistortedVoronoiCellSize", 96); + m_Voronoi.SetCellSize(m_CellSize); + InitializeBiomes(a_IniFile.GetValueSet("Generator", "DistortedVoronoiBiomes", "")); +} + + + + +void cBioGenDistortedVoronoi::Distort(int a_BlockX, int a_BlockZ, int & a_DistortedX, int & a_DistortedZ) +{ + double NoiseX = m_Noise.CubicNoise3D((float)a_BlockX / m_CellSize, (float)a_BlockZ / m_CellSize, 1000); + NoiseX += 0.5 * m_Noise.CubicNoise3D(2 * (float)a_BlockX / m_CellSize, 2 * (float)a_BlockZ / m_CellSize, 2000); + NoiseX += 0.08 * m_Noise.CubicNoise3D(16 * (float)a_BlockX / m_CellSize, 16 * (float)a_BlockZ / m_CellSize, 3000); + double NoiseZ = m_Noise.CubicNoise3D((float)a_BlockX / m_CellSize, (float)a_BlockZ / m_CellSize, 4000); + NoiseZ += 0.5 * m_Noise.CubicNoise3D(2 * (float)a_BlockX / m_CellSize, 2 * (float)a_BlockZ / m_CellSize, 5000); + NoiseZ += 0.08 * m_Noise.CubicNoise3D(16 * (float)a_BlockX / m_CellSize, 16 * (float)a_BlockZ / m_CellSize, 6000); + + a_DistortedX = a_BlockX + (int)(m_CellSize * 0.5 * NoiseX); + a_DistortedZ = a_BlockZ + (int)(m_CellSize * 0.5 * NoiseZ); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cBioGenMultiStepMap : + +cBioGenMultiStepMap::cBioGenMultiStepMap(int a_Seed) : + m_Noise1(a_Seed + 1000), + m_Noise2(a_Seed + 2000), + m_Noise3(a_Seed + 3000), + m_Noise4(a_Seed + 4000), + m_Noise5(a_Seed + 5000), + m_Noise6(a_Seed + 6000), + m_Seed(a_Seed), + m_OceanCellSize(384), + m_MushroomIslandSize(64), + m_RiverCellSize(384), + m_RiverWidthThreshold(0.125), + m_LandBiomesSize(1024) +{ +} + + + + + +void cBioGenMultiStepMap::InitializeBiomeGen(cIniFile & a_IniFile) +{ + m_OceanCellSize = a_IniFile.GetValueSetI("Generator", "MultiStepMapOceanCellSize", m_OceanCellSize); + m_MushroomIslandSize = a_IniFile.GetValueSetI("Generator", "MultiStepMapMushroomIslandSize", m_MushroomIslandSize); + m_RiverCellSize = a_IniFile.GetValueSetI("Generator", "MultiStepMapRiverCellSize", m_RiverCellSize); + m_RiverWidthThreshold = a_IniFile.GetValueSetF("Generator", "MultiStepMapRiverWidth", m_RiverWidthThreshold); + m_LandBiomesSize = (float)a_IniFile.GetValueSetI("Generator", "MultiStepMapLandBiomeSize", (int)m_LandBiomesSize); +} + + + + + +void cBioGenMultiStepMap::GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + DecideOceanLandMushroom(a_ChunkX, a_ChunkZ, a_BiomeMap); + AddRivers(a_ChunkX, a_ChunkZ, a_BiomeMap); + ApplyTemperatureHumidity(a_ChunkX, a_ChunkZ, a_BiomeMap); +} + + + + + +void cBioGenMultiStepMap::DecideOceanLandMushroom(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + // Distorted Voronoi over 3 biomes, with mushroom having only a special occurence. + + // Prepare a distortion lookup table, by distorting a 5x5 area and using that as 1:4 zoom (linear interpolate): + int BaseZ = cChunkDef::Width * a_ChunkZ; + int BaseX = cChunkDef::Width * a_ChunkX; + int DistortX[cChunkDef::Width + 1][cChunkDef::Width + 1]; + int DistortZ[cChunkDef::Width + 1][cChunkDef::Width + 1]; + int DistortSize = m_OceanCellSize / 2; + for (int x = 0; x <= 4; x++) for (int z = 0; z <= 4; z++) + { + Distort(BaseX + x * 4, BaseZ + z * 4, DistortX[4 * x][4 * z], DistortZ[4 * x][4 * z], DistortSize); + } + LinearUpscale2DArrayInPlace(&DistortX[0][0], cChunkDef::Width + 1, cChunkDef::Width + 1, 4, 4); + LinearUpscale2DArrayInPlace(&DistortZ[0][0], cChunkDef::Width + 1, cChunkDef::Width + 1, 4, 4); + + // Prepare a 9x9 area of neighboring cell seeds + // (assuming that 7x7 cell area is larger than a chunk being generated) + const int NEIGHBORHOOD_SIZE = 4; // How many seeds in each direction to check + int CellX = BaseX / m_OceanCellSize; + int CellZ = BaseZ / m_OceanCellSize; + int SeedX[2 * NEIGHBORHOOD_SIZE + 1][2 * NEIGHBORHOOD_SIZE + 1]; + int SeedZ[2 * NEIGHBORHOOD_SIZE + 1][2 * NEIGHBORHOOD_SIZE + 1]; + EMCSBiome SeedV[2 * NEIGHBORHOOD_SIZE + 1][2 * NEIGHBORHOOD_SIZE + 1]; + for (int xc = 0; xc < 2 * NEIGHBORHOOD_SIZE + 1; xc++) + { + int RealCellX = xc + CellX - NEIGHBORHOOD_SIZE; + int CellBlockX = RealCellX * m_OceanCellSize; + for (int zc = 0; zc < 2 * NEIGHBORHOOD_SIZE + 1; zc++) + { + int RealCellZ = zc + CellZ - NEIGHBORHOOD_SIZE; + int CellBlockZ = RealCellZ * m_OceanCellSize; + int OffsetX = (m_Noise2.IntNoise3DInt(RealCellX, 16 * RealCellX + 32 * RealCellZ, RealCellZ) / 8) % m_OceanCellSize; + int OffsetZ = (m_Noise4.IntNoise3DInt(RealCellX, 32 * RealCellX - 16 * RealCellZ, RealCellZ) / 8) % m_OceanCellSize; + SeedX[xc][zc] = CellBlockX + OffsetX; + SeedZ[xc][zc] = CellBlockZ + OffsetZ; + SeedV[xc][zc] = (((m_Noise6.IntNoise3DInt(RealCellX, RealCellX - RealCellZ + 1000, RealCellZ) / 11) % 256) > 90) ? biOcean : ((EMCSBiome)(-1)); + } // for z + } // for x + + for (int xc = 1; xc < 2 * NEIGHBORHOOD_SIZE; xc++) for (int zc = 1; zc < 2 * NEIGHBORHOOD_SIZE; zc++) + { + if ( + (SeedV[xc ][zc] == biOcean) && + (SeedV[xc - 1][zc] == biOcean) && + (SeedV[xc + 1][zc] == biOcean) && + (SeedV[xc ][zc - 1] == biOcean) && + (SeedV[xc ][zc + 1] == biOcean) && + (SeedV[xc - 1][zc - 1] == biOcean) && + (SeedV[xc + 1][zc - 1] == biOcean) && + (SeedV[xc - 1][zc + 1] == biOcean) && + (SeedV[xc + 1][zc + 1] == biOcean) + ) + { + SeedV[xc][zc] = biMushroomIsland; + } + } + + // For each column find the nearest distorted cell and use its value as the biome: + int MushroomOceanThreshold = m_OceanCellSize * m_OceanCellSize * m_MushroomIslandSize / 1024; + int MushroomShoreThreshold = m_OceanCellSize * m_OceanCellSize * m_MushroomIslandSize / 2048; + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + int AbsoluteZ = DistortZ[x][z]; + int AbsoluteX = DistortX[x][z]; + int MinDist = m_OceanCellSize * m_OceanCellSize * 16; // There has to be a cell closer than this + EMCSBiome Biome = biPlains; + // Find the nearest cell seed: + for (int xs = 1; xs < 2 * NEIGHBORHOOD_SIZE; xs++) for (int zs = 1; zs < 2 * NEIGHBORHOOD_SIZE; zs++) + { + int Dist = (SeedX[xs][zs] - AbsoluteX) * (SeedX[xs][zs] - AbsoluteX) + (SeedZ[xs][zs] - AbsoluteZ) * (SeedZ[xs][zs] - AbsoluteZ); + if (Dist >= MinDist) + { + continue; + } + MinDist = Dist; + Biome = SeedV[xs][zs]; + // Shrink mushroom biome and add a shore: + if (Biome == biMushroomIsland) + { + if (Dist > MushroomOceanThreshold) + { + Biome = biOcean; + } + else if (Dist > MushroomShoreThreshold) + { + Biome = biMushroomShore; + } + } + } // for zs, xs + + cChunkDef::SetBiome(a_BiomeMap, x, z, Biome); + } // for x + } // for z +} + + + + + +void cBioGenMultiStepMap::AddRivers(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + for (int z = 0; z < cChunkDef::Width; z++) + { + float NoiseCoordZ = (float)(a_ChunkZ * cChunkDef::Width + z) / m_RiverCellSize; + for (int x = 0; x < cChunkDef::Width; x++) + { + if (cChunkDef::GetBiome(a_BiomeMap, x, z) != -1) + { + // Biome already set, skip this column + continue; + } + + float NoiseCoordX = (float)(a_ChunkX * cChunkDef::Width + x) / m_RiverCellSize; + + double Noise = m_Noise1.CubicNoise2D( NoiseCoordX, NoiseCoordZ); + Noise += 0.5 * m_Noise3.CubicNoise2D(2 * NoiseCoordX, 2 * NoiseCoordZ); + Noise += 0.1 * m_Noise5.CubicNoise2D(8 * NoiseCoordX, 8 * NoiseCoordZ); + + if ((Noise > 0) && (Noise < m_RiverWidthThreshold)) + { + cChunkDef::SetBiome(a_BiomeMap, x, z, biRiver); + } + } // for x + } // for z +} + + + + + +void cBioGenMultiStepMap::ApplyTemperatureHumidity(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + IntMap TemperatureMap; + IntMap HumidityMap; + BuildTemperatureHumidityMaps(a_ChunkX, a_ChunkZ, TemperatureMap, HumidityMap); + + FreezeWaterBiomes(a_BiomeMap, TemperatureMap); + DecideLandBiomes(a_BiomeMap, TemperatureMap, HumidityMap); +} + + + + + +void cBioGenMultiStepMap::Distort(int a_BlockX, int a_BlockZ, int & a_DistortedX, int & a_DistortedZ, int a_CellSize) +{ + double NoiseX = m_Noise3.CubicNoise2D( (float)a_BlockX / a_CellSize, (float)a_BlockZ / a_CellSize); + NoiseX += 0.5 * m_Noise2.CubicNoise2D(2 * (float)a_BlockX / a_CellSize, 2 * (float)a_BlockZ / a_CellSize); + NoiseX += 0.1 * m_Noise1.CubicNoise2D(16 * (float)a_BlockX / a_CellSize, 16 * (float)a_BlockZ / a_CellSize); + double NoiseZ = m_Noise6.CubicNoise2D( (float)a_BlockX / a_CellSize, (float)a_BlockZ / a_CellSize); + NoiseZ += 0.5 * m_Noise5.CubicNoise2D(2 * (float)a_BlockX / a_CellSize, 2 * (float)a_BlockZ / a_CellSize); + NoiseZ += 0.1 * m_Noise4.CubicNoise2D(16 * (float)a_BlockX / a_CellSize, 16 * (float)a_BlockZ / a_CellSize); + + a_DistortedX = a_BlockX + (int)(a_CellSize * 0.5 * NoiseX); + a_DistortedZ = a_BlockZ + (int)(a_CellSize * 0.5 * NoiseZ); +} + + + + + +void cBioGenMultiStepMap::BuildTemperatureHumidityMaps(int a_ChunkX, int a_ChunkZ, IntMap & a_TemperatureMap, IntMap & a_HumidityMap) +{ + // Linear interpolation over 8x8 blocks; use double for better precision: + DblMap TemperatureMap; + DblMap HumidityMap; + for (int z = 0; z < 17; z += 8) + { + float NoiseCoordZ = (float)(a_ChunkZ * cChunkDef::Width + z) / m_LandBiomesSize; + for (int x = 0; x < 17; x += 8) + { + float NoiseCoordX = (float)(a_ChunkX * cChunkDef::Width + x) / m_LandBiomesSize; + + double NoiseT = m_Noise1.CubicNoise2D( NoiseCoordX, NoiseCoordZ); + NoiseT += 0.5 * m_Noise2.CubicNoise2D(2 * NoiseCoordX, 2 * NoiseCoordZ); + NoiseT += 0.1 * m_Noise3.CubicNoise2D(8 * NoiseCoordX, 8 * NoiseCoordZ); + TemperatureMap[x + 17 * z] = NoiseT; + + double NoiseH = m_Noise4.CubicNoise2D( NoiseCoordX, NoiseCoordZ); + NoiseH += 0.5 * m_Noise5.CubicNoise2D(2 * NoiseCoordX, 2 * NoiseCoordZ); + NoiseH += 0.1 * m_Noise6.CubicNoise2D(8 * NoiseCoordX, 8 * NoiseCoordZ); + HumidityMap[x + 17 * z] = NoiseH; + } // for x + } // for z + LinearUpscale2DArrayInPlace(TemperatureMap, 17, 17, 8, 8); + LinearUpscale2DArrayInPlace(HumidityMap, 17, 17, 8, 8); + + // Re-map into integral values in [0 .. 255] range: + for (int idx = 0; idx < ARRAYCOUNT(a_TemperatureMap); idx++) + { + a_TemperatureMap[idx] = std::max(0, std::min(255, (int)(128 + TemperatureMap[idx] * 128))); + a_HumidityMap[idx] = std::max(0, std::min(255, (int)(128 + HumidityMap[idx] * 128))); + } +} + + + + + +void cBioGenMultiStepMap::DecideLandBiomes(cChunkDef::BiomeMap & a_BiomeMap, const IntMap & a_TemperatureMap, const IntMap & a_HumidityMap) +{ + static const EMCSBiome BiomeMap[] = + { + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + /* 0 */ biTundra, biTundra, biTundra, biTundra, biPlains, biPlains, biPlains, biPlains, biPlains, biPlains, biDesert, biDesert, biDesert, biDesert, biDesert, biDesert, + /* 1 */ biTundra, biTundra, biTundra, biTundra, biPlains, biPlains, biPlains, biPlains, biPlains, biPlains, biDesert, biDesert, biDesert, biDesert, biDesert, biDesert, + /* 2 */ biTundra, biTundra, biTundra, biTundra, biPlains, biExtremeHills, biPlains, biPlains, biPlains, biPlains, biDesert, biDesert, biDesertHills, biDesertHills, biDesert, biDesert, + /* 3 */ biTundra, biTundra, biTundra, biTundra, biExtremeHills, biExtremeHills, biPlains, biPlains, biPlains, biPlains, biDesert, biDesert, biDesertHills, biDesertHills, biDesert, biDesert, + /* 4 */ biTundra, biTundra, biIceMountains, biIceMountains, biExtremeHills, biExtremeHills, biPlains, biPlains, biPlains, biPlains, biForestHills, biForestHills, biExtremeHills, biExtremeHills, biDesertHills, biDesert, + /* 5 */ biTundra, biTundra, biIceMountains, biIceMountains, biExtremeHills, biExtremeHills, biPlains, biPlains, biPlains, biPlains, biForestHills, biForestHills, biExtremeHills, biExtremeHills, biDesertHills, biDesert, + /* 6 */ biTundra, biTundra, biIceMountains, biIceMountains, biForestHills, biForestHills, biForest, biForest, biForest, biForest, biForest, biForestHills, biExtremeHills, biExtremeHills, biPlains, biPlains, + /* 7 */ biTundra, biTundra, biIceMountains, biIceMountains, biForestHills, biForestHills, biForest, biForest, biForest, biForest, biForest, biForestHills, biExtremeHills, biExtremeHills, biPlains, biPlains, + /* 8 */ biTundra, biTundra, biTaiga, biTaiga, biForest, biForest, biForest, biForest, biForest, biForest, biForest, biForestHills, biExtremeHills, biExtremeHills, biPlains, biPlains, + /* 9 */ biTundra, biTundra, biTaiga, biTaiga, biForest, biForest, biForest, biForest, biForest, biForest, biForest, biForestHills, biExtremeHills, biExtremeHills, biPlains, biPlains, + /* 10 */ biTaiga, biTaiga, biTaiga, biIceMountains, biForestHills, biForestHills, biForest, biForest, biForest, biForest, biJungle, biJungle, biSwampland, biSwampland, biSwampland, biSwampland, + /* 11 */ biTaiga, biTaiga, biIceMountains, biIceMountains, biExtremeHills, biForestHills, biForest, biForest, biForest, biForest, biJungle, biJungle, biSwampland, biSwampland, biSwampland, biSwampland, + /* 12 */ biTaiga, biTaiga, biIceMountains, biIceMountains, biExtremeHills, biJungleHills, biJungle, biJungle, biJungle, biJungle, biJungle, biJungle, biSwampland, biSwampland, biSwampland, biSwampland, + /* 13 */ biTaiga, biTaiga, biTaiga, biIceMountains, biJungleHills, biJungleHills, biJungle, biJungle, biJungle, biJungle, biJungle, biJungle, biSwampland, biSwampland, biSwampland, biSwampland, + /* 14 */ biTaiga, biTaiga, biTaiga, biTaiga, biJungle, biJungle, biJungle, biJungle, biJungle, biJungle, biJungle, biJungle, biSwampland, biSwampland, biSwampland, biSwampland, + /* 15 */ biTaiga, biTaiga, biTaiga, biTaiga, biJungle, biJungle, biJungle, biJungle, biJungle, biJungle, biJungle, biJungle, biSwampland, biSwampland, biSwampland, biSwampland, + } ; + for (int z = 0; z < cChunkDef::Width; z++) + { + int idxZ = 17 * z; + for (int x = 0; x < cChunkDef::Width; x++) + { + if (cChunkDef::GetBiome(a_BiomeMap, x, z) != -1) + { + // Already set before + continue; + } + int idx = idxZ + x; + int Temperature = a_TemperatureMap[idx] / 16; // -> [0..15] range + int Humidity = a_HumidityMap[idx] / 16; // -> [0..15] range + cChunkDef::SetBiome(a_BiomeMap, x, z, BiomeMap[Temperature + 16 * Humidity]); + } // for x + } // for z +} + + + + + +void cBioGenMultiStepMap::FreezeWaterBiomes(cChunkDef::BiomeMap & a_BiomeMap, const IntMap & a_TemperatureMap) +{ + int idx = 0; + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++, idx++) + { + if (a_TemperatureMap[idx] > 4 * 16) + { + // Not frozen + continue; + } + switch (cChunkDef::GetBiome(a_BiomeMap, x, z)) + { + case biRiver: cChunkDef::SetBiome(a_BiomeMap, x, z, biFrozenRiver); break; + case biOcean: cChunkDef::SetBiome(a_BiomeMap, x, z, biFrozenOcean); break; + } + } // for x + idx += 1; + } // for z +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cBioGenTwoLevel: + +cBioGenTwoLevel::cBioGenTwoLevel(int a_Seed) : + m_VoronoiLarge(a_Seed + 1000), + m_VoronoiSmall(a_Seed + 2000), + m_DistortX(a_Seed + 3000), + m_DistortZ(a_Seed + 4000), + m_Noise(a_Seed + 5000) +{ +} + + + + + +void cBioGenTwoLevel::GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + int BaseZ = cChunkDef::Width * a_ChunkZ; + int BaseX = cChunkDef::Width * a_ChunkX; + + // Distortions for linear interpolation: + int DistortX[cChunkDef::Width + 1][cChunkDef::Width + 1]; + int DistortZ[cChunkDef::Width + 1][cChunkDef::Width + 1]; + for (int x = 0; x <= 4; x++) for (int z = 0; z <= 4; z++) + { + int BlockX = BaseX + x * 4; + int BlockZ = BaseZ + z * 4; + float BlockXF = (float)(16 * BlockX) / 128; + float BlockZF = (float)(16 * BlockZ) / 128; + double NoiseX = m_Noise.CubicNoise3D(BlockXF / 16, BlockZF / 16, 1000); + NoiseX += 0.5 * m_Noise.CubicNoise3D(BlockXF / 8, BlockZF / 8, 2000); + NoiseX += 0.08 * m_Noise.CubicNoise3D(BlockXF, BlockZF, 3000); + double NoiseZ = m_Noise.CubicNoise3D(BlockXF / 16, BlockZF / 16, 4000); + NoiseZ += 0.5 * m_Noise.CubicNoise3D(BlockXF / 8, BlockZF / 8, 5000); + NoiseZ += 0.08 * m_Noise.CubicNoise3D(BlockXF, BlockZF, 6000); + + DistortX[4 * x][4 * z] = BlockX + (int)(64 * NoiseX); + DistortZ[4 * x][4 * z] = BlockZ + (int)(64 * NoiseZ); + } + + LinearUpscale2DArrayInPlace(&DistortX[0][0], cChunkDef::Width + 1, cChunkDef::Width + 1, 4, 4); + LinearUpscale2DArrayInPlace(&DistortZ[0][0], cChunkDef::Width + 1, cChunkDef::Width + 1, 4, 4); + + // Apply distortion to each block coord, then query the voronoi maps for biome group and biome index and choose biome based on that: + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + int BiomeGroup = m_VoronoiLarge.GetValueAt(DistortX[x][z], DistortZ[x][z]) / 7; + int MinDist1, MinDist2; + int BiomeIdx = m_VoronoiSmall.GetValueAt(DistortX[x][z], DistortZ[x][z], MinDist1, MinDist2) / 11; + cChunkDef::SetBiome(a_BiomeMap, x, z, SelectBiome(BiomeGroup, BiomeIdx, (MinDist1 < MinDist2 / 4) ? 0 : 1)); + } + } +} + + + + + +EMCSBiome cBioGenTwoLevel::SelectBiome(int a_BiomeGroup, int a_BiomeIdx, int a_DistLevel) +{ + // TODO: Move this into settings + struct BiomeLevels + { + EMCSBiome InnerBiome; + EMCSBiome OuterBiome; + } ; + + static BiomeLevels bgOcean[] = + { + { biOcean, biOcean, }, + { biOcean, biOcean, }, + { biOcean, biOcean, }, + { biOcean, biOcean, }, + { biOcean, biDeepOcean, }, + { biOcean, biDeepOcean, }, + { biDeepOcean, biDeepOcean, }, + { biDeepOcean, biDeepOcean, }, + { biDeepOcean, biDeepOcean, }, + { biDeepOcean, biDeepOcean, }, + { biMushroomIsland, biMushroomShore, } + } ; + static BiomeLevels bgFrozen[] = + { + { biIcePlains, biIcePlains, }, + { biIceMountains, biIceMountains, }, + { biFrozenOcean, biFrozenRiver, }, + { biColdTaiga, biColdTaiga, }, + { biColdTaigaHills, biColdTaigaHills, }, + { biColdTaigaM, biColdTaigaM, }, + { biIcePlainsSpikes, biIcePlainsSpikes, }, + { biExtremeHills, biExtremeHillsEdge, }, + { biExtremeHillsPlus, biExtremeHillsEdge, }, + { biExtremeHillsPlusM, biExtremeHillsPlusM, }, + } ; + static BiomeLevels bgTemperate[] = + { + { biBirchForestHills, biBirchForest, }, + { biBirchForest, biBirchForest, }, + { biBirchForestHillsM, biBirchForestM, }, + { biBirchForestM, biBirchForestM, }, + { biForest, biForestHills, }, + { biFlowerForest, biFlowerForest, }, + { biRoofedForest, biForest, }, + { biRoofedForest, biRoofedForest, }, + { biRoofedForestM, biForest, }, + { biPlains, biPlains, }, + { biSunflowerPlains, biSunflowerPlains, }, + { biSwampland, biSwampland, }, + { biSwamplandM, biSwamplandM, }, + } ; + static BiomeLevels bgWarm[] = + { + { biDesertHills, biDesert, }, + { biDesert, biDesert, }, + { biDesertM, biDesertM, }, + { biSavannaPlateau, biSavanna, }, + { biSavanna, biSavanna, }, + { biSavannaM, biSavannaM, }, + } ; + static BiomeLevels bgMesa[] = + { + { biMesaPlateau, biMesa, }, + { biMesaPlateauF, biMesa, }, + { biMesaPlateauFM, biMesa, }, + { biMesaPlateauM, biMesa, }, + { biMesaBryce, biMesaBryce, }, + { biSavanna, biSavanna, }, + { biSavannaPlateau, biSavanna, }, + } ; + static BiomeLevels bgConifers[] = + { + { biTaiga, biTaiga, }, + { biTaigaM, biTaigaM, }, + { biMegaTaiga, biMegaTaiga, }, + { biMegaSpruceTaiga, biMegaSpruceTaiga, }, + { biMegaSpruceTaigaHills, biMegaSpruceTaiga, } + } ; + static BiomeLevels bgDenseTrees[] = + { + { biJungleHills, biJungle, }, + { biJungle, biJungleEdge, }, + { biJungleM, biJungleEdgeM, }, + } ; + static struct + { + BiomeLevels * Biomes; + size_t Count; + } BiomeGroups[] = + { + { bgOcean, ARRAYCOUNT(bgOcean), }, + { bgOcean, ARRAYCOUNT(bgOcean), }, + { bgFrozen, ARRAYCOUNT(bgFrozen), }, + { bgFrozen, ARRAYCOUNT(bgFrozen), }, + { bgTemperate, ARRAYCOUNT(bgTemperate), }, + { bgTemperate, ARRAYCOUNT(bgTemperate), }, + { bgConifers, ARRAYCOUNT(bgConifers), }, + { bgConifers, ARRAYCOUNT(bgConifers), }, + { bgWarm, ARRAYCOUNT(bgWarm), }, + { bgWarm, ARRAYCOUNT(bgWarm), }, + { bgMesa, ARRAYCOUNT(bgMesa), }, + { bgDenseTrees, ARRAYCOUNT(bgDenseTrees), }, + } ; + size_t Group = a_BiomeGroup % ARRAYCOUNT(BiomeGroups); + size_t Index = a_BiomeIdx % BiomeGroups[Group].Count; + return (a_DistLevel > 0) ? BiomeGroups[Group].Biomes[Index].InnerBiome : BiomeGroups[Group].Biomes[Index].OuterBiome; +} + + + + + +void cBioGenTwoLevel::InitializeBiomeGen(cIniFile & a_IniFile) +{ + // TODO: Read these from a file + m_VoronoiLarge.SetCellSize(1024); + m_VoronoiSmall.SetCellSize(128); + m_DistortX.AddOctave(0.01f, 16); + m_DistortX.AddOctave(0.005f, 8); + m_DistortX.AddOctave(0.0025f, 4); + m_DistortZ.AddOctave(0.01f, 16); + m_DistortZ.AddOctave(0.005f, 8); + m_DistortZ.AddOctave(0.0025f, 4); +} + + + + diff --git a/src/Generating/BioGen.h b/src/Generating/BioGen.h new file mode 100644 index 000000000..8bd460d8f --- /dev/null +++ b/src/Generating/BioGen.h @@ -0,0 +1,279 @@ + +// BioGen.h + +/* +Interfaces to the various biome generators: + - cBioGenConstant + - cBioGenCheckerboard + - cBioGenDistortedVoronoi +*/ + + + + + +#pragma once + +#include "ComposableGenerator.h" +#include "../Noise.h" +#include "../VoronoiMap.h" + + + + + +class cBioGenConstant : + public cBiomeGen +{ +public: + cBioGenConstant(void) : m_Biome(biPlains) {} + +protected: + + EMCSBiome m_Biome; + + // cBiomeGen overrides: + virtual void GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override; + virtual void InitializeBiomeGen(cIniFile & a_IniFile) override; +} ; + + + + + +/// A simple cache that stores N most recently generated chunks' biomes; N being settable upon creation +class cBioGenCache : + public cBiomeGen +{ + typedef cBiomeGen super; + +public: + cBioGenCache(cBiomeGen * a_BioGenToCache, int a_CacheSize); // Doesn't take ownership of a_BioGenToCache + ~cBioGenCache(); + +protected: + + cBiomeGen * m_BioGenToCache; + + struct sCacheData + { + int m_ChunkX; + int m_ChunkZ; + cChunkDef::BiomeMap m_BiomeMap; + } ; + + // To avoid moving large amounts of data for the MRU behavior, we MRU-ize indices to an array of the actual data + int m_CacheSize; + int * m_CacheOrder; // MRU-ized order, indices into m_CacheData array + sCacheData * m_CacheData; // m_CacheData[m_CacheOrder[0]] is the most recently used + + // Cache statistics + int m_NumHits; + int m_NumMisses; + int m_TotalChain; // Number of cache items walked to get to a hit (only added for hits) + + virtual void GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override; + virtual void InitializeBiomeGen(cIniFile & a_IniFile) override; +} ; + + + + + +/// Base class for generators that use a list of available biomes. This class takes care of the list. +class cBiomeGenList : + public cBiomeGen +{ + typedef cBiomeGen super; + +protected: + // List of biomes that the generator is allowed to generate: + typedef std::vector<EMCSBiome> EMCSBiomes; + EMCSBiomes m_Biomes; + int m_BiomesCount; // Pulled out of m_Biomes for faster access + + /// Parses the INI file setting string into m_Biomes. + void InitializeBiomes(const AString & a_Biomes); +} ; + + + + + +class cBioGenCheckerboard : + public cBiomeGenList +{ + typedef cBiomeGenList super; + +protected: + int m_BiomeSize; + + // cBiomeGen overrides: + virtual void GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override; + virtual void InitializeBiomeGen(cIniFile & a_IniFile) override; +} ; + + + + + +class cBioGenVoronoi : + public cBiomeGenList +{ + typedef cBiomeGenList super; + +public: + cBioGenVoronoi(int a_Seed) : + m_Voronoi(a_Seed) + { + } + +protected: + cVoronoiMap m_Voronoi; + + // cBiomeGen overrides: + virtual void GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override; + virtual void InitializeBiomeGen(cIniFile & a_IniFile) override; + + EMCSBiome VoronoiBiome(int a_BlockX, int a_BlockZ); +} ; + + + + + +class cBioGenDistortedVoronoi : + public cBiomeGenList +{ + typedef cBiomeGenList super; + +public: + cBioGenDistortedVoronoi(int a_Seed) : + m_Noise(a_Seed), + m_Voronoi(a_Seed), + m_CellSize(0) + { + } + +protected: + /// Noise used for the distortion + cNoise m_Noise; + + /// The underlying Voronoi map of the biomes + cVoronoiMap m_Voronoi; + + /// Size of the Voronoi cells, also used for distortion amplitude + int m_CellSize; + + // cBiomeGen overrides: + virtual void GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override; + virtual void InitializeBiomeGen(cIniFile & a_IniFile) override; + + /// Distorts the coords using a Perlin-like noise + void Distort(int a_BlockX, int a_BlockZ, int & a_DistortedX, int & a_DistortedZ); +} ; + + + + + +class cBioGenMultiStepMap : + public cBiomeGen +{ + typedef cBiomeGen super; + +public: + cBioGenMultiStepMap(int a_Seed); + +protected: + // Noises used for composing the perlin-noise: + cNoise m_Noise1; + cNoise m_Noise2; + cNoise m_Noise3; + cNoise m_Noise4; + cNoise m_Noise5; + cNoise m_Noise6; + + int m_Seed; + int m_OceanCellSize; + int m_MushroomIslandSize; + int m_RiverCellSize; + double m_RiverWidthThreshold; + float m_LandBiomesSize; + + typedef int IntMap[17 * 17]; // x + 17 * z, expected trimmed into [0..255] range + typedef double DblMap[17 * 17]; // x + 17 * z, expected trimmed into [0..1] range + + // cBiomeGen overrides: + virtual void GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override; + virtual void InitializeBiomeGen(cIniFile & a_IniFile) override; + + /** Step 1: Decides between ocean, land and mushroom, using a DistVoronoi with special conditions and post-processing for mushroom islands + Sets biomes to biOcean, -1 (i.e. land), biMushroomIsland or biMushroomShore + */ + void DecideOceanLandMushroom(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap); + + /** Step 2: Add rivers to the land + Flips some "-1" biomes into biRiver + */ + void AddRivers(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap); + + /** Step 3: Decide land biomes using a temperature / humidity map; freeze ocean / river in low temperatures. + Flips all remaining "-1" biomes into land biomes. Also flips some biOcean and biRiver into biFrozenOcean, biFrozenRiver, based on temp map. + */ + void ApplyTemperatureHumidity(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap); + + /// Distorts the coords using a Perlin-like noise, with a specified cell-size + void Distort(int a_BlockX, int a_BlockZ, int & a_DistortedX, int & a_DistortedZ, int a_CellSize); + + /// Builds two Perlin-noise maps, one for temperature, the other for humidity. Trims both into [0..255] range + void BuildTemperatureHumidityMaps(int a_ChunkX, int a_ChunkZ, IntMap & a_TemperatureMap, IntMap & a_HumidityMap); + + /// Flips all remaining "-1" biomes into land biomes using the two maps + void DecideLandBiomes(cChunkDef::BiomeMap & a_BiomeMap, const IntMap & a_TemperatureMap, const IntMap & a_HumidityMap); + + /// Flips biOcean and biRiver into biFrozenOcean and biFrozenRiver if the temperature is too low + void FreezeWaterBiomes(cChunkDef::BiomeMap & a_BiomeMap, const IntMap & a_TemperatureMap); +} ; + + + + + +class cBioGenTwoLevel : + public cBiomeGen +{ + typedef cBiomeGen super; + +public: + cBioGenTwoLevel(int a_Seed); + +protected: + /// The Voronoi map that decides the groups of biomes + cVoronoiMap m_VoronoiLarge; + + /// The Voronoi map that decides biomes inside individual biome groups + cVoronoiMap m_VoronoiSmall; + + /// The noise used to distort the input X coord + cPerlinNoise m_DistortX; + + /// The noise used to distort the inupt Z coord + cPerlinNoise m_DistortZ; + + cNoise m_Noise; + + + // cBiomeGen overrides: + virtual void GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override; + virtual void InitializeBiomeGen(cIniFile & a_IniFile) override; + + /// Selects biome from the specified biome group, based on the specified index. + /// Note that both params may overflow + /// a_DistLevel is either 0 or 1; zero when it is at the edge of the small Voronoi cell, 1 near the center + EMCSBiome SelectBiome(int a_BiomeGroup, int a_BiomeIdx, int a_DistLevel); +} ; + + + + diff --git a/src/Generating/Caves.cpp b/src/Generating/Caves.cpp new file mode 100644 index 000000000..df45bb4c2 --- /dev/null +++ b/src/Generating/Caves.cpp @@ -0,0 +1,968 @@ + +// Caves.cpp + +// Implements the various cave structure generators: +// - cStructGenWormNestCaves +// - cStructGenDualRidgeCaves +// - cStructGenMarbleCaves +// - cStructGenNetherCaves + +/* +WormNestCave generator: +Caves are generated in "nests" - groups of tunnels generated from a single point. +For each chunk, all the nests that could intersect it are generated. +For each nest, first the schematic structure is generated (tunnel from ... to ..., branch, tunnel2 from ... to ...) +Then each tunnel is randomized by inserting points in between its ends. +Finally each tunnel is smoothed and Bresenham-3D-ed so that it is a collection of spheres with their centers next to each other. +When the tunnels are ready, they are simply carved into the chunk, one by one. +To optimize, each tunnel keeps track of its bounding box, so that it can be skipped for chunks that don't intersect it. + +MarbleCaves generator: +For each voxel a 3D noise function is evaluated, if the value crosses a boundary, the voxel is dug out, otherwise it is kept. +Problem with this is the amount of CPU work needed for each chunk. +Also the overall shape of the generated holes is unsatisfactory - there are whole "sheets" of holes in the ground. + +DualRidgeCaves generator: +Instead of evaluating a single noise function, two different noise functions are multiplied. This produces +regular tunnels instead of sheets. However due to the sheer amount of CPU work needed, the noise functions need to be +reduced in complexity in order for this generator to be useful, so the caves' shapes are "bubbly" at best. +*/ + +#include "Globals.h" +#include "Caves.h" + + + + + +/// How many nests in each direction are generated for a given chunk. Must be an even number +#define NEIGHBORHOOD_SIZE 8 + + + + + +const int MIN_RADIUS = 3; +const int MAX_RADIUS = 8; + + + + + +struct cCaveDefPoint +{ + int m_BlockX; + int m_BlockY; + int m_BlockZ; + int m_Radius; + + cCaveDefPoint(int a_BlockX, int a_BlockY, int a_BlockZ, int a_Radius) : + m_BlockX(a_BlockX), + m_BlockY(a_BlockY), + m_BlockZ(a_BlockZ), + m_Radius(a_Radius) + { + } +} ; + +typedef std::vector<cCaveDefPoint> cCaveDefPoints; + + + + + +/// A single non-branching tunnel of a WormNestCave +class cCaveTunnel +{ + // The bounding box, including the radii around defpoints: + int m_MinBlockX, m_MaxBlockX; + int m_MinBlockY, m_MaxBlockY; + int m_MinBlockZ, m_MaxBlockZ; + + /// Generates the shaping defpoints for the ravine, based on the ravine block coords and noise + void Randomize(cNoise & a_Noise); + + /// Refines (adds and smooths) defpoints from a_Src into a_Dst; returns false if no refinement possible (segments too short) + bool RefineDefPoints(const cCaveDefPoints & a_Src, cCaveDefPoints & a_Dst); + + /// Does rounds of smoothing, two passes of RefineDefPoints(), as long as they return true + void Smooth(void); + + /// Linearly interpolates the points so that the maximum distance between two neighbors is max 1 block + void FinishLinear(void); + + /// Calculates the bounding box of the points present + void CalcBoundingBox(void); + +public: + cCaveDefPoints m_Points; + + cCaveTunnel( + int a_BlockStartX, int a_BlockStartY, int a_BlockStartZ, int a_StartRadius, + int a_BlockEndX, int a_BlockEndY, int a_BlockEndZ, int a_EndRadius, + cNoise & a_Noise + ); + + /// Carves the tunnel into the chunk specified + void ProcessChunk( + int a_ChunkX, int a_ChunkZ, + cChunkDef::BlockTypes & a_BlockTypes, + cChunkDef::HeightMap & a_HeightMap + ); + + #ifdef _DEBUG + AString ExportAsSVG(int a_Color, int a_OffsetX, int a_OffsetZ) const; + #endif // _DEBUG +} ; + +typedef std::vector<cCaveTunnel *> cCaveTunnels; + + + + + +/// A collection of connected tunnels, possibly branching. +class cStructGenWormNestCaves::cCaveSystem +{ +public: + // The generating block position; is read directly in cStructGenWormNestCaves::GetCavesForChunk() + int m_BlockX; + int m_BlockZ; + + cCaveSystem(int a_BlockX, int a_BlockZ, int a_MaxOffset, int a_Size, cNoise & a_Noise); + ~cCaveSystem(); + + /// Carves the cave system into the chunk specified + void ProcessChunk( + int a_ChunkX, int a_ChunkZ, + cChunkDef::BlockTypes & a_BlockTypes, + cChunkDef::HeightMap & a_HeightMap + ); + + #ifdef _DEBUG + AString ExportAsSVG(int a_Color, int a_OffsetX, int a_OffsetZ) const; + #endif // _DEBUG + +protected: + int m_Size; + cCaveTunnels m_Tunnels; + + void Clear(void); + + /// Generates a_Segment successive tunnels, with possible branches. Generates the same output for the same [x, y, z, a_Segments] + void GenerateTunnelsFromPoint( + int a_OriginX, int a_OriginY, int a_OriginZ, + cNoise & a_Noise, int a_Segments + ); + + /// Returns a radius based on the location provided. + int GetRadius(cNoise & a_Noise, int a_OriginX, int a_OriginY, int a_OriginZ); +} ; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cCaveTunnel: + +cCaveTunnel::cCaveTunnel( + int a_BlockStartX, int a_BlockStartY, int a_BlockStartZ, int a_StartRadius, + int a_BlockEndX, int a_BlockEndY, int a_BlockEndZ, int a_EndRadius, + cNoise & a_Noise +) +{ + m_Points.push_back(cCaveDefPoint(a_BlockStartX, a_BlockStartY, a_BlockStartZ, a_StartRadius)); + m_Points.push_back(cCaveDefPoint(a_BlockEndX, a_BlockEndY, a_BlockEndZ, a_EndRadius)); + + if ((a_BlockStartY <= 0) && (a_BlockEndY <= 0)) + { + // Don't bother detailing this cave, it's under the world anyway + return; + } + + Randomize(a_Noise); + Smooth(); + + // We know that the linear finishing won't affect the bounding box, so let's calculate it now, as we have less data: + CalcBoundingBox(); + + FinishLinear(); +} + + + + + +void cCaveTunnel::Randomize(cNoise & a_Noise) +{ + // Repeat 4 times: + for (int i = 0; i < 4; i++) + { + // For each already present point, insert a point in between it and its predecessor, shifted randomly. + int PrevX = m_Points.front().m_BlockX; + int PrevY = m_Points.front().m_BlockY; + int PrevZ = m_Points.front().m_BlockZ; + int PrevR = m_Points.front().m_Radius; + cCaveDefPoints Pts; + Pts.reserve(m_Points.size() * 2 + 1); + Pts.push_back(m_Points.front()); + for (cCaveDefPoints::const_iterator itr = m_Points.begin() + 1, end = m_Points.end(); itr != end; ++itr) + { + int Random = a_Noise.IntNoise3DInt(PrevX, PrevY, PrevZ + i) / 11; + int len = (PrevX - itr->m_BlockX) * (PrevX - itr->m_BlockX); + len += (PrevY - itr->m_BlockY) * (PrevY - itr->m_BlockY); + len += (PrevZ - itr->m_BlockZ) * (PrevZ - itr->m_BlockZ); + len = 3 * (int)sqrt((double)len) / 4; + int Rad = std::min(MAX_RADIUS, std::max(MIN_RADIUS, (PrevR + itr->m_Radius) / 2 + (Random % 3) - 1)); + Random /= 4; + int x = (itr->m_BlockX + PrevX) / 2 + (Random % (len + 1) - len / 2); + Random /= 256; + int y = (itr->m_BlockY + PrevY) / 2 + (Random % (len / 2 + 1) - len / 4); + Random /= 256; + int z = (itr->m_BlockZ + PrevZ) / 2 + (Random % (len + 1) - len / 2); + Pts.push_back(cCaveDefPoint(x, y, z, Rad)); + Pts.push_back(*itr); + PrevX = itr->m_BlockX; + PrevY = itr->m_BlockY; + PrevZ = itr->m_BlockZ; + PrevR = itr->m_Radius; + } + std::swap(Pts, m_Points); + } +} + + + + + +bool cCaveTunnel::RefineDefPoints(const cCaveDefPoints & a_Src, cCaveDefPoints & a_Dst) +{ + // Smoothing: for each line segment, add points on its 1/4 lengths + bool res = false; + int Num = a_Src.size() - 2; // this many intermediary points + a_Dst.clear(); + a_Dst.reserve(Num * 2 + 2); + cCaveDefPoints::const_iterator itr = a_Src.begin() + 1; + a_Dst.push_back(a_Src.front()); + int PrevX = a_Src.front().m_BlockX; + int PrevY = a_Src.front().m_BlockY; + int PrevZ = a_Src.front().m_BlockZ; + int PrevR = a_Src.front().m_Radius; + for (int i = 0; i <= Num; ++i, ++itr) + { + int dx = itr->m_BlockX - PrevX; + int dy = itr->m_BlockY - PrevY; + int dz = itr->m_BlockZ - PrevZ; + if (abs(dx) + abs(dz) + abs(dy) < 6) + { + // Too short a segment to smooth-subdivide into quarters + PrevX = itr->m_BlockX; + PrevY = itr->m_BlockY; + PrevZ = itr->m_BlockZ; + PrevR = itr->m_Radius; + continue; + } + int dr = itr->m_Radius - PrevR; + int Rad1 = std::max(PrevR + 1 * dr / 4, 1); + int Rad2 = std::max(PrevR + 3 * dr / 4, 1); + a_Dst.push_back(cCaveDefPoint(PrevX + 1 * dx / 4, PrevY + 1 * dy / 4, PrevZ + 1 * dz / 4, Rad1)); + a_Dst.push_back(cCaveDefPoint(PrevX + 3 * dx / 4, PrevY + 3 * dy / 4, PrevZ + 3 * dz / 4, Rad2)); + PrevX = itr->m_BlockX; + PrevY = itr->m_BlockY; + PrevZ = itr->m_BlockZ; + PrevR = itr->m_Radius; + res = true; + } + a_Dst.push_back(a_Src.back()); + return res && (a_Src.size() < a_Dst.size()); +} + + + + + +void cCaveTunnel::Smooth(void) +{ + cCaveDefPoints Pts; + while (true) + { + if (!RefineDefPoints(m_Points, Pts)) + { + std::swap(Pts, m_Points); + return; + } + if (!RefineDefPoints(Pts, m_Points)) + { + return; + } + } +} + + + + + +void cCaveTunnel::FinishLinear(void) +{ + // For each segment, use Bresenham's 3D line algorithm to draw a "line" of defpoints + cCaveDefPoints Pts; + std::swap(Pts, m_Points); + + m_Points.reserve(Pts.size() * 3); + int PrevX = Pts.front().m_BlockX; + int PrevY = Pts.front().m_BlockY; + int PrevZ = Pts.front().m_BlockZ; + for (cCaveDefPoints::const_iterator itr = Pts.begin() + 1, end = Pts.end(); itr != end; ++itr) + { + int x1 = itr->m_BlockX; + int y1 = itr->m_BlockY; + int z1 = itr->m_BlockZ; + int dx = abs(x1 - PrevX); + int dy = abs(y1 - PrevY); + int dz = abs(z1 - PrevZ); + int sx = (PrevX < x1) ? 1 : -1; + int sy = (PrevY < y1) ? 1 : -1; + int sz = (PrevZ < z1) ? 1 : -1; + int R = itr->m_Radius; + + if (dx >= std::max(dy, dz)) // x dominant + { + int yd = dy - dx / 2; + int zd = dz - dx / 2; + + while (true) + { + m_Points.push_back(cCaveDefPoint(PrevX, PrevY, PrevZ, R)); + + if (PrevX == x1) + { + break; + } + + if (yd >= 0) // move along y + { + PrevY += sy; + yd -= dx; + } + + if (zd >= 0) // move along z + { + PrevZ += sz; + zd -= dx; + } + + // move along x + PrevX += sx; + yd += dy; + zd += dz; + } + } + else if (dy >= std::max(dx, dz)) // y dominant + { + int xd = dx - dy / 2; + int zd = dz - dy / 2; + + while (true) + { + m_Points.push_back(cCaveDefPoint(PrevX, PrevY, PrevZ, R)); + + if (PrevY == y1) + { + break; + } + + if (xd >= 0) // move along x + { + PrevX += sx; + xd -= dy; + } + + if (zd >= 0) // move along z + { + PrevZ += sz; + zd -= dy; + } + + // move along y + PrevY += sy; + xd += dx; + zd += dz; + } + } + else + { + // z dominant + ASSERT(dz >= std::max(dx, dy)); + int xd = dx - dz / 2; + int yd = dy - dz / 2; + + while (true) + { + m_Points.push_back(cCaveDefPoint(PrevX, PrevY, PrevZ, R)); + + if (PrevZ == z1) + { + break; + } + + if (xd >= 0) // move along x + { + PrevX += sx; + xd -= dz; + } + + if (yd >= 0) // move along y + { + PrevY += sy; + yd -= dz; + } + + // move along z + PrevZ += sz; + xd += dx; + yd += dy; + } + } // if (which dimension is dominant) + } // for itr +} + + + + + +void cCaveTunnel::CalcBoundingBox(void) +{ + m_MinBlockX = m_MaxBlockX = m_Points.front().m_BlockX; + m_MinBlockY = m_MaxBlockY = m_Points.front().m_BlockY; + m_MinBlockZ = m_MaxBlockZ = m_Points.front().m_BlockZ; + for (cCaveDefPoints::const_iterator itr = m_Points.begin() + 1, end = m_Points.end(); itr != end; ++itr) + { + m_MinBlockX = std::min(m_MinBlockX, itr->m_BlockX - itr->m_Radius); + m_MaxBlockX = std::max(m_MaxBlockX, itr->m_BlockX + itr->m_Radius); + m_MinBlockY = std::min(m_MinBlockY, itr->m_BlockY - itr->m_Radius); + m_MaxBlockY = std::max(m_MaxBlockY, itr->m_BlockY + itr->m_Radius); + m_MinBlockZ = std::min(m_MinBlockZ, itr->m_BlockZ - itr->m_Radius); + m_MaxBlockZ = std::max(m_MaxBlockZ, itr->m_BlockZ + itr->m_Radius); + } // for itr - m_Points[] +} + + + + + +void cCaveTunnel::ProcessChunk( + int a_ChunkX, int a_ChunkZ, + cChunkDef::BlockTypes & a_BlockTypes, + cChunkDef::HeightMap & a_HeightMap +) +{ + int BaseX = a_ChunkX * cChunkDef::Width; + int BaseZ = a_ChunkZ * cChunkDef::Width; + if ( + (BaseX > m_MaxBlockX) || (BaseX + cChunkDef::Width < m_MinBlockX) || + (BaseZ > m_MaxBlockZ) || (BaseZ + cChunkDef::Width < m_MinBlockZ) + ) + { + // Tunnel does not intersect the chunk at all, bail out + return; + } + + int BlockStartX = a_ChunkX * cChunkDef::Width; + int BlockStartZ = a_ChunkZ * cChunkDef::Width; + int BlockEndX = BlockStartX + cChunkDef::Width; + int BlockEndZ = BlockStartZ + cChunkDef::Width; + for (cCaveDefPoints::const_iterator itr = m_Points.begin(), end = m_Points.end(); itr != end; ++itr) + { + if ( + (itr->m_BlockX + itr->m_Radius < BlockStartX) || + (itr->m_BlockX - itr->m_Radius > BlockEndX) || + (itr->m_BlockZ + itr->m_Radius < BlockStartZ) || + (itr->m_BlockZ - itr->m_Radius > BlockEndZ) + ) + { + // Cannot intersect, bail out early + continue; + } + + // Carve out a sphere around the xyz point, m_Radius in diameter; skip 3/7 off the top and bottom: + int DifX = itr->m_BlockX - BlockStartX; // substitution for faster calc + int DifY = itr->m_BlockY; + int DifZ = itr->m_BlockZ - BlockStartZ; // substitution for faster calc + int Bottom = std::max(itr->m_BlockY - 3 * itr->m_Radius / 7, 1); + int Top = std::min(itr->m_BlockY + 3 * itr->m_Radius / 7, (int)(cChunkDef::Height)); + int SqRad = itr->m_Radius * itr->m_Radius; + for (int z = 0; z < cChunkDef::Width; z++) for (int x = 0; x < cChunkDef::Width; x++) + { + for (int y = Bottom; y <= Top; y++) + { + int SqDist = (DifX - x) * (DifX - x) + (DifY - y) * (DifY - y) + (DifZ - z) * (DifZ - z); + if (4 * SqDist <= SqRad) + { + switch (cChunkDef::GetBlock(a_BlockTypes, x, y, z)) + { + // Only carve out these specific block types + case E_BLOCK_DIRT: + case E_BLOCK_GRASS: + case E_BLOCK_STONE: + case E_BLOCK_COBBLESTONE: + case E_BLOCK_GRAVEL: + case E_BLOCK_SAND: + case E_BLOCK_SANDSTONE: + case E_BLOCK_NETHERRACK: + case E_BLOCK_COAL_ORE: + case E_BLOCK_IRON_ORE: + case E_BLOCK_GOLD_ORE: + case E_BLOCK_DIAMOND_ORE: + case E_BLOCK_REDSTONE_ORE: + case E_BLOCK_REDSTONE_ORE_GLOWING: + { + cChunkDef::SetBlock(a_BlockTypes, x, y, z, E_BLOCK_AIR); + break; + } + default: break; + } + } + } // for y + } // for x, z + } // for itr - m_Points[] + + /* + #ifdef _DEBUG + // For debugging purposes, outline the shape of the cave using glowstone, *after* carving the entire cave: + for (cCaveDefPoints::const_iterator itr = m_Points.begin(), end = m_Points.end(); itr != end; ++itr) + { + int DifX = itr->m_BlockX - BlockStartX; // substitution for faster calc + int DifZ = itr->m_BlockZ - BlockStartZ; // substitution for faster calc + if ( + (DifX >= 0) && (DifX < cChunkDef::Width) && + (itr->m_BlockY > 0) && (itr->m_BlockY < cChunkDef::Height) && + (DifZ >= 0) && (DifZ < cChunkDef::Width) + ) + { + cChunkDef::SetBlock(a_BlockTypes, DifX, itr->m_BlockY, DifZ, E_BLOCK_GLOWSTONE); + } + } // for itr - m_Points[] + #endif // _DEBUG + //*/ +} + + + + + +#ifdef _DEBUG +AString cCaveTunnel::ExportAsSVG(int a_Color, int a_OffsetX, int a_OffsetZ) const +{ + AString SVG; + SVG.reserve(m_Points.size() * 20 + 200); + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#%06x;stroke-width:1px;\"\nd=\"", a_Color); + char Prefix = 'M'; // The first point needs "M" prefix, all the others need "L" + for (cCaveDefPoints::const_iterator itr = m_Points.begin(); itr != m_Points.end(); ++itr) + { + AppendPrintf(SVG, "%c %d,%d ", Prefix, a_OffsetX + itr->m_BlockX, a_OffsetZ + itr->m_BlockZ); + Prefix = 'L'; + } + SVG.append("\"/>\n"); + return SVG; +} +#endif // _DEBUG + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenWormNestCaves::cCaveSystem: + +cStructGenWormNestCaves::cCaveSystem::cCaveSystem(int a_BlockX, int a_BlockZ, int a_MaxOffset, int a_Size, cNoise & a_Noise) : + m_BlockX(a_BlockX), + m_BlockZ(a_BlockZ), + m_Size(a_Size) +{ + int Num = 1 + a_Noise.IntNoise2DInt(a_BlockX, a_BlockZ) % 3; + for (int i = 0; i < Num; i++) + { + int OriginX = a_BlockX + (a_Noise.IntNoise3DInt(13 * a_BlockX, 17 * a_BlockZ, 11 * i) / 19) % a_MaxOffset; + int OriginZ = a_BlockZ + (a_Noise.IntNoise3DInt(17 * a_BlockX, 13 * a_BlockZ, 11 * i) / 23) % a_MaxOffset; + int OriginY = 20 + (a_Noise.IntNoise3DInt(19 * a_BlockX, 13 * a_BlockZ, 11 * i) / 17) % 20; + + // Generate three branches from the origin point: + // The tunnels generated depend on X, Y, Z and Branches, + // for the same set of numbers it generates the same offsets! + // That's why we add a +1 to X in the third line + GenerateTunnelsFromPoint(OriginX, OriginY, OriginZ, a_Noise, 3); + GenerateTunnelsFromPoint(OriginX, OriginY, OriginZ, a_Noise, 2); + GenerateTunnelsFromPoint(OriginX + 1, OriginY, OriginZ, a_Noise, 3); + } +} + + + + + +cStructGenWormNestCaves::cCaveSystem::~cCaveSystem() +{ + Clear(); +} + + + + + + +void cStructGenWormNestCaves::cCaveSystem::ProcessChunk( + int a_ChunkX, int a_ChunkZ, + cChunkDef::BlockTypes & a_BlockTypes, + cChunkDef::HeightMap & a_HeightMap +) +{ + for (cCaveTunnels::const_iterator itr = m_Tunnels.begin(), end = m_Tunnels.end(); itr != end; ++itr) + { + (*itr)->ProcessChunk(a_ChunkX, a_ChunkZ, a_BlockTypes, a_HeightMap); + } // for itr - m_Tunnels[] +} + + + + + +#ifdef _DEBUG +AString cStructGenWormNestCaves::cCaveSystem::ExportAsSVG(int a_Color, int a_OffsetX, int a_OffsetZ) const +{ + AString SVG; + SVG.reserve(512 * 1024); + for (cCaveTunnels::const_iterator itr = m_Tunnels.begin(), end = m_Tunnels.end(); itr != end; ++itr) + { + SVG.append((*itr)->ExportAsSVG(a_Color, a_OffsetX, a_OffsetZ)); + } // for itr - m_Tunnels[] + + // Base point highlight: + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#ff0000;stroke-width:1px;\"\nd=\"M %d,%d L %d,%d\"/>\n", + a_OffsetX + m_BlockX - 5, a_OffsetZ + m_BlockZ, a_OffsetX + m_BlockX + 5, a_OffsetZ + m_BlockZ + ); + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#ff0000;stroke-width:1px;\"\nd=\"M %d,%d L %d,%d\"/>\n", + a_OffsetX + m_BlockX, a_OffsetZ + m_BlockZ - 5, a_OffsetX + m_BlockX, a_OffsetZ + m_BlockZ + 5 + ); + + // A gray line from the base point to the first point of the ravine, for identification: + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#cfcfcf;stroke-width:1px;\"\nd=\"M %d,%d L %d,%d\"/>\n", + a_OffsetX + m_BlockX, a_OffsetZ + m_BlockZ, + a_OffsetX + m_Tunnels.front()->m_Points.front().m_BlockX, + a_OffsetZ + m_Tunnels.front()->m_Points.front().m_BlockZ + ); + + // Offset guides: + if (a_OffsetX > 0) + { + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#0000ff;stroke-width:1px;\"\nd=\"M %d,0 L %d,1024\"/>\n", + a_OffsetX, a_OffsetX + ); + } + if (a_OffsetZ > 0) + { + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#0000ff;stroke-width:1px;\"\nd=\"M 0,%d L 1024,%d\"/>\n", + a_OffsetZ, a_OffsetZ + ); + } + + return SVG; +} +#endif // _DEBUG + + + + + +void cStructGenWormNestCaves::cCaveSystem::Clear(void) +{ + for (cCaveTunnels::const_iterator itr = m_Tunnels.begin(), end = m_Tunnels.end(); itr != end; ++itr) + { + delete *itr; + } + m_Tunnels.clear(); +} + + + + + +void cStructGenWormNestCaves::cCaveSystem::GenerateTunnelsFromPoint( + int a_OriginX, int a_OriginY, int a_OriginZ, + cNoise & a_Noise, int a_NumSegments +) +{ + int DoubleSize = m_Size * 2; + int Radius = GetRadius(a_Noise, a_OriginX + a_OriginY, a_OriginY + a_OriginZ, a_OriginZ + a_OriginX); + for (int i = a_NumSegments - 1; i >= 0; --i) + { + int EndX = a_OriginX + (((a_Noise.IntNoise3DInt(a_OriginX, a_OriginY, a_OriginZ + 11 * a_NumSegments) / 7) % DoubleSize) - m_Size) / 2; + int EndY = a_OriginY + (((a_Noise.IntNoise3DInt(a_OriginY, 13 * a_NumSegments, a_OriginZ + a_OriginX) / 7) % DoubleSize) - m_Size) / 4; + int EndZ = a_OriginZ + (((a_Noise.IntNoise3DInt(a_OriginZ + 17 * a_NumSegments, a_OriginX, a_OriginY) / 7) % DoubleSize) - m_Size) / 2; + int EndR = GetRadius(a_Noise, a_OriginX + 7 * i, a_OriginY + 11 * i, a_OriginZ + a_OriginX); + m_Tunnels.push_back(new cCaveTunnel(a_OriginX, a_OriginY, a_OriginZ, Radius, EndX, EndY, EndZ, EndR, a_Noise)); + GenerateTunnelsFromPoint(EndX, EndY, EndZ, a_Noise, i); + a_OriginX = EndX; + a_OriginY = EndY; + a_OriginZ = EndZ; + Radius = EndR; + } // for i - a_NumSegments +} + + + + + +int cStructGenWormNestCaves::cCaveSystem::GetRadius(cNoise & a_Noise, int a_OriginX, int a_OriginY, int a_OriginZ) +{ + // Instead of a flat distribution noise function, we need to shape it, so that most caves are smallish and only a few select are large + int rnd = a_Noise.IntNoise3DInt(a_OriginX, a_OriginY, a_OriginZ) / 11; + /* + // Not good enough: + // The algorithm of choice: emulate gauss-distribution noise by adding 3 flat noises, then fold it in half using absolute value. + // To save on processing, use one random value and extract 3 bytes to be separately added as the gaussian noise + int sum = (rnd & 0xff) + ((rnd >> 8) & 0xff) + ((rnd >> 16) & 0xff); + // sum is now a gaussian-distribution noise within [0 .. 767], with center at 384. + // We want mapping 384 -> 3, 0 -> 19, 768 -> 19, so divide by 24 to get [0 .. 31] with center at 16, then use abs() to fold around the center + int res = 3 + abs((sum / 24) - 16); + */ + + // Algorithm of choice: random value in the range of zero to random value - heavily towards zero + int res = MIN_RADIUS + (rnd >> 8) % ((rnd % (MAX_RADIUS - MIN_RADIUS)) + 1); + return res; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenWormNestCaves: + +cStructGenWormNestCaves::~cStructGenWormNestCaves() +{ + ClearCache(); +} + + + + + +void cStructGenWormNestCaves::ClearCache(void) +{ + for (cCaveSystems::const_iterator itr = m_Cache.begin(), end = m_Cache.end(); itr != end; ++itr) + { + delete *itr; + } // for itr - m_Cache[] + m_Cache.clear(); +} + + + + + +void cStructGenWormNestCaves::GenStructures(cChunkDesc & a_ChunkDesc) +{ + int ChunkX = a_ChunkDesc.GetChunkX(); + int ChunkZ = a_ChunkDesc.GetChunkZ(); + cCaveSystems Caves; + GetCavesForChunk(ChunkX, ChunkZ, Caves); + for (cCaveSystems::const_iterator itr = Caves.begin(); itr != Caves.end(); ++itr) + { + (*itr)->ProcessChunk(ChunkX, ChunkZ, a_ChunkDesc.GetBlockTypes(), a_ChunkDesc.GetHeightMap()); + } // for itr - Caves[] +} + + + + + +void cStructGenWormNestCaves::GetCavesForChunk(int a_ChunkX, int a_ChunkZ, cStructGenWormNestCaves::cCaveSystems & a_Caves) +{ + int BaseX = a_ChunkX * cChunkDef::Width / m_Grid; + int BaseZ = a_ChunkZ * cChunkDef::Width / m_Grid; + if (BaseX < 0) + { + --BaseX; + } + if (BaseZ < 0) + { + --BaseZ; + } + BaseX -= NEIGHBORHOOD_SIZE / 2; + BaseZ -= NEIGHBORHOOD_SIZE / 2; + + // Walk the cache, move each cave system that we want into a_Caves: + int StartX = BaseX * m_Grid; + int EndX = (BaseX + NEIGHBORHOOD_SIZE + 1) * m_Grid; + int StartZ = BaseZ * m_Grid; + int EndZ = (BaseZ + NEIGHBORHOOD_SIZE + 1) * m_Grid; + for (cCaveSystems::iterator itr = m_Cache.begin(), end = m_Cache.end(); itr != end;) + { + if ( + ((*itr)->m_BlockX >= StartX) && ((*itr)->m_BlockX < EndX) && + ((*itr)->m_BlockZ >= StartZ) && ((*itr)->m_BlockZ < EndZ) + ) + { + // want + a_Caves.push_back(*itr); + itr = m_Cache.erase(itr); + } + else + { + // don't want + ++itr; + } + } // for itr - m_Cache[] + + for (int x = 0; x < NEIGHBORHOOD_SIZE; x++) + { + int RealX = (BaseX + x) * m_Grid; + for (int z = 0; z < NEIGHBORHOOD_SIZE; z++) + { + int RealZ = (BaseZ + z) * m_Grid; + bool Found = false; + for (cCaveSystems::const_iterator itr = a_Caves.begin(), end = a_Caves.end(); itr != end; ++itr) + { + if (((*itr)->m_BlockX == RealX) && ((*itr)->m_BlockZ == RealZ)) + { + Found = true; + break; + } + } + if (!Found) + { + a_Caves.push_back(new cCaveSystem(RealX, RealZ, m_MaxOffset, m_Size, m_Noise)); + } + } + } + + // Copy a_Caves into m_Cache to the beginning: + cCaveSystems CavesCopy(a_Caves); + m_Cache.splice(m_Cache.begin(), CavesCopy, CavesCopy.begin(), CavesCopy.end()); + + // Trim the cache if it's too long: + if (m_Cache.size() > 100) + { + cCaveSystems::iterator itr = m_Cache.begin(); + std::advance(itr, 100); + for (cCaveSystems::iterator end = m_Cache.end(); itr != end; ++itr) + { + delete *itr; + } + itr = m_Cache.begin(); + std::advance(itr, 100); + m_Cache.erase(itr, m_Cache.end()); + } + + /* + // Uncomment this block for debugging the caves' shapes in 2D using an SVG export + #ifdef _DEBUG + AString SVG; + SVG.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1024\" height = \"1024\">\n"); + SVG.reserve(2 * 1024 * 1024); + for (cCaveSystems::const_iterator itr = a_Caves.begin(), end = a_Caves.end(); itr != end; ++itr) + { + int Color = 0x10 * abs((*itr)->m_BlockX / m_Grid); + Color |= 0x1000 * abs((*itr)->m_BlockZ / m_Grid); + SVG.append((*itr)->ExportAsSVG(Color, 512, 512)); + } + SVG.append("</svg>\n"); + + AString fnam; + Printf(fnam, "wnc\\%03d_%03d.svg", a_ChunkX, a_ChunkZ); + cFile File(fnam, cFile::fmWrite); + File.Write(SVG.c_str(), SVG.size()); + #endif // _DEBUG + //*/ +} + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenMarbleCaves: + +static float GetMarbleNoise( float x, float y, float z, cNoise & a_Noise ) +{ + static const float PI_2 = 1.57079633f; + float oct1 = (a_Noise.CubicNoise3D(x * 0.1f, y * 0.1f, z * 0.1f )) * 4; + + oct1 = oct1 * oct1 * oct1; + if (oct1 < 0.f) oct1 = PI_2; + if (oct1 > PI_2) oct1 = PI_2; + + return oct1; +} + + + + + +void cStructGenMarbleCaves::GenStructures(cChunkDesc & a_ChunkDesc) +{ + cNoise Noise(m_Seed); + for (int z = 0; z < cChunkDef::Width; z++) + { + const float zz = (float)(a_ChunkDesc.GetChunkZ() * cChunkDef::Width + z); + for (int x = 0; x < cChunkDef::Width; x++) + { + const float xx = (float)(a_ChunkDesc.GetChunkX() * cChunkDef::Width + x); + + int Top = a_ChunkDesc.GetHeight(x, z); + for (int y = 1; y < Top; ++y ) + { + if (a_ChunkDesc.GetBlockType(x, y, z) != E_BLOCK_STONE) + { + continue; + } + + const float yy = (float)y; + const float WaveNoise = 1; + if (cosf(GetMarbleNoise(xx, yy * 0.5f, zz, Noise)) * fabs(cosf(yy * 0.2f + WaveNoise * 2) * 0.75f + WaveNoise) > 0.0005f) + { + a_ChunkDesc.SetBlockType(x, y, z, E_BLOCK_AIR); + } + } // for y + } // for x + } // for z +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenDualRidgeCaves: + +void cStructGenDualRidgeCaves::GenStructures(cChunkDesc & a_ChunkDesc) +{ + for (int z = 0; z < cChunkDef::Width; z++) + { + const float zz = (float)(a_ChunkDesc.GetChunkZ() * cChunkDef::Width + z) / 10; + for (int x = 0; x < cChunkDef::Width; x++) + { + const float xx = (float)(a_ChunkDesc.GetChunkX() * cChunkDef::Width + x) / 10; + + int Top = a_ChunkDesc.GetHeight(x, z); + for (int y = 1; y <= Top; ++y) + { + const float yy = (float)y / 10; + float n1 = m_Noise1.CubicNoise3D(xx, yy, zz); + float n2 = m_Noise2.CubicNoise3D(xx, yy, zz); + float n3 = m_Noise1.CubicNoise3D(xx * 4, yy * 4, zz * 4) / 4; + float n4 = m_Noise2.CubicNoise3D(xx * 4, yy * 4, zz * 4) / 4; + if ((abs(n1 + n3) * abs(n2 + n4)) > m_Threshold) + { + a_ChunkDesc.SetBlockType(x, y, z, E_BLOCK_AIR); + } + } // for y + } // for x + } // for z +} + + + + diff --git a/src/Generating/Caves.h b/src/Generating/Caves.h new file mode 100644 index 000000000..70cf6fe8c --- /dev/null +++ b/src/Generating/Caves.h @@ -0,0 +1,102 @@ + +// Caves.h + +// Interfaces to the various cave structure generators: +// - cStructGenWormNestCaves +// - cStructGenMarbleCaves +// - cStructGenNetherCaves + + + + + +#pragma once + +#include "ComposableGenerator.h" +#include "../Noise.h" + + + + + +class cStructGenMarbleCaves : + public cStructureGen +{ +public: + cStructGenMarbleCaves(int a_Seed) : m_Seed(a_Seed) {} + +protected: + + int m_Seed; + + // cStructureGen override: + virtual void GenStructures(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +class cStructGenDualRidgeCaves : + public cStructureGen +{ +public: + cStructGenDualRidgeCaves(int a_Seed, float a_Threshold) : + m_Noise1(a_Seed), + m_Noise2(2 * a_Seed + 19999), + m_Seed(a_Seed), + m_Threshold(a_Threshold) + { + } + +protected: + cNoise m_Noise1; + cNoise m_Noise2; + int m_Seed; + float m_Threshold; + + // cStructureGen override: + virtual void GenStructures(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +class cStructGenWormNestCaves : + public cStructureGen +{ +public: + cStructGenWormNestCaves(int a_Seed, int a_Size = 64, int a_Grid = 96, int a_MaxOffset = 128) : + m_Noise(a_Seed), + m_Size(a_Size), + m_Grid(a_Grid), + m_MaxOffset(a_MaxOffset) + { + } + + ~cStructGenWormNestCaves(); + +protected: + class cCaveSystem; // fwd: Caves.cpp + typedef std::list<cCaveSystem *> cCaveSystems; + + cNoise m_Noise; + int m_Size; // relative size of the cave systems' caves. Average number of blocks of each initial tunnel + int m_MaxOffset; // maximum offset of the cave nest origin from the grid cell the nest belongs to + int m_Grid; // average spacing of the nests + cCaveSystems m_Cache; + + /// Clears everything from the cache + void ClearCache(void); + + /// Returns all caves that *may* intersect the given chunk. All the caves are valid until the next call to this function. + void GetCavesForChunk(int a_ChunkX, int a_ChunkZ, cCaveSystems & a_Caves); + + // cStructGen override: + virtual void GenStructures(cChunkDesc & a_ChunkDesc) override; +} ; + + + + diff --git a/src/Generating/ChunkDesc.cpp b/src/Generating/ChunkDesc.cpp new file mode 100644 index 000000000..6050430fd --- /dev/null +++ b/src/Generating/ChunkDesc.cpp @@ -0,0 +1,605 @@ + +// ChunkDesc.cpp + +// Implements the cChunkDesc class representing the chunk description used while generating a chunk. This class is also exported to Lua for HOOK_CHUNK_GENERATING. + +#include "Globals.h" +#include "ChunkDesc.h" +#include "../BlockArea.h" +#include "../Cuboid.h" +#include "../Noise.h" +#include "../BlockEntities/BlockEntity.h" + + + + + +cChunkDesc::cChunkDesc(int a_ChunkX, int a_ChunkZ) : + m_ChunkX(a_ChunkX), + m_ChunkZ(a_ChunkZ), + m_bUseDefaultBiomes(true), + m_bUseDefaultHeight(true), + m_bUseDefaultComposition(true), + m_bUseDefaultStructures(true), + m_bUseDefaultFinish(true) +{ + m_BlockArea.Create(cChunkDef::Width, cChunkDef::Height, cChunkDef::Width); + /* + memset(m_BlockTypes, 0, sizeof(cChunkDef::BlockTypes)); + memset(m_BlockMeta, 0, sizeof(cChunkDef::BlockNibbles)); + */ + memset(m_BiomeMap, 0, sizeof(cChunkDef::BiomeMap)); + memset(m_HeightMap, 0, sizeof(cChunkDef::HeightMap)); +} + + + + + +cChunkDesc::~cChunkDesc() +{ + // Nothing needed yet +} + + + + + +void cChunkDesc::SetChunkCoords(int a_ChunkX, int a_ChunkZ) +{ + m_ChunkX = a_ChunkX; + m_ChunkZ = a_ChunkZ; +} + + + + + +void cChunkDesc::FillBlocks(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) +{ + m_BlockArea.Fill(cBlockArea::baTypes | cBlockArea::baMetas, a_BlockType, a_BlockMeta); +} + + + + + +void cChunkDesc::SetBlockTypeMeta(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) +{ + m_BlockArea.SetRelBlockTypeMeta(a_RelX, a_RelY, a_RelZ, a_BlockType, a_BlockMeta); +} + + + + + +void cChunkDesc::GetBlockTypeMeta(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta) +{ + m_BlockArea.GetRelBlockTypeMeta(a_RelX, a_RelY, a_RelZ, a_BlockType, a_BlockMeta); +} + + + + + +void cChunkDesc::SetBlockType(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType) +{ + cChunkDef::SetBlock(m_BlockArea.GetBlockTypes(), a_RelX, a_RelY, a_RelZ, a_BlockType); +} + + + + + +BLOCKTYPE cChunkDesc::GetBlockType(int a_RelX, int a_RelY, int a_RelZ) +{ + return cChunkDef::GetBlock(m_BlockArea.GetBlockTypes(), a_RelX, a_RelY, a_RelZ); +} + + + + + +NIBBLETYPE cChunkDesc::GetBlockMeta(int a_RelX, int a_RelY, int a_RelZ) +{ + return m_BlockArea.GetRelBlockMeta(a_RelX, a_RelY, a_RelZ); +} + + + + + +void cChunkDesc::SetBlockMeta(int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_BlockMeta) +{ + m_BlockArea.SetRelBlockMeta(a_RelX, a_RelY, a_RelZ, a_BlockMeta); +} + + + + + +void cChunkDesc::SetBiome(int a_RelX, int a_RelZ, int a_BiomeID) +{ + cChunkDef::SetBiome(m_BiomeMap, a_RelX, a_RelZ, (EMCSBiome)a_BiomeID); +} + + + + +EMCSBiome cChunkDesc::GetBiome(int a_RelX, int a_RelZ) +{ + return cChunkDef::GetBiome(m_BiomeMap, a_RelX, a_RelZ); +} + + + + + +void cChunkDesc::SetHeight(int a_RelX, int a_RelZ, int a_Height) +{ + cChunkDef::SetHeight(m_HeightMap, a_RelX, a_RelZ, a_Height); +} + + + + + +int cChunkDesc::GetHeight(int a_RelX, int a_RelZ) +{ + return cChunkDef::GetHeight(m_HeightMap, a_RelX, a_RelZ); +} + + + + + +void cChunkDesc::SetUseDefaultBiomes(bool a_bUseDefaultBiomes) +{ + m_bUseDefaultBiomes = a_bUseDefaultBiomes; +} + + + + + +bool cChunkDesc::IsUsingDefaultBiomes(void) const +{ + return m_bUseDefaultBiomes; +} + + + + + +void cChunkDesc::SetUseDefaultHeight(bool a_bUseDefaultHeight) +{ + m_bUseDefaultHeight = a_bUseDefaultHeight; +} + + + + + +bool cChunkDesc::IsUsingDefaultHeight(void) const +{ + return m_bUseDefaultHeight; +} + + + + + +void cChunkDesc::SetUseDefaultComposition(bool a_bUseDefaultComposition) +{ + m_bUseDefaultComposition = a_bUseDefaultComposition; +} + + + + + +bool cChunkDesc::IsUsingDefaultComposition(void) const +{ + return m_bUseDefaultComposition; +} + + + + + +void cChunkDesc::SetUseDefaultStructures(bool a_bUseDefaultStructures) +{ + m_bUseDefaultStructures = a_bUseDefaultStructures; +} + + + + + +bool cChunkDesc::IsUsingDefaultStructures(void) const +{ + return m_bUseDefaultStructures; +} + + + + + +void cChunkDesc::SetUseDefaultFinish(bool a_bUseDefaultFinish) +{ + m_bUseDefaultFinish = a_bUseDefaultFinish; +} + + + + + +bool cChunkDesc::IsUsingDefaultFinish(void) const +{ + return m_bUseDefaultFinish; +} + + + + +void cChunkDesc::WriteBlockArea(const cBlockArea & a_BlockArea, int a_RelX, int a_RelY, int a_RelZ, cBlockArea::eMergeStrategy a_MergeStrategy) +{ + m_BlockArea.Merge(a_BlockArea, a_RelX, a_RelY, a_RelZ, a_MergeStrategy); +} + + + + + +void cChunkDesc::ReadBlockArea(cBlockArea & a_Dest, int a_MinRelX, int a_MaxRelX, int a_MinRelY, int a_MaxRelY, int a_MinRelZ, int a_MaxRelZ) +{ + // Normalize the coords: + if (a_MinRelX > a_MaxRelX) + { + std::swap(a_MinRelX, a_MaxRelX); + } + if (a_MinRelY > a_MaxRelY) + { + std::swap(a_MinRelY, a_MaxRelY); + } + if (a_MinRelZ > a_MaxRelZ) + { + std::swap(a_MinRelZ, a_MaxRelZ); + } + + // Include the Max coords: + a_MaxRelX += 1; + a_MaxRelY += 1; + a_MaxRelZ += 1; + + // Check coords validity: + if (a_MinRelX < 0) + { + LOGWARNING("%s: MinRelX less than zero, adjusting to zero", __FUNCTION__); + a_MinRelX = 0; + } + else if (a_MinRelX >= cChunkDef::Width) + { + LOGWARNING("%s: MinRelX more than chunk width, adjusting to chunk width", __FUNCTION__); + a_MinRelX = cChunkDef::Width - 1; + } + if (a_MaxRelX < 0) + { + LOGWARNING("%s: MaxRelX less than zero, adjusting to zero", __FUNCTION__); + a_MaxRelX = 0; + } + else if (a_MinRelX >= cChunkDef::Width) + { + LOGWARNING("%s: MaxRelX more than chunk width, adjusting to chunk width", __FUNCTION__); + a_MaxRelX = cChunkDef::Width - 1; + } + + if (a_MinRelY < 0) + { + LOGWARNING("%s: MinRelY less than zero, adjusting to zero", __FUNCTION__); + a_MinRelY = 0; + } + else if (a_MinRelY >= cChunkDef::Height) + { + LOGWARNING("%s: MinRelY more than chunk height, adjusting to chunk height", __FUNCTION__); + a_MinRelY = cChunkDef::Height - 1; + } + if (a_MaxRelY < 0) + { + LOGWARNING("%s: MaxRelY less than zero, adjusting to zero", __FUNCTION__); + a_MaxRelY = 0; + } + else if (a_MinRelY >= cChunkDef::Height) + { + LOGWARNING("%s: MaxRelY more than chunk height, adjusting to chunk height", __FUNCTION__); + a_MaxRelY = cChunkDef::Height - 1; + } + + if (a_MinRelZ < 0) + { + LOGWARNING("%s: MinRelZ less than zero, adjusting to zero", __FUNCTION__); + a_MinRelZ = 0; + } + else if (a_MinRelZ >= cChunkDef::Width) + { + LOGWARNING("%s: MinRelZ more than chunk width, adjusting to chunk width", __FUNCTION__); + a_MinRelZ = cChunkDef::Width - 1; + } + if (a_MaxRelZ < 0) + { + LOGWARNING("%s: MaxRelZ less than zero, adjusting to zero", __FUNCTION__); + a_MaxRelZ = 0; + } + else if (a_MinRelZ >= cChunkDef::Width) + { + LOGWARNING("%s: MaxRelZ more than chunk width, adjusting to chunk width", __FUNCTION__); + a_MaxRelZ = cChunkDef::Width - 1; + } + + // Prepare the block area: + int SizeX = a_MaxRelX - a_MinRelX; + int SizeY = a_MaxRelY - a_MinRelY; + int SizeZ = a_MaxRelZ - a_MinRelZ; + a_Dest.Clear(); + a_Dest.m_OriginX = m_ChunkX * cChunkDef::Width + a_MinRelX; + a_Dest.m_OriginY = a_MinRelY; + a_Dest.m_OriginZ = m_ChunkZ * cChunkDef::Width + a_MinRelZ; + a_Dest.SetSize(SizeX, SizeY, SizeZ, cBlockArea::baTypes | cBlockArea::baMetas); + + for (int y = 0; y < SizeY; y++) + { + int CDY = a_MinRelY + y; + for (int z = 0; z < SizeZ; z++) + { + int CDZ = a_MinRelZ + z; + for (int x = 0; x < SizeX; x++) + { + int CDX = a_MinRelX + x; + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + GetBlockTypeMeta(CDX, CDY, CDZ, BlockType, BlockMeta); + a_Dest.SetRelBlockTypeMeta(x, y, z, BlockType, BlockMeta); + } // for x + } // for z + } // for y +} + + + + + +HEIGHTTYPE cChunkDesc::GetMaxHeight(void) const +{ + HEIGHTTYPE MaxHeight = m_HeightMap[0]; + for (unsigned int i = 1; i < ARRAYCOUNT(m_HeightMap); i++) + { + if (m_HeightMap[i] > MaxHeight) + { + MaxHeight = m_HeightMap[i]; + } + } + return MaxHeight; +} + + + + + +void cChunkDesc::FillRelCuboid( + int a_MinX, int a_MaxX, + int a_MinY, int a_MaxY, + int a_MinZ, int a_MaxZ, + BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta +) +{ + int MinX = std::max(a_MinX, 0); + int MinY = std::max(a_MinY, 0); + int MinZ = std::max(a_MinZ, 0); + int MaxX = std::min(a_MaxX, cChunkDef::Width - 1); + int MaxY = std::min(a_MaxY, cChunkDef::Height - 1); + int MaxZ = std::min(a_MaxZ, cChunkDef::Width - 1); + + for (int y = MinY; y <= MaxY; y++) + { + for (int z = MinZ; z <= MaxZ; z++) + { + for (int x = MinX; x <= MaxX; x++) + { + SetBlockTypeMeta(x, y, z, a_BlockType, a_BlockMeta); + } + } // for z + } // for y +} + + + + + +void cChunkDesc::ReplaceRelCuboid( + int a_MinX, int a_MaxX, + int a_MinY, int a_MaxY, + int a_MinZ, int a_MaxZ, + BLOCKTYPE a_SrcType, NIBBLETYPE a_SrcMeta, + BLOCKTYPE a_DstType, NIBBLETYPE a_DstMeta +) +{ + int MinX = std::max(a_MinX, 0); + int MinY = std::max(a_MinY, 0); + int MinZ = std::max(a_MinZ, 0); + int MaxX = std::min(a_MaxX, cChunkDef::Width - 1); + int MaxY = std::min(a_MaxY, cChunkDef::Height - 1); + int MaxZ = std::min(a_MaxZ, cChunkDef::Width - 1); + + for (int y = MinY; y <= MaxY; y++) + { + for (int z = MinZ; z <= MaxZ; z++) + { + for (int x = MinX; x <= MaxX; x++) + { + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + GetBlockTypeMeta(x, y, z, BlockType, BlockMeta); + if ((BlockType == a_SrcType) && (BlockMeta == a_SrcMeta)) + { + SetBlockTypeMeta(x, y, z, a_DstType, a_DstMeta); + } + } + } // for z + } // for y +} + + + + + +void cChunkDesc::FloorRelCuboid( + int a_MinX, int a_MaxX, + int a_MinY, int a_MaxY, + int a_MinZ, int a_MaxZ, + BLOCKTYPE a_DstType, NIBBLETYPE a_DstMeta +) +{ + int MinX = std::max(a_MinX, 0); + int MinY = std::max(a_MinY, 0); + int MinZ = std::max(a_MinZ, 0); + int MaxX = std::min(a_MaxX, cChunkDef::Width - 1); + int MaxY = std::min(a_MaxY, cChunkDef::Height - 1); + int MaxZ = std::min(a_MaxZ, cChunkDef::Width - 1); + + for (int y = MinY; y <= MaxY; y++) + { + for (int z = MinZ; z <= MaxZ; z++) + { + for (int x = MinX; x <= MaxX; x++) + { + switch (GetBlockType(x, y, z)) + { + case E_BLOCK_AIR: + case E_BLOCK_WATER: + case E_BLOCK_STATIONARY_WATER: + { + SetBlockTypeMeta(x, y, z, a_DstType, a_DstMeta); + break; + } + } // switch (GetBlockType) + } // for x + } // for z + } // for y +} + + + + + +void cChunkDesc::RandomFillRelCuboid( + int a_MinX, int a_MaxX, + int a_MinY, int a_MaxY, + int a_MinZ, int a_MaxZ, + BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, + int a_RandomSeed, int a_ChanceOutOf10k +) +{ + cNoise Noise(a_RandomSeed); + int MinX = std::max(a_MinX, 0); + int MinY = std::max(a_MinY, 0); + int MinZ = std::max(a_MinZ, 0); + int MaxX = std::min(a_MaxX, cChunkDef::Width - 1); + int MaxY = std::min(a_MaxY, cChunkDef::Height - 1); + int MaxZ = std::min(a_MaxZ, cChunkDef::Width - 1); + + for (int y = MinY; y <= MaxY; y++) + { + for (int z = MinZ; z <= MaxZ; z++) + { + for (int x = MinX; x <= MaxX; x++) + { + int rnd = (Noise.IntNoise3DInt(x, y, z) / 7) % 10000; + if (rnd <= a_ChanceOutOf10k) + { + SetBlockTypeMeta(x, y, z, a_BlockType, a_BlockMeta); + } + } + } // for z + } // for y +} + + + + + +cBlockEntity * cChunkDesc::GetBlockEntity(int a_RelX, int a_RelY, int a_RelZ) +{ + int AbsX = a_RelX + m_ChunkX * cChunkDef::Width; + int AbsZ = a_RelZ + m_ChunkZ * cChunkDef::Width; + for (cBlockEntityList::iterator itr = m_BlockEntities.begin(), end = m_BlockEntities.end(); itr != end; ++itr) + { + if (((*itr)->GetPosX() == AbsX) && ((*itr)->GetPosY() == a_RelY) && ((*itr)->GetPosZ() == AbsZ)) + { + // Already in the list: + if ((*itr)->GetBlockType() != GetBlockType(a_RelX, a_RelY, a_RelZ)) + { + // Wrong type, the block type has been overwritten. Erase and create new: + m_BlockEntities.erase(itr); + break; + } + // Correct type, already present. Return it: + return *itr; + } + } // for itr - m_BlockEntities[] + + // The block entity is not created yet, try to create it and add to list: + cBlockEntity * be = cBlockEntity::CreateByBlockType(GetBlockType(a_RelX, a_RelY, a_RelZ), GetBlockMeta(a_RelX, a_RelY, a_RelZ), AbsX, a_RelY, AbsZ); + if (be == NULL) + { + // No block entity for this block type + return NULL; + } + m_BlockEntities.push_back(be); + return be; +} + + + + + +void cChunkDesc::CompressBlockMetas(cChunkDef::BlockNibbles & a_DestMetas) +{ + const NIBBLETYPE * AreaMetas = m_BlockArea.GetBlockMetas(); + for (unsigned int i = 0; i < ARRAYCOUNT(a_DestMetas); i++) + { + a_DestMetas[i] = AreaMetas[2 * i] | (AreaMetas[2 * i + 1] << 4); + } +} + + + + + +#ifdef _DEBUG + +void cChunkDesc::VerifyHeightmap(void) +{ + for (int x = 0; x < cChunkDef::Width; x++) + { + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int y = cChunkDef::Height - 1; y > 0; y--) + { + BLOCKTYPE BlockType = GetBlockType(x, y, z); + if (BlockType != E_BLOCK_AIR) + { + int Height = GetHeight(x, z); + ASSERT(Height == y); + break; + } + } // for y + } // for z + } // for x +} + +#endif // _DEBUG + + + + + diff --git a/src/Generating/ChunkDesc.h b/src/Generating/ChunkDesc.h new file mode 100644 index 000000000..e130c463f --- /dev/null +++ b/src/Generating/ChunkDesc.h @@ -0,0 +1,217 @@ + +// ChunkDesc.h + +// Declares the cChunkDesc class representing the chunk description used while generating a chunk. This class is also exported to Lua for HOOK_CHUNK_GENERATING. + + + + + +#pragma once + +#include "../BlockArea.h" +#include "../ChunkDef.h" +#include "../Cuboid.h" + + + + + +// fwd: ../BlockArea.h +class cBlockArea; + + + + + +// tolua_begin +class cChunkDesc +{ +public: + // tolua_end + + /// Uncompressed block metas, 1 meta per byte + typedef NIBBLETYPE BlockNibbleBytes[cChunkDef::NumBlocks]; + + cChunkDesc(int a_ChunkX, int a_ChunkZ); + ~cChunkDesc(); + + void SetChunkCoords(int a_ChunkX, int a_ChunkZ); + + // tolua_begin + + int GetChunkX(void) const { return m_ChunkX; } + int GetChunkZ(void) const { return m_ChunkZ; } + + void FillBlocks(BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); + void SetBlockTypeMeta(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta); + void GetBlockTypeMeta(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE & a_BlockType, NIBBLETYPE & a_BlockMeta); + + void SetBlockType(int a_RelX, int a_RelY, int a_RelZ, BLOCKTYPE a_BlockType); + BLOCKTYPE GetBlockType(int a_RelX, int a_RelY, int a_RelZ); + + void SetBlockMeta(int a_RelX, int a_RelY, int a_RelZ, NIBBLETYPE a_BlockMeta); + NIBBLETYPE GetBlockMeta(int a_RelX, int a_RelY, int a_RelZ); + + void SetBiome(int a_RelX, int a_RelZ, int a_BiomeID); + EMCSBiome GetBiome(int a_RelX, int a_RelZ); + + void SetHeight(int a_RelX, int a_RelZ, int a_Height); + int GetHeight(int a_RelX, int a_RelZ); + + // Default generation: + void SetUseDefaultBiomes(bool a_bUseDefaultBiomes); + bool IsUsingDefaultBiomes(void) const; + void SetUseDefaultHeight(bool a_bUseDefaultHeight); + bool IsUsingDefaultHeight(void) const; + void SetUseDefaultComposition(bool a_bUseDefaultComposition); + bool IsUsingDefaultComposition(void) const; + void SetUseDefaultStructures(bool a_bUseDefaultStructures); + bool IsUsingDefaultStructures(void) const; + void SetUseDefaultFinish(bool a_bUseDefaultFinish); + bool IsUsingDefaultFinish(void) const; + + /// Writes the block area into the chunk, with its origin set at the specified relative coords. Area's data overwrite everything in the chunk. + void WriteBlockArea(const cBlockArea & a_BlockArea, int a_RelX, int a_RelY, int a_RelZ, cBlockArea::eMergeStrategy a_MergeStrategy = cBlockArea::msOverwrite); + + /// Reads an area from the chunk into a cBlockArea, blocktypes and blockmetas + void ReadBlockArea(cBlockArea & a_Dest, int a_MinRelX, int a_MaxRelX, int a_MinRelY, int a_MaxRelY, int a_MinRelZ, int a_MaxRelZ); + + /// Returns the maximum height value in the heightmap + HEIGHTTYPE GetMaxHeight(void) const; + + /// Fills the relative cuboid with specified block; allows cuboid out of range of this chunk + void FillRelCuboid( + int a_MinX, int a_MaxX, + int a_MinY, int a_MaxY, + int a_MinZ, int a_MaxZ, + BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta + ); + + /// Fills the relative cuboid with specified block; allows cuboid out of range of this chunk + void FillRelCuboid(const cCuboid & a_RelCuboid, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta) + { + FillRelCuboid( + a_RelCuboid.p1.x, a_RelCuboid.p2.x, + a_RelCuboid.p1.y, a_RelCuboid.p2.y, + a_RelCuboid.p1.z, a_RelCuboid.p2.z, + a_BlockType, a_BlockMeta + ); + } + + /// Replaces the specified src blocks in the cuboid by the dst blocks; allows cuboid out of range of this chunk + void ReplaceRelCuboid( + int a_MinX, int a_MaxX, + int a_MinY, int a_MaxY, + int a_MinZ, int a_MaxZ, + BLOCKTYPE a_SrcType, NIBBLETYPE a_SrcMeta, + BLOCKTYPE a_DstType, NIBBLETYPE a_DstMeta + ); + + /// Replaces the specified src blocks in the cuboid by the dst blocks; allows cuboid out of range of this chunk + void ReplaceRelCuboid( + const cCuboid & a_RelCuboid, + BLOCKTYPE a_SrcType, NIBBLETYPE a_SrcMeta, + BLOCKTYPE a_DstType, NIBBLETYPE a_DstMeta + ) + { + ReplaceRelCuboid( + a_RelCuboid.p1.x, a_RelCuboid.p2.x, + a_RelCuboid.p1.y, a_RelCuboid.p2.y, + a_RelCuboid.p1.z, a_RelCuboid.p2.z, + a_SrcType, a_SrcMeta, + a_DstType, a_DstMeta + ); + } + + /// Replaces the blocks in the cuboid by the dst blocks if they are considered non-floor (air, water); allows cuboid out of range of this chunk + void FloorRelCuboid( + int a_MinX, int a_MaxX, + int a_MinY, int a_MaxY, + int a_MinZ, int a_MaxZ, + BLOCKTYPE a_DstType, NIBBLETYPE a_DstMeta + ); + + /// Replaces the blocks in the cuboid by the dst blocks if they are considered non-floor (air, water); allows cuboid out of range of this chunk + void FloorRelCuboid( + const cCuboid & a_RelCuboid, + BLOCKTYPE a_DstType, NIBBLETYPE a_DstMeta + ) + { + FloorRelCuboid( + a_RelCuboid.p1.x, a_RelCuboid.p2.x, + a_RelCuboid.p1.y, a_RelCuboid.p2.y, + a_RelCuboid.p1.z, a_RelCuboid.p2.z, + a_DstType, a_DstMeta + ); + } + + /// Fills the relative cuboid with specified block with a random chance; allows cuboid out of range of this chunk + void RandomFillRelCuboid( + int a_MinX, int a_MaxX, + int a_MinY, int a_MaxY, + int a_MinZ, int a_MaxZ, + BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, + int a_RandomSeed, int a_ChanceOutOf10k + ); + + /// Fills the relative cuboid with specified block with a random chance; allows cuboid out of range of this chunk + void RandomFillRelCuboid( + const cCuboid & a_RelCuboid, BLOCKTYPE a_BlockType, NIBBLETYPE a_BlockMeta, + int a_RandomSeed, int a_ChanceOutOf10k + ) + { + RandomFillRelCuboid( + a_RelCuboid.p1.x, a_RelCuboid.p2.x, + a_RelCuboid.p1.y, a_RelCuboid.p2.y, + a_RelCuboid.p1.z, a_RelCuboid.p2.z, + a_BlockType, a_BlockMeta, + a_RandomSeed, a_ChanceOutOf10k + ); + } + + /// Returns the block entity at the specified coords. + /// If there is no block entity at those coords, tries to create one, based on the block type + /// If the blocktype doesn't support a block entity, returns NULL. + cBlockEntity * GetBlockEntity(int a_RelX, int a_RelY, int a_RelZ); + + // tolua_end + + // Accessors used by cChunkGenerator::Generator descendants: + inline cChunkDef::BiomeMap & GetBiomeMap (void) { return m_BiomeMap; } + inline cChunkDef::BlockTypes & GetBlockTypes (void) { return *((cChunkDef::BlockTypes *)m_BlockArea.GetBlockTypes()); } + // CANNOT, different compression! + // inline cChunkDef::BlockNibbles & GetBlockMetas (void) { return *((cChunkDef::BlockNibbles *)m_BlockArea.GetBlockMetas()); } + inline BlockNibbleBytes & GetBlockMetasUncompressed(void) { return *((BlockNibbleBytes *)m_BlockArea.GetBlockMetas()); } + inline cChunkDef::HeightMap & GetHeightMap (void) { return m_HeightMap; } + inline cEntityList & GetEntities (void) { return m_Entities; } + inline cBlockEntityList & GetBlockEntities (void) { return m_BlockEntities; } + + /// Compresses the metas from the BlockArea format (1 meta per byte) into regular format (2 metas per byte) + void CompressBlockMetas(cChunkDef::BlockNibbles & a_DestMetas); + + #ifdef _DEBUG + /// Verifies that the heightmap corresponds to blocktype contents; if not, asserts on that column + void VerifyHeightmap(void); + #endif // _DEBUG + +private: + int m_ChunkX; + int m_ChunkZ; + + cChunkDef::BiomeMap m_BiomeMap; + cBlockArea m_BlockArea; + cChunkDef::HeightMap m_HeightMap; + cEntityList m_Entities; // Individual entities are NOT owned by this object! + cBlockEntityList m_BlockEntities; // Individual block entities are NOT owned by this object! + + bool m_bUseDefaultBiomes; + bool m_bUseDefaultHeight; + bool m_bUseDefaultComposition; + bool m_bUseDefaultStructures; + bool m_bUseDefaultFinish; +} ; // tolua_export + + + + diff --git a/src/Generating/ChunkGenerator.cpp b/src/Generating/ChunkGenerator.cpp new file mode 100644 index 000000000..126a896af --- /dev/null +++ b/src/Generating/ChunkGenerator.cpp @@ -0,0 +1,329 @@ + +#include "Globals.h" + +#include "ChunkGenerator.h" +#include "../World.h" +#include "inifile/iniFile.h" +#include "../Root.h" +#include "../Bindings/PluginManager.h" +#include "ChunkDesc.h" +#include "ComposableGenerator.h" +#include "Noise3DGenerator.h" + + + + + +/// If the generation queue size exceeds this number, a warning will be output +const unsigned int QUEUE_WARNING_LIMIT = 1000; + +/// If the generation queue size exceeds this number, chunks with no clients will be skipped +const unsigned int QUEUE_SKIP_LIMIT = 500; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cChunkGenerator: + +cChunkGenerator::cChunkGenerator(void) : + super("cChunkGenerator"), + m_World(NULL), + m_Generator(NULL) +{ +} + + + + + +cChunkGenerator::~cChunkGenerator() +{ + Stop(); +} + + + + + +bool cChunkGenerator::Start(cWorld * a_World, cIniFile & a_IniFile) +{ + MTRand rnd; + m_World = a_World; + m_Seed = a_IniFile.GetValueSetI("Seed", "Seed", rnd.randInt()); + AString GeneratorName = a_IniFile.GetValueSet("Generator", "Generator", "Composable"); + + if (NoCaseCompare(GeneratorName, "Noise3D") == 0) + { + m_Generator = new cNoise3DGenerator(*this); + } + else + { + if (NoCaseCompare(GeneratorName, "composable") != 0) + { + LOGWARN("[Generator]::Generator value \"%s\" not recognized, using \"Composable\".", GeneratorName.c_str()); + } + m_Generator = new cComposableGenerator(*this); + } + + if (m_Generator == NULL) + { + LOGERROR("Generator could not start, aborting the server"); + return false; + } + + m_Generator->Initialize(a_World, a_IniFile); + + return super::Start(); +} + + + + + +void cChunkGenerator::Stop(void) +{ + m_ShouldTerminate = true; + m_Event.Set(); + m_evtRemoved.Set(); // Wake up anybody waiting for empty queue + Wait(); + + delete m_Generator; + m_Generator = NULL; +} + + + + + +void cChunkGenerator::QueueGenerateChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ) +{ + { + cCSLock Lock(m_CS); + + // Check if it is already in the queue: + for (cChunkCoordsList::iterator itr = m_Queue.begin(); itr != m_Queue.end(); ++itr) + { + if ((itr->m_ChunkX == a_ChunkX) && (itr->m_ChunkY == a_ChunkY) && (itr->m_ChunkZ == a_ChunkZ)) + { + // Already in the queue, bail out + return; + } + } // for itr - m_Queue[] + + // Add to queue, issue a warning if too many: + if (m_Queue.size() >= QUEUE_WARNING_LIMIT) + { + LOGWARN("WARNING: Adding chunk [%i, %i] to generation queue; Queue is too big! (%i)", a_ChunkX, a_ChunkZ, m_Queue.size()); + } + m_Queue.push_back(cChunkCoords(a_ChunkX, a_ChunkY, a_ChunkZ)); + } + + m_Event.Set(); +} + + + + + +void cChunkGenerator::GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + if (m_Generator != NULL) + { + m_Generator->GenerateBiomes(a_ChunkX, a_ChunkZ, a_BiomeMap); + } +} + + + + + +void cChunkGenerator::WaitForQueueEmpty(void) +{ + cCSLock Lock(m_CS); + while (!m_ShouldTerminate && !m_Queue.empty()) + { + cCSUnlock Unlock(Lock); + m_evtRemoved.Wait(); + } +} + + + + + +int cChunkGenerator::GetQueueLength(void) +{ + cCSLock Lock(m_CS); + return (int)m_Queue.size(); +} + + + + + +EMCSBiome cChunkGenerator::GetBiomeAt(int a_BlockX, int a_BlockZ) +{ + ASSERT(m_Generator != NULL); + return m_Generator->GetBiomeAt(a_BlockX, a_BlockZ); +} + + + + + +BLOCKTYPE cChunkGenerator::GetIniBlock(cIniFile & a_IniFile, const AString & a_SectionName, const AString & a_ValueName, const AString & a_Default) +{ + AString BlockType = a_IniFile.GetValueSet(a_SectionName, a_ValueName, a_Default); + BLOCKTYPE Block = BlockStringToType(BlockType); + if (Block < 0) + { + LOGWARN("[&s].%s Could not parse block value \"%s\". Using default: \"%s\".", a_SectionName.c_str(), a_ValueName.c_str(), BlockType.c_str(),a_Default.c_str()); + return BlockStringToType(a_Default); + } + return Block; +} + + + + + +void cChunkGenerator::Execute(void) +{ + // To be able to display performance information, the generator counts the chunks generated. + // When the queue gets empty, the count is reset, so that waiting for the queue is not counted into the total time. + int NumChunksGenerated = 0; // Number of chunks generated since the queue was last empty + clock_t GenerationStart = clock(); // Clock tick when the queue started to fill + clock_t LastReportTick = clock(); // Clock tick of the last report made (so that performance isn't reported too often) + + while (!m_ShouldTerminate) + { + cCSLock Lock(m_CS); + while (m_Queue.size() == 0) + { + if ((NumChunksGenerated > 16) && (clock() - LastReportTick > CLOCKS_PER_SEC)) + { + LOG("Chunk generator performance: %.2f ch/s (%d ch total)", + (double)NumChunksGenerated * CLOCKS_PER_SEC/ (clock() - GenerationStart), + NumChunksGenerated + ); + } + cCSUnlock Unlock(Lock); + m_Event.Wait(); + if (m_ShouldTerminate) + { + return; + } + NumChunksGenerated = 0; + GenerationStart = clock(); + LastReportTick = clock(); + } + + cChunkCoords coords = m_Queue.front(); // Get next coord from queue + m_Queue.erase( m_Queue.begin() ); // Remove coordinate from queue + bool SkipEnabled = (m_Queue.size() > QUEUE_SKIP_LIMIT); + Lock.Unlock(); // Unlock ASAP + m_evtRemoved.Set(); + + // Display perf info once in a while: + if ((NumChunksGenerated > 16) && (clock() - LastReportTick > 2 * CLOCKS_PER_SEC)) + { + LOG("Chunk generator performance: %.2f ch/s (%d ch total)", + (double)NumChunksGenerated * CLOCKS_PER_SEC / (clock() - GenerationStart), + NumChunksGenerated + ); + LastReportTick = clock(); + } + + // Hack for regenerating chunks: if Y != 0, the chunk is considered invalid, even if it has its data set + if ((coords.m_ChunkY == 0) && m_World->IsChunkValid(coords.m_ChunkX, coords.m_ChunkZ)) + { + LOGD("Chunk [%d, %d] already generated, skipping generation", coords.m_ChunkX, coords.m_ChunkZ); + // Already generated, ignore request + continue; + } + + if (SkipEnabled && !m_World->HasChunkAnyClients(coords.m_ChunkX, coords.m_ChunkZ)) + { + LOGWARNING("Chunk generator overloaded, skipping chunk [%d, %d]", coords.m_ChunkX, coords.m_ChunkZ); + continue; + } + + LOGD("Generating chunk [%d, %d, %d]", coords.m_ChunkX, coords.m_ChunkY, coords.m_ChunkZ); + DoGenerate(coords.m_ChunkX, coords.m_ChunkY, coords.m_ChunkZ); + + // Save the chunk right after generating, so that we don't have to generate it again on next run + m_World->GetStorage().QueueSaveChunk(coords.m_ChunkX, coords.m_ChunkY, coords.m_ChunkZ); + + NumChunksGenerated++; + } // while (!bStop) +} + + + + +void cChunkGenerator::DoGenerate(int a_ChunkX, int a_ChunkY, int a_ChunkZ) +{ + cChunkDesc ChunkDesc(a_ChunkX, a_ChunkZ); + cRoot::Get()->GetPluginManager()->CallHookChunkGenerating(m_World, a_ChunkX, a_ChunkZ, &ChunkDesc); + m_Generator->DoGenerate(a_ChunkX, a_ChunkZ, ChunkDesc); + cRoot::Get()->GetPluginManager()->CallHookChunkGenerated(m_World, a_ChunkX, a_ChunkZ, &ChunkDesc); + + #ifdef _DEBUG + // Verify that the generator has produced valid data: + ChunkDesc.VerifyHeightmap(); + #endif + + cChunkDef::BlockNibbles BlockMetas; + ChunkDesc.CompressBlockMetas(BlockMetas); + + m_World->SetChunkData( + a_ChunkX, a_ChunkZ, + ChunkDesc.GetBlockTypes(), BlockMetas, + NULL, NULL, // We don't have lighting, chunk will be lighted when needed + &ChunkDesc.GetHeightMap(), &ChunkDesc.GetBiomeMap(), + ChunkDesc.GetEntities(), ChunkDesc.GetBlockEntities(), + true + ); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cChunkGenerator::cGenerator: + +cChunkGenerator::cGenerator::cGenerator(cChunkGenerator & a_ChunkGenerator) : + m_ChunkGenerator(a_ChunkGenerator) +{ +} + + + + + +void cChunkGenerator::cGenerator::Initialize(cWorld * a_World, cIniFile & a_IniFile) +{ + m_World = a_World; + UNUSED(a_IniFile); +} + + + + + +EMCSBiome cChunkGenerator::cGenerator::GetBiomeAt(int a_BlockX, int a_BlockZ) +{ + cChunkDef::BiomeMap Biomes; + int Y = 0; + int ChunkX, ChunkZ; + cWorld::AbsoluteToRelative(a_BlockX, Y, a_BlockZ, ChunkX, Y, ChunkZ); + GenerateBiomes(ChunkX, ChunkZ, Biomes); + return cChunkDef::GetBiome(Biomes, a_BlockX, a_BlockZ); +} + + + + diff --git a/src/Generating/ChunkGenerator.h b/src/Generating/ChunkGenerator.h new file mode 100644 index 000000000..2d3bb8082 --- /dev/null +++ b/src/Generating/ChunkGenerator.h @@ -0,0 +1,113 @@ + +// ChunkGenerator.h + +// Interfaces to the cChunkGenerator class representing the thread that generates chunks + +/* +The object takes requests for generating chunks and processes them in a separate thread one by one. +The requests are not added to the queue if there is already a request with the same coords +Before generating, the thread checks if the chunk hasn't been already generated. +It is theoretically possible to have multiple generator threads by having multiple instances of this object, +but then it MAY happen that the chunk is generated twice. +If the generator queue is overloaded, the generator skips chunks with no clients in them +*/ + + + + + +#pragma once + +#include "../OSSupport/IsThread.h" +#include "../ChunkDef.h" + + + + + +// fwd: +class cWorld; +class cIniFile; +class cChunkDesc; + + + + + +class cChunkGenerator : + cIsThread +{ + typedef cIsThread super; + +public: + /// The interface that a class has to implement to become a generator + class cGenerator + { + public: + cGenerator(cChunkGenerator & a_ChunkGenerator); + virtual ~cGenerator() {} ; // Force a virtual destructor + + /// Called to initialize the generator on server startup. + virtual void Initialize(cWorld * a_World, cIniFile & a_IniFile); + + /// Generates the biomes for the specified chunk (directly, not in a separate thread). Used by the world loader if biomes failed loading. + virtual void GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) = 0; + + /// Returns the biome at the specified coords. Used by ChunkMap if an invalid chunk is queried for biome. Default implementation uses GenerateBiomes(). + virtual EMCSBiome GetBiomeAt(int a_BlockX, int a_BlockZ); + + /// Called in a separate thread to do the actual chunk generation. Generator should generate into a_ChunkDesc. + virtual void DoGenerate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) = 0; + + protected: + cChunkGenerator & m_ChunkGenerator; + cWorld * m_World; + } ; + + + cChunkGenerator (void); + ~cChunkGenerator(); + + bool Start(cWorld * a_World, cIniFile & a_IniFile); + void Stop(void); + + /// Queues the chunk for generation; removes duplicate requests + void QueueGenerateChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ); + + /// Generates the biomes for the specified chunk (directly, not in a separate thread). Used by the world loader if biomes failed loading. + void GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap); + + void WaitForQueueEmpty(void); + + int GetQueueLength(void); + + int GetSeed(void) const { return m_Seed; } + + /// Returns the biome at the specified coords. Used by ChunkMap if an invalid chunk is queried for biome + EMCSBiome GetBiomeAt(int a_BlockX, int a_BlockZ); + + /// Reads a block type from the ini file; returns the blocktype on success, emits a warning and returns a_Default's representation on failure. + static BLOCKTYPE GetIniBlock(cIniFile & a_IniFile, const AString & a_SectionName, const AString & a_ValueName, const AString & a_Default); + +private: + + cWorld * m_World; + + int m_Seed; + + cCriticalSection m_CS; + cChunkCoordsList m_Queue; + cEvent m_Event; ///< Set when an item is added to the queue or the thread should terminate + cEvent m_evtRemoved; ///< Set when an item is removed from the queue + + cGenerator * m_Generator; ///< The actual generator engine used to generate chunks + + // cIsThread override: + virtual void Execute(void) override; + + void DoGenerate(int a_ChunkX, int a_ChunkY, int a_ChunkZ); +}; + + + + diff --git a/src/Generating/CompoGen.cpp b/src/Generating/CompoGen.cpp new file mode 100644 index 000000000..f929ddc2f --- /dev/null +++ b/src/Generating/CompoGen.cpp @@ -0,0 +1,724 @@ + +// CompoGen.cpp + +/* Implements the various terrain composition generators: + - cCompoGenSameBlock + - cCompoGenDebugBiomes + - cCompoGenClassic +*/ + +#include "Globals.h" +#include "CompoGen.h" +#include "../BlockID.h" +#include "../Item.h" +#include "../LinearUpscale.h" +#include "inifile/iniFile.h" + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cCompoGenSameBlock: + +void cCompoGenSameBlock::ComposeTerrain(cChunkDesc & a_ChunkDesc) +{ + a_ChunkDesc.FillBlocks(E_BLOCK_AIR, 0); + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + int Start; + if (m_IsBedrocked) + { + a_ChunkDesc.SetBlockType(x, 0, z, E_BLOCK_BEDROCK); + Start = 1; + } + else + { + Start = 0; + } + for (int y = a_ChunkDesc.GetHeight(x, z); y >= Start; y--) + { + a_ChunkDesc.SetBlockType(x, y, z, m_BlockType); + } // for y + } // for z + } // for x +} + + + + + +void cCompoGenSameBlock::InitializeCompoGen(cIniFile & a_IniFile) +{ + m_BlockType = (BLOCKTYPE)(GetIniItemSet(a_IniFile, "Generator", "SameBlockType", "stone").m_ItemType); + m_IsBedrocked = (a_IniFile.GetValueSetI("Generator", "SameBlockBedrocked", 1) != 0); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cCompoGenDebugBiomes: + +void cCompoGenDebugBiomes::ComposeTerrain(cChunkDesc & a_ChunkDesc) +{ + static BLOCKTYPE Blocks[] = + { + E_BLOCK_STONE, + E_BLOCK_COBBLESTONE, + E_BLOCK_LOG, + E_BLOCK_PLANKS, + E_BLOCK_SANDSTONE, + E_BLOCK_WOOL, + E_BLOCK_COAL_ORE, + E_BLOCK_IRON_ORE, + E_BLOCK_GOLD_ORE, + E_BLOCK_DIAMOND_ORE, + E_BLOCK_LAPIS_ORE, + E_BLOCK_REDSTONE_ORE, + E_BLOCK_IRON_BLOCK, + E_BLOCK_GOLD_BLOCK, + E_BLOCK_DIAMOND_BLOCK, + E_BLOCK_LAPIS_BLOCK, + E_BLOCK_BRICK, + E_BLOCK_MOSSY_COBBLESTONE, + E_BLOCK_OBSIDIAN, + E_BLOCK_NETHERRACK, + E_BLOCK_SOULSAND, + E_BLOCK_NETHER_BRICK, + E_BLOCK_BEDROCK, + } ; + + a_ChunkDesc.FillBlocks(E_BLOCK_AIR, 0); + + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + BLOCKTYPE BlockType = Blocks[a_ChunkDesc.GetBiome(x, z)]; + for (int y = a_ChunkDesc.GetHeight(x, z); y >= 0; y--) + { + a_ChunkDesc.SetBlockType(x, y, z, BlockType); + } // for y + } // for z + } // for x +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cCompoGenClassic: + +cCompoGenClassic::cCompoGenClassic(void) : + m_SeaLevel(60), + m_BeachHeight(2), + m_BeachDepth(4), + m_BlockTop(E_BLOCK_GRASS), + m_BlockMiddle(E_BLOCK_DIRT), + m_BlockBottom(E_BLOCK_STONE), + m_BlockBeach(E_BLOCK_SAND), + m_BlockBeachBottom(E_BLOCK_SANDSTONE), + m_BlockSea(E_BLOCK_STATIONARY_WATER) +{ +} + + + + + +void cCompoGenClassic::ComposeTerrain(cChunkDesc & a_ChunkDesc) +{ + /* The classic composition means: + - 1 layer of grass, 3 of dirt and the rest stone, if the height > sealevel + beachheight + - 3 sand and a 1 sandstone, rest stone if between sealevel and sealevel + beachheight + - water from waterlevel to height, then 3 sand, 1 sandstone, the rest stone, if water depth < beachdepth + - water from waterlevel, then 3 dirt, the rest stone otherwise + - bedrock at the bottom + */ + + a_ChunkDesc.FillBlocks(E_BLOCK_AIR, 0); + + // The patterns to use for different situations, must be same length! + const BLOCKTYPE PatternGround[] = {m_BlockTop, m_BlockMiddle, m_BlockMiddle, m_BlockMiddle} ; + const BLOCKTYPE PatternBeach[] = {m_BlockBeach, m_BlockBeach, m_BlockBeach, m_BlockBeachBottom} ; + const BLOCKTYPE PatternOcean[] = {m_BlockMiddle, m_BlockMiddle, m_BlockMiddle, m_BlockBottom} ; + static int PatternLength = ARRAYCOUNT(PatternGround); + ASSERT(ARRAYCOUNT(PatternGround) == ARRAYCOUNT(PatternBeach)); + ASSERT(ARRAYCOUNT(PatternGround) == ARRAYCOUNT(PatternOcean)); + + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + int Height = a_ChunkDesc.GetHeight(x, z); + const BLOCKTYPE * Pattern; + if (Height > m_SeaLevel + m_BeachHeight) + { + Pattern = PatternGround; + } + else if (Height > m_SeaLevel - m_BeachDepth) + { + Pattern = PatternBeach; + } + else + { + Pattern = PatternOcean; + } + + // Fill water from sealevel down to height (if any): + for (int y = m_SeaLevel; y >= Height; --y) + { + a_ChunkDesc.SetBlockType(x, y, z, m_BlockSea); + } + + // Fill from height till the bottom: + for (int y = Height; y >= 1; y--) + { + a_ChunkDesc.SetBlockType(x, y, z, (Height - y < PatternLength) ? Pattern[Height - y] : m_BlockBottom); + } + + // The last layer is always bedrock: + a_ChunkDesc.SetBlockType(x, 0, z, E_BLOCK_BEDROCK); + } // for x + } // for z +} + + + + + +void cCompoGenClassic::InitializeCompoGen(cIniFile & a_IniFile) +{ + m_SeaLevel = a_IniFile.GetValueSetI("Generator", "ClassicSeaLevel", m_SeaLevel); + m_BeachHeight = a_IniFile.GetValueSetI("Generator", "ClassicBeachHeight", m_BeachHeight); + m_BeachDepth = a_IniFile.GetValueSetI("Generator", "ClassicBeachDepth", m_BeachDepth); + m_BlockTop = (BLOCKTYPE)(GetIniItemSet(a_IniFile, "Generator", "ClassicBlockTop", "grass").m_ItemType); + m_BlockMiddle = (BLOCKTYPE)(GetIniItemSet(a_IniFile, "Generator", "ClassicBlockMiddle", "dirt").m_ItemType); + m_BlockBottom = (BLOCKTYPE)(GetIniItemSet(a_IniFile, "Generator", "ClassicBlockBottom", "stone").m_ItemType); + m_BlockBeach = (BLOCKTYPE)(GetIniItemSet(a_IniFile, "Generator", "ClassicBlockBeach", "sand").m_ItemType); + m_BlockBeachBottom = (BLOCKTYPE)(GetIniItemSet(a_IniFile, "Generator", "ClassicBlockBeachBottom", "sandstone").m_ItemType); + m_BlockSea = (BLOCKTYPE)(GetIniItemSet(a_IniFile, "Generator", "ClassicBlockSea", "stationarywater").m_ItemType); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cCompoGenBiomal: + +void cCompoGenBiomal::ComposeTerrain(cChunkDesc & a_ChunkDesc) +{ + a_ChunkDesc.FillBlocks(E_BLOCK_AIR, 0); + + int ChunkX = a_ChunkDesc.GetChunkX(); + int ChunkZ = a_ChunkDesc.GetChunkZ(); + + /* + _X 2013_04_22: + There's no point in generating the whole cubic noise at once, because the noise values are used in + only about 20 % of the cases, so the speed gained by precalculating is lost by precalculating too much data + */ + + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + int Height = a_ChunkDesc.GetHeight(x, z); + if (Height > m_SeaLevel) + { + switch (a_ChunkDesc.GetBiome(x, z)) + { + case biOcean: + case biPlains: + case biExtremeHills: + case biForest: + case biTaiga: + case biSwampland: + case biRiver: + case biFrozenOcean: + case biFrozenRiver: + case biIcePlains: + case biIceMountains: + case biForestHills: + case biTaigaHills: + case biExtremeHillsEdge: + case biJungle: + case biJungleHills: + case biJungleEdge: + case biDeepOcean: + case biStoneBeach: + case biColdBeach: + case biBirchForest: + case biBirchForestHills: + case biRoofedForest: + case biColdTaiga: + case biColdTaigaHills: + case biExtremeHillsPlus: + case biSavanna: + case biSavannaPlateau: + case biSunflowerPlains: + case biExtremeHillsM: + case biFlowerForest: + case biTaigaM: + case biSwamplandM: + case biIcePlainsSpikes: + case biJungleM: + case biJungleEdgeM: + case biBirchForestM: + case biBirchForestHillsM: + case biRoofedForestM: + case biColdTaigaM: + case biExtremeHillsPlusM: + case biSavannaM: + case biSavannaPlateauM: + { + FillColumnGrass(x, z, Height, a_ChunkDesc.GetBlockTypes()); + break; + } + + case biMesa: + case biMesaPlateauF: + case biMesaPlateau: + case biMesaBryce: + case biMesaPlateauFM: + case biMesaPlateauM: + { + FillColumnClay(x, z, Height, a_ChunkDesc.GetBlockTypes()); + break; + } + + case biMegaTaiga: + case biMegaTaigaHills: + case biMegaSpruceTaiga: + case biMegaSpruceTaigaHills: + { + FillColumnDirt(x, z, Height, a_ChunkDesc.GetBlockTypes()); + break; + } + + case biDesertHills: + case biDesert: + case biDesertM: + case biBeach: + { + FillColumnSand(x, z, Height, a_ChunkDesc.GetBlockTypes()); + break; + } + case biMushroomIsland: + case biMushroomShore: + { + FillColumnMycelium(x, z, Height, a_ChunkDesc.GetBlockTypes()); + break; + } + default: + { + // TODO + ASSERT(!"CompoGenBiomal: Biome not implemented yet!"); + break; + } + } + } + else + { + switch (a_ChunkDesc.GetBiome(x, z)) + { + case biDesert: + case biBeach: + { + // Fill with water, sand, sandstone and stone + FillColumnWaterSand(x, z, Height, a_ChunkDesc.GetBlockTypes()); + break; + } + default: + { + // Fill with water, sand/dirt/clay mix and stone + if (m_Noise.CubicNoise2D(0.3f * (cChunkDef::Width * ChunkX + x), 0.3f * (cChunkDef::Width * ChunkZ + z)) < 0) + { + FillColumnWaterSand(x, z, Height, a_ChunkDesc.GetBlockTypes()); + } + else + { + FillColumnWaterDirt(x, z, Height, a_ChunkDesc.GetBlockTypes()); + } + break; + } + } // switch (biome) + a_ChunkDesc.SetHeight(x, z, m_SeaLevel + 1); + } // else (under water) + a_ChunkDesc.SetBlockType(x, 0, z, E_BLOCK_BEDROCK); + } // for x + } // for z +} + + + + + +void cCompoGenBiomal::InitializeCompoGen(cIniFile & a_IniFile) +{ + m_SeaLevel = a_IniFile.GetValueSetI("Generator", "BiomalSeaLevel", m_SeaLevel) - 1; +} + + + + + +void cCompoGenBiomal::FillColumnGrass(int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes) +{ + BLOCKTYPE Pattern[] = + { + E_BLOCK_GRASS, + E_BLOCK_DIRT, + E_BLOCK_DIRT, + E_BLOCK_DIRT, + } ; + FillColumnPattern(a_RelX, a_RelZ, a_Height, a_BlockTypes, Pattern, ARRAYCOUNT(Pattern)); + + for (int y = a_Height - ARRAYCOUNT(Pattern); y > 0; y--) + { + cChunkDef::SetBlock(a_BlockTypes, a_RelX, y, a_RelZ, E_BLOCK_STONE); + } +} + + + + + +void cCompoGenBiomal::FillColumnClay(int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes) +{ + BLOCKTYPE Pattern[] = + { + E_BLOCK_HARDENED_CLAY, + E_BLOCK_HARDENED_CLAY, + E_BLOCK_HARDENED_CLAY, + E_BLOCK_HARDENED_CLAY, + } ; + FillColumnPattern(a_RelX, a_RelZ, a_Height, a_BlockTypes, Pattern, ARRAYCOUNT(Pattern)); + + for (int y = a_Height - ARRAYCOUNT(Pattern); y > 0; y--) + { + cChunkDef::SetBlock(a_BlockTypes, a_RelX, y, a_RelZ, E_BLOCK_STONE); + } +} + + + + + +void cCompoGenBiomal::FillColumnDirt(int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes) +{ + for (int y = 0; y < 4; y++) + { + if (a_Height - y < 0) + { + return; + } + cChunkDef::SetBlock(a_BlockTypes, a_RelX, a_Height - y, a_RelZ, E_BLOCK_DIRT); + } + for (int y = a_Height - 4; y > 0; y--) + { + cChunkDef::SetBlock(a_BlockTypes, a_RelX, y, a_RelZ, E_BLOCK_STONE); + } +} + + + + + +void cCompoGenBiomal::FillColumnSand(int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes) +{ + BLOCKTYPE Pattern[] = + { + E_BLOCK_SAND, + E_BLOCK_SAND, + E_BLOCK_SAND, + E_BLOCK_SANDSTONE, + } ; + FillColumnPattern(a_RelX, a_RelZ, a_Height, a_BlockTypes, Pattern, ARRAYCOUNT(Pattern)); + + for (int y = a_Height - ARRAYCOUNT(Pattern); y > 0; y--) + { + cChunkDef::SetBlock(a_BlockTypes, a_RelX, y, a_RelZ, E_BLOCK_STONE); + } +} + + + + + + +void cCompoGenBiomal::FillColumnMycelium (int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes) +{ + BLOCKTYPE Pattern[] = + { + E_BLOCK_MYCELIUM, + E_BLOCK_DIRT, + E_BLOCK_DIRT, + E_BLOCK_DIRT, + } ; + FillColumnPattern(a_RelX, a_RelZ, a_Height, a_BlockTypes, Pattern, ARRAYCOUNT(Pattern)); + + for (int y = a_Height - ARRAYCOUNT(Pattern); y > 0; y--) + { + cChunkDef::SetBlock(a_BlockTypes, a_RelX, y, a_RelZ, E_BLOCK_STONE); + } +} + + + + + +void cCompoGenBiomal::FillColumnWaterSand(int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes) +{ + FillColumnSand(a_RelX, a_RelZ, a_Height, a_BlockTypes); + for (int y = a_Height + 1; y <= m_SeaLevel + 1; y++) + { + cChunkDef::SetBlock(a_BlockTypes, a_RelX, y, a_RelZ, E_BLOCK_STATIONARY_WATER); + } +} + + + + + +void cCompoGenBiomal::FillColumnWaterDirt(int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes) +{ + // Dirt + BLOCKTYPE Pattern[] = + { + E_BLOCK_DIRT, + E_BLOCK_DIRT, + E_BLOCK_DIRT, + E_BLOCK_DIRT, + } ; + FillColumnPattern(a_RelX, a_RelZ, a_Height, a_BlockTypes, Pattern, ARRAYCOUNT(Pattern)); + + for (int y = a_Height - ARRAYCOUNT(Pattern); y > 0; y--) + { + cChunkDef::SetBlock(a_BlockTypes, a_RelX, y, a_RelZ, E_BLOCK_STONE); + } + for (int y = a_Height + 1; y <= m_SeaLevel + 1; y++) + { + cChunkDef::SetBlock(a_BlockTypes, a_RelX, y, a_RelZ, E_BLOCK_STATIONARY_WATER); + } +} + + + + + + +void cCompoGenBiomal::FillColumnPattern(int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes, const BLOCKTYPE * a_Pattern, int a_PatternSize) +{ + for (int y = a_Height, idx = 0; (y >= 0) && (idx < a_PatternSize); y--, idx++) + { + cChunkDef::SetBlock(a_BlockTypes, a_RelX, y, a_RelZ, a_Pattern[idx]); + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cCompoGenNether: + +cCompoGenNether::cCompoGenNether(int a_Seed) : + m_Noise1(a_Seed + 10), + m_Noise2(a_Seed * a_Seed * 10 + a_Seed * 1000 + 6000), + m_Threshold(0) +{ +} + + + + + +void cCompoGenNether::ComposeTerrain(cChunkDesc & a_ChunkDesc) +{ + HEIGHTTYPE MaxHeight = a_ChunkDesc.GetMaxHeight(); + + const int SEGMENT_HEIGHT = 8; + const int INTERPOL_X = 16; // Must be a divisor of 16 + const int INTERPOL_Z = 16; // Must be a divisor of 16 + // Interpolate the chunk in 16 * SEGMENT_HEIGHT * 16 "segments", each SEGMENT_HEIGHT blocks high and each linearly interpolated separately. + // Have two buffers, one for the lowest floor and one for the highest floor, so that Y-interpolation can be done between them + // Then swap the buffers and use the previously-top one as the current-bottom, without recalculating it. + + int FloorBuf1[17 * 17]; + int FloorBuf2[17 * 17]; + int * FloorHi = FloorBuf1; + int * FloorLo = FloorBuf2; + int BaseX = a_ChunkDesc.GetChunkX() * cChunkDef::Width; + int BaseZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width; + + // Interpolate the lowest floor: + for (int z = 0; z <= 16 / INTERPOL_Z; z++) for (int x = 0; x <= 16 / INTERPOL_X; x++) + { + FloorLo[INTERPOL_X * x + 17 * INTERPOL_Z * z] = + m_Noise1.IntNoise3DInt(BaseX + INTERPOL_X * x, 0, BaseZ + INTERPOL_Z * z) * + m_Noise2.IntNoise3DInt(BaseX + INTERPOL_X * x, 0, BaseZ + INTERPOL_Z * z) / + 256; + } // for x, z - FloorLo[] + LinearUpscale2DArrayInPlace(FloorLo, 17, 17, INTERPOL_X, INTERPOL_Z); + + // Interpolate segments: + for (int Segment = 0; Segment < MaxHeight; Segment += SEGMENT_HEIGHT) + { + // First update the high floor: + for (int z = 0; z <= 16 / INTERPOL_Z; z++) for (int x = 0; x <= 16 / INTERPOL_X; x++) + { + FloorHi[INTERPOL_X * x + 17 * INTERPOL_Z * z] = + m_Noise1.IntNoise3DInt(BaseX + INTERPOL_X * x, Segment + SEGMENT_HEIGHT, BaseZ + INTERPOL_Z * z) * + m_Noise2.IntNoise3DInt(BaseX + INTERPOL_Z * x, Segment + SEGMENT_HEIGHT, BaseZ + INTERPOL_Z * z) / + 256; + } // for x, z - FloorLo[] + LinearUpscale2DArrayInPlace(FloorHi, 17, 17, INTERPOL_X, INTERPOL_Z); + + // Interpolate between FloorLo and FloorHi: + for (int z = 0; z < 16; z++) for (int x = 0; x < 16; x++) + { + int Lo = FloorLo[x + 17 * z] / 256; + int Hi = FloorHi[x + 17 * z] / 256; + for (int y = 0; y < SEGMENT_HEIGHT; y++) + { + int Val = Lo + (Hi - Lo) * y / SEGMENT_HEIGHT; + a_ChunkDesc.SetBlockType(x, y + Segment, z, (Val < m_Threshold) ? E_BLOCK_NETHERRACK : E_BLOCK_AIR); + } + } + + // Swap the floors: + std::swap(FloorLo, FloorHi); + } + + // Bedrock at the bottom and at the top: + for (int z = 0; z < 16; z++) for (int x = 0; x < 16; x++) + { + a_ChunkDesc.SetBlockType(x, 0, z, E_BLOCK_BEDROCK); + a_ChunkDesc.SetBlockType(x, a_ChunkDesc.GetHeight(x, z), z, E_BLOCK_BEDROCK); + } +} + + + + + +void cCompoGenNether::InitializeCompoGen(cIniFile & a_IniFile) +{ + m_Threshold = a_IniFile.GetValueSetI("Generator", "NetherThreshold", m_Threshold); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cCompoGenCache: + +cCompoGenCache::cCompoGenCache(cTerrainCompositionGen & a_Underlying, int a_CacheSize) : + m_Underlying(a_Underlying), + m_CacheSize(a_CacheSize), + m_CacheOrder(new int[a_CacheSize]), + m_CacheData(new sCacheData[a_CacheSize]), + m_NumHits(0), + m_NumMisses(0), + m_TotalChain(0) +{ + for (int i = 0; i < m_CacheSize; i++) + { + m_CacheOrder[i] = i; + m_CacheData[i].m_ChunkX = 0x7fffffff; + m_CacheData[i].m_ChunkZ = 0x7fffffff; + } +} + + + + + +cCompoGenCache::~cCompoGenCache() +{ + delete[] m_CacheData; + delete[] m_CacheOrder; +} + + + + + +void cCompoGenCache::ComposeTerrain(cChunkDesc & a_ChunkDesc) +{ + #ifdef _DEBUG + if (((m_NumHits + m_NumMisses) % 1024) == 10) + { + LOGD("CompoGenCache: %d hits, %d misses, saved %.2f %%", m_NumHits, m_NumMisses, 100.0 * m_NumHits / (m_NumHits + m_NumMisses)); + LOGD("CompoGenCache: Avg cache chain length: %.2f", (float)m_TotalChain / m_NumHits); + } + #endif // _DEBUG + + int ChunkX = a_ChunkDesc.GetChunkX(); + int ChunkZ = a_ChunkDesc.GetChunkZ(); + + for (int i = 0; i < m_CacheSize; i++) + { + if ( + (m_CacheData[m_CacheOrder[i]].m_ChunkX != ChunkX) || + (m_CacheData[m_CacheOrder[i]].m_ChunkZ != ChunkZ) + ) + { + continue; + } + // Found it in the cache + int Idx = m_CacheOrder[i]; + + // Move to front: + for (int j = i; j > 0; j--) + { + m_CacheOrder[j] = m_CacheOrder[j - 1]; + } + m_CacheOrder[0] = Idx; + + // Use the cached data: + memcpy(a_ChunkDesc.GetBlockTypes(), m_CacheData[Idx].m_BlockTypes, sizeof(a_ChunkDesc.GetBlockTypes())); + memcpy(a_ChunkDesc.GetBlockMetasUncompressed(), m_CacheData[Idx].m_BlockMetas, sizeof(a_ChunkDesc.GetBlockMetasUncompressed())); + + m_NumHits++; + m_TotalChain += i; + return; + } // for i - cache + + // Not in the cache: + m_NumMisses++; + m_Underlying.ComposeTerrain(a_ChunkDesc); + + // Insert it as the first item in the MRU order: + int Idx = m_CacheOrder[m_CacheSize - 1]; + for (int i = m_CacheSize - 1; i > 0; i--) + { + m_CacheOrder[i] = m_CacheOrder[i - 1]; + } // for i - m_CacheOrder[] + m_CacheOrder[0] = Idx; + memcpy(m_CacheData[Idx].m_BlockTypes, a_ChunkDesc.GetBlockTypes(), sizeof(a_ChunkDesc.GetBlockTypes())); + memcpy(m_CacheData[Idx].m_BlockMetas, a_ChunkDesc.GetBlockMetasUncompressed(), sizeof(a_ChunkDesc.GetBlockMetasUncompressed())); + m_CacheData[Idx].m_ChunkX = ChunkX; + m_CacheData[Idx].m_ChunkZ = ChunkZ; +} + + + + + +void cCompoGenCache::InitializeCompoGen(cIniFile & a_IniFile) +{ + m_Underlying.InitializeCompoGen(a_IniFile); +} + + + + diff --git a/src/Generating/CompoGen.h b/src/Generating/CompoGen.h new file mode 100644 index 000000000..096b0fb5a --- /dev/null +++ b/src/Generating/CompoGen.h @@ -0,0 +1,184 @@ + +// CompoGen.h + +/* Interfaces to the various terrain composition generators: + - cCompoGenSameBlock + - cCompoGenDebugBiomes + - cCompoGenClassic + - cCompoGenBiomal + - cCompoGenNether + - cCompoGenCache +*/ + + + + + +#pragma once + +#include "ComposableGenerator.h" +#include "../Noise.h" + + + + + +class cCompoGenSameBlock : + public cTerrainCompositionGen +{ +public: + cCompoGenSameBlock(void) : + m_BlockType(E_BLOCK_STONE), + m_IsBedrocked(true) + {} + +protected: + + BLOCKTYPE m_BlockType; + bool m_IsBedrocked; + + // cTerrainCompositionGen overrides: + virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc) override; + virtual void InitializeCompoGen(cIniFile & a_IniFile) override; +} ; + + + + + +class cCompoGenDebugBiomes : + public cTerrainCompositionGen +{ +public: + cCompoGenDebugBiomes(void) {} + +protected: + + // cTerrainCompositionGen overrides: + virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +class cCompoGenClassic : + public cTerrainCompositionGen +{ +public: + cCompoGenClassic(void); + +protected: + + int m_SeaLevel; + int m_BeachHeight; + int m_BeachDepth; + BLOCKTYPE m_BlockTop; + BLOCKTYPE m_BlockMiddle; + BLOCKTYPE m_BlockBottom; + BLOCKTYPE m_BlockBeach; + BLOCKTYPE m_BlockBeachBottom; + BLOCKTYPE m_BlockSea; + + // cTerrainCompositionGen overrides: + virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc) override; + virtual void InitializeCompoGen(cIniFile & a_IniFile) override; +} ; + + + + + +class cCompoGenBiomal : + public cTerrainCompositionGen +{ +public: + cCompoGenBiomal(int a_Seed) : + m_Noise(a_Seed + 1000), + m_SeaLevel(62) + { + } + +protected: + + cNoise m_Noise; + int m_SeaLevel; + + // cTerrainCompositionGen overrides: + virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc) override; + virtual void InitializeCompoGen(cIniFile & a_IniFile) override; + + void FillColumnGrass (int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes); + void FillColumnClay (int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes); + void FillColumnDirt (int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes); + void FillColumnSand (int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes); + void FillColumnMycelium (int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes); + void FillColumnWaterSand(int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes); + void FillColumnWaterDirt(int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes); + + void FillColumnPattern (int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes, const BLOCKTYPE * a_Pattern, int a_PatternSize); +} ; + + + + + +class cCompoGenNether : + public cTerrainCompositionGen +{ +public: + cCompoGenNether(int a_Seed); + +protected: + cNoise m_Noise1; + cNoise m_Noise2; + + int m_Threshold; + + // cTerrainCompositionGen overrides: + virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc) override; + virtual void InitializeCompoGen(cIniFile & a_IniFile) override; +} ; + + + + + +/// Caches most-recently-used chunk composition of another composition generator. Caches only the types and metas +class cCompoGenCache : + public cTerrainCompositionGen +{ +public: + cCompoGenCache(cTerrainCompositionGen & a_Underlying, int a_CacheSize); // Doesn't take ownership of a_Underlying + ~cCompoGenCache(); + + // cTerrainCompositionGen override: + virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc) override; + virtual void InitializeCompoGen(cIniFile & a_IniFile) override; + +protected: + + cTerrainCompositionGen & m_Underlying; + + struct sCacheData + { + int m_ChunkX; + int m_ChunkZ; + cChunkDef::BlockTypes m_BlockTypes; + cChunkDesc::BlockNibbleBytes m_BlockMetas; // The metas are uncompressed, 1 meta per byte + } ; + + // To avoid moving large amounts of data for the MRU behavior, we MRU-ize indices to an array of the actual data + int m_CacheSize; + int * m_CacheOrder; // MRU-ized order, indices into m_CacheData array + sCacheData * m_CacheData; // m_CacheData[m_CacheOrder[0]] is the most recently used + + // Cache statistics + int m_NumHits; + int m_NumMisses; + int m_TotalChain; // Number of cache items walked to get to a hit (only added for hits) +} ; + + + + diff --git a/src/Generating/ComposableGenerator.cpp b/src/Generating/ComposableGenerator.cpp new file mode 100644 index 000000000..c86568c95 --- /dev/null +++ b/src/Generating/ComposableGenerator.cpp @@ -0,0 +1,454 @@ + +// ComposableGenerator.cpp + +// Implements the cComposableGenerator class representing the chunk generator that takes the composition approach to generating chunks + +#include "Globals.h" + +#include "ComposableGenerator.h" +#include "../World.h" +#include "inifile/iniFile.h" +#include "../Root.h" + +// Individual composed algorithms: +#include "BioGen.h" +#include "HeiGen.h" +#include "CompoGen.h" +#include "StructGen.h" +#include "FinishGen.h" + +#include "Caves.h" +#include "DistortedHeightmap.h" +#include "EndGen.h" +#include "MineShafts.h" +#include "Noise3DGenerator.h" +#include "Ravines.h" + + + + + + + + + + +cComposableGenerator::cComposableGenerator(cChunkGenerator & a_ChunkGenerator) : + super(a_ChunkGenerator), + m_BiomeGen(NULL), + m_HeightGen(NULL), + m_CompositionGen(NULL), + m_UnderlyingBiomeGen(NULL), + m_UnderlyingHeightGen(NULL), + m_UnderlyingCompositionGen(NULL) +{ +} + + + + + +cComposableGenerator::~cComposableGenerator() +{ + // Delete the generating composition: + for (cFinishGenList::const_iterator itr = m_FinishGens.begin(); itr != m_FinishGens.end(); ++itr) + { + delete *itr; + } + m_FinishGens.clear(); + for (cStructureGenList::const_iterator itr = m_StructureGens.begin(); itr != m_StructureGens.end(); ++itr) + { + delete *itr; + } + m_StructureGens.clear(); + + delete m_CompositionGen; + m_CompositionGen = NULL; + delete m_HeightGen; + m_HeightGen = NULL; + delete m_BiomeGen; + m_BiomeGen = NULL; + delete m_UnderlyingCompositionGen; + m_UnderlyingCompositionGen = NULL; + delete m_UnderlyingHeightGen; + m_UnderlyingHeightGen = NULL; + delete m_UnderlyingBiomeGen; + m_UnderlyingBiomeGen = NULL; +} + + + + + +void cComposableGenerator::Initialize(cWorld * a_World, cIniFile & a_IniFile) +{ + super::Initialize(a_World, a_IniFile); + + InitBiomeGen(a_IniFile); + InitHeightGen(a_IniFile); + InitCompositionGen(a_IniFile); + InitStructureGens(a_IniFile); + InitFinishGens(a_IniFile); +} + + + + + +void cComposableGenerator::GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + if (m_BiomeGen != NULL) // Quick fix for generator deinitializing before the world storage finishes loading + { + m_BiomeGen->GenBiomes(a_ChunkX, a_ChunkZ, a_BiomeMap); + } +} + + + + + +void cComposableGenerator::DoGenerate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) +{ + if (a_ChunkDesc.IsUsingDefaultBiomes()) + { + m_BiomeGen->GenBiomes(a_ChunkX, a_ChunkZ, a_ChunkDesc.GetBiomeMap()); + } + + if (a_ChunkDesc.IsUsingDefaultHeight()) + { + m_HeightGen->GenHeightMap(a_ChunkX, a_ChunkZ, a_ChunkDesc.GetHeightMap()); + } + + if (a_ChunkDesc.IsUsingDefaultComposition()) + { + m_CompositionGen->ComposeTerrain(a_ChunkDesc); + } + + if (a_ChunkDesc.IsUsingDefaultStructures()) + { + for (cStructureGenList::iterator itr = m_StructureGens.begin(); itr != m_StructureGens.end(); ++itr) + { + (*itr)->GenStructures(a_ChunkDesc); + } // for itr - m_StructureGens[] + } + + if (a_ChunkDesc.IsUsingDefaultFinish()) + { + for (cFinishGenList::iterator itr = m_FinishGens.begin(); itr != m_FinishGens.end(); ++itr) + { + (*itr)->GenFinish(a_ChunkDesc); + } // for itr - m_FinishGens[] + } +} + + + + + +void cComposableGenerator::InitBiomeGen(cIniFile & a_IniFile) +{ + bool CacheOffByDefault = false; + m_BiomeGen = cBiomeGen::CreateBiomeGen(a_IniFile, m_ChunkGenerator.GetSeed(), CacheOffByDefault); + + // Add a cache, if requested: + int CacheSize = a_IniFile.GetValueSetI("Generator", "BiomeGenCacheSize", CacheOffByDefault ? 0 : 64); + if (CacheSize > 0) + { + if (CacheSize < 4) + { + LOGWARNING("Biomegen cache size set too low, would hurt performance instead of helping. Increasing from %d to %d", + CacheSize, 4 + ); + CacheSize = 4; + } + LOGD("Using a cache for biomegen of size %d.", CacheSize); + m_UnderlyingBiomeGen = m_BiomeGen; + m_BiomeGen = new cBioGenCache(m_UnderlyingBiomeGen, CacheSize); + } +} + + + + + +void cComposableGenerator::InitHeightGen(cIniFile & a_IniFile) +{ + AString HeightGenName = a_IniFile.GetValueSet("Generator", "HeightGen", ""); + if (HeightGenName.empty()) + { + LOGWARN("[Generator] HeightGen value not set in world.ini, using \"Biomal\"."); + HeightGenName = "Biomal"; + } + + int Seed = m_ChunkGenerator.GetSeed(); + bool CacheOffByDefault = false; + if (NoCaseCompare(HeightGenName, "flat") == 0) + { + m_HeightGen = new cHeiGenFlat; + CacheOffByDefault = true; // We're generating faster than a cache would retrieve data + } + else if (NoCaseCompare(HeightGenName, "classic") == 0) + { + m_HeightGen = new cHeiGenClassic(Seed); + } + else if (NoCaseCompare(HeightGenName, "DistortedHeightmap") == 0) + { + m_HeightGen = new cDistortedHeightmap(Seed, *m_BiomeGen); + } + else if (NoCaseCompare(HeightGenName, "End") == 0) + { + m_HeightGen = new cEndGen(Seed); + } + else if (NoCaseCompare(HeightGenName, "Noise3D") == 0) + { + m_HeightGen = new cNoise3DComposable(Seed); + } + else // "biomal" or <not found> + { + if (NoCaseCompare(HeightGenName, "biomal") != 0) + { + LOGWARN("Unknown HeightGen \"%s\", using \"Biomal\" instead.", HeightGenName.c_str()); + } + m_HeightGen = new cHeiGenBiomal(Seed, *m_BiomeGen); + + /* + // Performance-testing: + LOGINFO("Measuring performance of cHeiGenBiomal..."); + clock_t BeginTick = clock(); + for (int x = 0; x < 500; x++) + { + cChunkDef::HeightMap Heights; + m_HeightGen->GenHeightMap(x * 5, x * 5, Heights); + } + clock_t Duration = clock() - BeginTick; + LOGINFO("HeightGen for 500 chunks took %d ticks (%.02f sec)", Duration, (double)Duration / CLOCKS_PER_SEC); + //*/ + } + + // Read the settings: + m_HeightGen->InitializeHeightGen(a_IniFile); + + // Add a cache, if requested: + int CacheSize = a_IniFile.GetValueSetI("Generator", "HeightGenCacheSize", CacheOffByDefault ? 0 : 64); + if (CacheSize > 0) + { + if (CacheSize < 4) + { + LOGWARNING("Heightgen cache size set too low, would hurt performance instead of helping. Increasing from %d to %d", + CacheSize, 4 + ); + CacheSize = 4; + } + LOGD("Using a cache for Heightgen of size %d.", CacheSize); + m_UnderlyingHeightGen = m_HeightGen; + m_HeightGen = new cHeiGenCache(*m_UnderlyingHeightGen, CacheSize); + } +} + + + + + +void cComposableGenerator::InitCompositionGen(cIniFile & a_IniFile) +{ + int Seed = m_ChunkGenerator.GetSeed(); + AString CompoGenName = a_IniFile.GetValueSet("Generator", "CompositionGen", ""); + if (CompoGenName.empty()) + { + LOGWARN("[Generator] CompositionGen value not set in world.ini, using \"Biomal\"."); + CompoGenName = "Biomal"; + } + if (NoCaseCompare(CompoGenName, "sameblock") == 0) + { + m_CompositionGen = new cCompoGenSameBlock; + } + else if (NoCaseCompare(CompoGenName, "debugbiomes") == 0) + { + m_CompositionGen = new cCompoGenDebugBiomes; + } + else if (NoCaseCompare(CompoGenName, "classic") == 0) + { + m_CompositionGen = new cCompoGenClassic; + } + else if (NoCaseCompare(CompoGenName, "DistortedHeightmap") == 0) + { + m_CompositionGen = new cDistortedHeightmap(Seed, *m_BiomeGen); + } + else if (NoCaseCompare(CompoGenName, "end") == 0) + { + m_CompositionGen = new cEndGen(Seed); + } + else if (NoCaseCompare(CompoGenName, "nether") == 0) + { + m_CompositionGen = new cCompoGenNether(Seed); + } + else if (NoCaseCompare(CompoGenName, "Noise3D") == 0) + { + m_CompositionGen = new cNoise3DComposable(m_ChunkGenerator.GetSeed()); + } + else + { + if (NoCaseCompare(CompoGenName, "biomal") != 0) + { + LOGWARN("Unknown CompositionGen \"%s\", using \"biomal\" instead.", CompoGenName.c_str()); + } + m_CompositionGen = new cCompoGenBiomal(Seed); + + /* + // Performance-testing: + LOGINFO("Measuring performance of cCompoGenBiomal..."); + clock_t BeginTick = clock(); + for (int x = 0; x < 500; x++) + { + cChunkDesc Desc(200 + x * 8, 200 + x * 8); + m_BiomeGen->GenBiomes(Desc.GetChunkX(), Desc.GetChunkZ(), Desc.GetBiomeMap()); + m_HeightGen->GenHeightMap(Desc.GetChunkX(), Desc.GetChunkZ(), Desc.GetHeightMap()); + m_CompositionGen->ComposeTerrain(Desc); + } + clock_t Duration = clock() - BeginTick; + LOGINFO("CompositionGen for 500 chunks took %d ticks (%.02f sec)", Duration, (double)Duration / CLOCKS_PER_SEC); + //*/ + } + + // Read the settings from the ini file: + m_CompositionGen->InitializeCompoGen(a_IniFile); + + int CompoGenCacheSize = a_IniFile.GetValueSetI("Generator", "CompositionGenCacheSize", 64); + if (CompoGenCacheSize > 1) + { + m_UnderlyingCompositionGen = m_CompositionGen; + m_CompositionGen = new cCompoGenCache(*m_UnderlyingCompositionGen, 32); + } +} + + + + + +void cComposableGenerator::InitStructureGens(cIniFile & a_IniFile) +{ + AString Structures = a_IniFile.GetValueSet("Generator", "Structures", "Ravines, WormNestCaves, WaterLakes, LavaLakes, OreNests, Trees"); + + int Seed = m_ChunkGenerator.GetSeed(); + AStringVector Str = StringSplitAndTrim(Structures, ","); + for (AStringVector::const_iterator itr = Str.begin(); itr != Str.end(); ++itr) + { + if (NoCaseCompare(*itr, "DualRidgeCaves") == 0) + { + float Threshold = (float)a_IniFile.GetValueSetF("Generator", "DualRidgeCavesThreshold", 0.3); + m_StructureGens.push_back(new cStructGenDualRidgeCaves(Seed, Threshold)); + } + else if (NoCaseCompare(*itr, "DirectOverhangs") == 0) + { + m_StructureGens.push_back(new cStructGenDirectOverhangs(Seed)); + } + else if (NoCaseCompare(*itr, "DistortedMembraneOverhangs") == 0) + { + m_StructureGens.push_back(new cStructGenDistortedMembraneOverhangs(Seed)); + } + else if (NoCaseCompare(*itr, "LavaLakes") == 0) + { + int Probability = a_IniFile.GetValueSetI("Generator", "LavaLakesProbability", 10); + m_StructureGens.push_back(new cStructGenLakes(Seed * 5 + 16873, E_BLOCK_STATIONARY_LAVA, *m_HeightGen, Probability)); + } + else if (NoCaseCompare(*itr, "MarbleCaves") == 0) + { + m_StructureGens.push_back(new cStructGenMarbleCaves(Seed)); + } + else if (NoCaseCompare(*itr, "MineShafts") == 0) + { + int GridSize = a_IniFile.GetValueSetI("Generator", "MineShaftsGridSize", 512); + int MaxSystemSize = a_IniFile.GetValueSetI("Generator", "MineShaftsMaxSystemSize", 160); + int ChanceCorridor = a_IniFile.GetValueSetI("Generator", "MineShaftsChanceCorridor", 600); + int ChanceCrossing = a_IniFile.GetValueSetI("Generator", "MineShaftsChanceCrossing", 200); + int ChanceStaircase = a_IniFile.GetValueSetI("Generator", "MineShaftsChanceStaircase", 200); + m_StructureGens.push_back(new cStructGenMineShafts( + Seed, GridSize, MaxSystemSize, + ChanceCorridor, ChanceCrossing, ChanceStaircase + )); + } + else if (NoCaseCompare(*itr, "OreNests") == 0) + { + m_StructureGens.push_back(new cStructGenOreNests(Seed)); + } + else if (NoCaseCompare(*itr, "Ravines") == 0) + { + m_StructureGens.push_back(new cStructGenRavines(Seed, 128)); + } + else if (NoCaseCompare(*itr, "Trees") == 0) + { + m_StructureGens.push_back(new cStructGenTrees(Seed, m_BiomeGen, m_HeightGen, m_CompositionGen)); + } + else if (NoCaseCompare(*itr, "WaterLakes") == 0) + { + int Probability = a_IniFile.GetValueSetI("Generator", "WaterLakesProbability", 25); + m_StructureGens.push_back(new cStructGenLakes(Seed * 3 + 652, E_BLOCK_STATIONARY_WATER, *m_HeightGen, Probability)); + } + else if (NoCaseCompare(*itr, "WormNestCaves") == 0) + { + m_StructureGens.push_back(new cStructGenWormNestCaves(Seed)); + } + else + { + LOGWARNING("Unknown structure generator: \"%s\". Ignoring.", itr->c_str()); + } + } // for itr - Str[] +} + + + + + +void cComposableGenerator::InitFinishGens(cIniFile & a_IniFile) +{ + int Seed = m_ChunkGenerator.GetSeed(); + AString Structures = a_IniFile.GetValueSet("Generator", "Finishers", "SprinkleFoliage,Ice,Snow,Lilypads,BottomLava,DeadBushes,PreSimulator"); + + AStringVector Str = StringSplitAndTrim(Structures, ","); + for (AStringVector::const_iterator itr = Str.begin(); itr != Str.end(); ++itr) + { + // Finishers, alpha-sorted: + if (NoCaseCompare(*itr, "BottomLava") == 0) + { + int DefaultBottomLavaLevel = (m_World->GetDimension() == dimNether) ? 30 : 10; + int BottomLavaLevel = a_IniFile.GetValueSetI("Generator", "BottomLavaLevel", DefaultBottomLavaLevel); + m_FinishGens.push_back(new cFinishGenBottomLava(BottomLavaLevel)); + } + else if (NoCaseCompare(*itr, "DeadBushes") == 0) + { + m_FinishGens.push_back(new cFinishGenSingleBiomeSingleTopBlock(Seed, E_BLOCK_DEAD_BUSH, biDesert, 2, E_BLOCK_SAND, E_BLOCK_SAND)); + } + else if (NoCaseCompare(*itr, "Ice") == 0) + { + m_FinishGens.push_back(new cFinishGenIce); + } + else if (NoCaseCompare(*itr, "LavaSprings") == 0) + { + m_FinishGens.push_back(new cFinishGenFluidSprings(Seed, E_BLOCK_LAVA, a_IniFile, *m_World)); + } + else if (NoCaseCompare(*itr, "Lilypads") == 0) + { + m_FinishGens.push_back(new cFinishGenSingleBiomeSingleTopBlock(Seed, E_BLOCK_LILY_PAD, biSwampland, 4, E_BLOCK_WATER, E_BLOCK_STATIONARY_WATER)); + } + else if (NoCaseCompare(*itr, "PreSimulator") == 0) + { + m_FinishGens.push_back(new cFinishGenPreSimulator); + } + else if (NoCaseCompare(*itr, "Snow") == 0) + { + m_FinishGens.push_back(new cFinishGenSnow); + } + else if (NoCaseCompare(*itr, "SprinkleFoliage") == 0) + { + m_FinishGens.push_back(new cFinishGenSprinkleFoliage(Seed)); + } + else if (NoCaseCompare(*itr, "WaterSprings") == 0) + { + m_FinishGens.push_back(new cFinishGenFluidSprings(Seed, E_BLOCK_WATER, a_IniFile, *m_World)); + } + } // for itr - Str[] +} + + + + diff --git a/src/Generating/ComposableGenerator.h b/src/Generating/ComposableGenerator.h new file mode 100644 index 000000000..732f64303 --- /dev/null +++ b/src/Generating/ComposableGenerator.h @@ -0,0 +1,188 @@ + +// ComposableGenerator.h + +// Declares the cComposableGenerator class representing the chunk generator that takes the composition approach to generating chunks + +/* +Generating works by composing several algorithms: +Biome, TerrainHeight, TerrainComposition, Ores, Structures and SmallFoliage +Each algorithm may be chosen from a pool of available algorithms in the same class and combined with others, +based on user's preferences in the world.ini. +See http://forum.mc-server.org/showthread.php?tid=409 for details. +*/ + + + + + +#pragma once + +#include "ChunkGenerator.h" +#include "ChunkDesc.h" + + + + + +// fwd: Noise3DGenerator.h +class cNoise3DComposable; + +// fwd: DistortedHeightmap.h +class cDistortedHeightmap; + + + + + +/** The interface that a biome generator must implement +A biome generator takes chunk coords on input and outputs an array of biome indices for that chunk on output. +The output array is sequenced in the same way as the MapChunk packet's biome data. +*/ +class cBiomeGen +{ +public: + virtual ~cBiomeGen() {} // Force a virtual destructor in descendants + + /// Generates biomes for the given chunk + virtual void GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) = 0; + + /// Reads parameters from the ini file, prepares generator for use. + virtual void InitializeBiomeGen(cIniFile & a_IniFile) {} + + /// Creates the correct BiomeGen descendant based on the ini file settings and the seed provided. + /// a_CacheOffByDefault gets set to whether the cache should be enabled by default + /// Used in BiomeVisualiser, too. + /// Implemented in BioGen.cpp! + static cBiomeGen * CreateBiomeGen(cIniFile & a_IniFile, int a_Seed, bool & a_CacheOffByDefault); + +} ; + + + + + +/** The interface that a terrain height generator must implement +A terrain height generator takes chunk coords on input and outputs an array of terrain heights for that chunk. +The output array is sequenced in the same way as the BiomeGen's biome data. +The generator may request biome information from the underlying BiomeGen, it may even request information for +other chunks than the one it's currently generating (possibly neighbors - for averaging) +*/ +class cTerrainHeightGen +{ +public: + virtual ~cTerrainHeightGen() {} // Force a virtual destructor in descendants + + /// Generates heightmap for the given chunk + virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) = 0; + + /// Reads parameters from the ini file, prepares generator for use. + virtual void InitializeHeightGen(cIniFile & a_IniFile) {} +} ; + + + + + +/** The interface that a terrain composition generator must implement +Terrain composition takes chunk coords on input and outputs the blockdata for that entire chunk, along with +the list of entities. It is supposed to make use of the underlying TerrainHeightGen and BiomeGen for that purpose, +but it may request information for other chunks than the one it's currently generating from them. +*/ +class cTerrainCompositionGen +{ +public: + virtual ~cTerrainCompositionGen() {} // Force a virtual destructor in descendants + + virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc) = 0; + + /// Reads parameters from the ini file, prepares generator for use. + virtual void InitializeCompoGen(cIniFile & a_IniFile) {} +} ; + + + + + +/** The interface that a structure generator must implement +Structures are generated after the terrain composition took place. It should modify the blocktype data to account +for whatever structures the generator is generating. +Note that ores are considered structures too, at least from the interface point of view. +Also note that a worldgenerator may contain multiple structure generators, one for each type of structure +*/ +class cStructureGen +{ +public: + virtual ~cStructureGen() {} // Force a virtual destructor in descendants + + virtual void GenStructures(cChunkDesc & a_ChunkDesc) = 0; +} ; + +typedef std::list<cStructureGen *> cStructureGenList; + + + + + +/** The interface that a finisher must implement +Finisher implements small additions after all structures have been generated. +*/ +class cFinishGen +{ +public: + virtual ~cFinishGen() {} // Force a virtual destructor in descendants + + virtual void GenFinish(cChunkDesc & a_ChunkDesc) = 0; +} ; + +typedef std::list<cFinishGen *> cFinishGenList; + + + + + +class cComposableGenerator : + public cChunkGenerator::cGenerator +{ + typedef cChunkGenerator::cGenerator super; + +public: + cComposableGenerator(cChunkGenerator & a_ChunkGenerator); + virtual ~cComposableGenerator(); + + virtual void Initialize(cWorld * a_World, cIniFile & a_IniFile) override; + virtual void GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override; + virtual void DoGenerate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) override; + +protected: + // The generation composition: + cBiomeGen * m_BiomeGen; + cTerrainHeightGen * m_HeightGen; + cTerrainCompositionGen * m_CompositionGen; + cStructureGenList m_StructureGens; + cFinishGenList m_FinishGens; + + // Generators underlying the caches: + cBiomeGen * m_UnderlyingBiomeGen; + cTerrainHeightGen * m_UnderlyingHeightGen; + cTerrainCompositionGen * m_UnderlyingCompositionGen; + + + /// Reads the biome gen settings from the ini and initializes m_BiomeGen accordingly + void InitBiomeGen(cIniFile & a_IniFile); + + /// Reads the HeightGen settings from the ini and initializes m_HeightGen accordingly + void InitHeightGen(cIniFile & a_IniFile); + + /// Reads the CompositionGen settings from the ini and initializes m_CompositionGen accordingly + void InitCompositionGen(cIniFile & a_IniFile); + + /// Reads the structures to generate from the ini and initializes m_StructureGens accordingly + void InitStructureGens(cIniFile & a_IniFile); + + /// Reads the finishers from the ini and initializes m_FinishGens accordingly + void InitFinishGens(cIniFile & a_IniFile); +} ; + + + + diff --git a/src/Generating/DistortedHeightmap.cpp b/src/Generating/DistortedHeightmap.cpp new file mode 100644 index 000000000..a61d79bec --- /dev/null +++ b/src/Generating/DistortedHeightmap.cpp @@ -0,0 +1,898 @@ + +// DistortedHeightmap.cpp + +// Implements the cDistortedHeightmap class representing the height and composition generator capable of overhangs + +#include "Globals.h" + +#include "DistortedHeightmap.h" +#include "../OSSupport/File.h" +#include "inifile/iniFile.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 +class cPattern +{ +public: + cPattern(cDistortedHeightmap::sBlockInfo * 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 cDistortedHeightmap::sBlockInfo Stone = {E_BLOCK_STONE, 0}; + for (size_t i = a_Count; i < cChunkDef::Height; i++) + { + m_Pattern[i] = Stone; + } + } + + const cDistortedHeightmap::sBlockInfo * Get(void) const { return m_Pattern; } + +protected: + cDistortedHeightmap::sBlockInfo m_Pattern[cChunkDef::Height]; +} ; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// The arrays to use for the top block pattern definitions: + +static cDistortedHeightmap::sBlockInfo 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 cDistortedHeightmap::sBlockInfo tbSand[] = +{ + { E_BLOCK_SAND, 0}, + { E_BLOCK_SAND, 0}, + { E_BLOCK_SAND, 0}, + { E_BLOCK_SANDSTONE, 0}, +} ; + +static cDistortedHeightmap::sBlockInfo 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 cDistortedHeightmap::sBlockInfo 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 cDistortedHeightmap::sBlockInfo 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 cDistortedHeightmap::sBlockInfo tbMycelium[] = +{ + {E_BLOCK_MYCELIUM, 0}, + {E_BLOCK_DIRT, 0}, + {E_BLOCK_DIRT, 0}, + {E_BLOCK_DIRT, 0}, +} ; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Ocean floor pattern top-block definitions: + +static cDistortedHeightmap::sBlockInfo tbOFSand[] = +{ + {E_BLOCK_SAND, 0}, + {E_BLOCK_SAND, 0}, + {E_BLOCK_SAND, 0}, + {E_BLOCK_SANDSTONE, 0} +} ; + +static cDistortedHeightmap::sBlockInfo tbOFClay[] = +{ + { E_BLOCK_CLAY, 0}, + { E_BLOCK_CLAY, 0}, + { E_BLOCK_SAND, 0}, + { E_BLOCK_SAND, 0}, +} ; + +static cDistortedHeightmap::sBlockInfo tbOFRedSand[] = +{ + { E_BLOCK_SAND, E_META_SAND_RED}, + { E_BLOCK_SAND, E_META_SAND_RED}, + { E_BLOCK_SAND, E_META_SAND_RED}, + { E_BLOCK_SANDSTONE, 0}, +} ; + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// 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 patOFSand (tbOFSand, ARRAYCOUNT(tbOFSand)); +static cPattern patOFClay (tbOFClay, ARRAYCOUNT(tbOFClay)); +static cPattern patOFRedSand(tbOFRedSand, ARRAYCOUNT(tbOFRedSand)); + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cDistortedHeightmap: + +/** This table assigns a relative maximum overhang size in each direction to biomes. +Both numbers indicate a number which will multiply the noise value for each coord; +this means that you can have different-sized overhangs in each direction. +Usually you'd want to keep both numbers the same. +The numbers are "relative", not absolute maximum; overhangs of a slightly larger size are possible +due to the way that noise is calculated. +*/ +const cDistortedHeightmap::sGenParam cDistortedHeightmap::m_GenParam[256] = +{ + /* Biome | AmpX | AmpZ */ + /* biOcean */ { 1.5f, 1.5f}, + /* biPlains */ { 0.5f, 0.5f}, + /* biDesert */ { 0.5f, 0.5f}, + /* biExtremeHills */ {16.0f, 16.0f}, + /* biForest */ { 3.0f, 3.0f}, + /* biTaiga */ { 1.5f, 1.5f}, + + /* biSwampland */ { 0.0f, 0.0f}, + /* biRiver */ { 0.0f, 0.0f}, + /* biNether */ { 0.0f, 0.0f}, // Unused, but must be here due to indexing + /* biSky */ { 0.0f, 0.0f}, // Unused, but must be here due to indexing + /* biFrozenOcean */ { 0.0f, 0.0f}, + /* biFrozenRiver */ { 0.0f, 0.0f}, + /* biIcePlains */ { 0.0f, 0.0f}, + /* biIceMountains */ { 8.0f, 8.0f}, + /* biMushroomIsland */ { 4.0f, 4.0f}, + /* biMushroomShore */ { 0.0f, 0.0f}, + /* biBeach */ { 0.0f, 0.0f}, + /* biDesertHills */ { 5.0f, 5.0f}, + /* biForestHills */ { 6.0f, 6.0f}, + /* biTaigaHills */ { 8.0f, 8.0f}, + /* biExtremeHillsEdge */ { 7.0f, 7.0f}, + /* biJungle */ { 4.0f, 4.0f}, + /* biJungleHills */ { 8.0f, 8.0f}, + /* biJungleEdge */ { 3.0f, 3.0f}, // 23 + /* biDeepOcean */ { 1.0f, 1.0f}, // 24 + /* biStoneBeach */ { 1.0f, 1.0f}, // 25 + /* biColdBeach */ { 1.0f, 1.0f}, // 26 + /* biBirchForest */ { 3.0f, 3.0f}, // 27 + /* biBirchForestHills */ { 6.0f, 6.0f}, // 28 + /* biRoofedForest */ { 3.0f, 3.0f}, // 29 + /* biColdTaiga */ { 0.5f, 0.5f}, // 30 + /* biColdTaigaHills */ { 4.0f, 4.0f}, // 31 + /* biMegaTaiga */ { 1.0f, 1.0f}, // 32 + /* biMegaTaigaHills */ { 4.0f, 4.0f}, // 33 + /* biExtremeHillsPlus */ {32.0f, 32.0f}, // 34 - anyone say extreme plus? Make it extreme plus, then :) + /* biSavanna */ { 2.0f, 2.0f}, // 35 + /* biSavannaPlateau */ { 3.0f, 3.0f}, // 36 + /* biMesa */ { 2.0f, 2.0f}, // 37 + /* biMesaPlateauF */ { 2.0f, 2.0f}, // 38 + /* biMesaPlateau */ { 2.0f, 2.0f}, // 39 + + // biomes 40 .. 128 are unused, 89 empty placeholders here: + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 40 .. 49 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 50 .. 59 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 60 .. 69 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 70 .. 79 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 80 .. 89 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 90 .. 99 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 100 .. 109 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 110 .. 119 + {}, {}, {}, {}, {}, {}, {}, {}, {}, // 120 .. 128 + + // Release 1.7 /* biome variants: + /* biSunflowerPlains */ { 1.0f, 1.0f}, // 129 + /* biDesertM */ { 1.0f, 1.0f}, // 130 + /* biExtremeHillsM */ {16.0f, 16.0f}, // 131 + /* biFlowerForest */ { 4.0f, 4.0f}, // 132 + /* biTaigaM */ { 3.0f, 3.0f}, // 133 + /* biSwamplandM */ { 0.0f, 0.0f}, // 134 + + // Biomes 135 .. 139 unused, 5 empty placeholders here: + {}, {}, {}, {}, {}, // 135 .. 139 + + /* biIcePlainsSpikes */ { 1.0f, 1.0f}, // 140 + + // Biomes 141 .. 148 unused, 8 empty placeholders here: + {}, {}, {}, {}, {}, {}, {}, {}, // 141 .. 148 + + /* biJungleM */ { 4.0f, 4.0f}, // 149 + {}, // 150 + /* biJungleEdgeM */ { 3.0f, 3.0f}, // 151 + {}, {}, {}, // 152 .. 154 + /* biBirchForestM */ { 3.0f, 3.0f}, // 155 + /* biBirchForestHillsM */ { 5.0f, 5.0f}, // 156 + /* biRoofedForestM */ { 2.0f, 2.0f}, // 157 + /* biColdTaigaM */ { 1.0f, 1.0f}, // 158 + {}, // 159 + /* biMegaSpruceTaiga */ { 3.0f, 3.0f}, // 160 + /* biMegaSpruceTaigaHills */ { 3.0f, 3.0f}, // 161 + /* biExtremeHillsPlusM */ {32.0f, 32.0f}, // 162 + /* biSavannaM */ { 2.0f, 2.0f}, // 163 + /* biSavannaPlateauM */ { 3.0f, 3.0f}, // 164 + /* biMesaBryce */ { 0.5f, 0.5f}, // 165 + /* biMesaPlateauFM */ { 2.0f, 2.0f}, // 166 + /* biMesaPlateauM */ { 2.0f, 2.0f}, // 167 +} ; + + + + + +cDistortedHeightmap::cDistortedHeightmap(int a_Seed, cBiomeGen & a_BiomeGen) : + m_NoiseDistortX(a_Seed + 1000), + m_NoiseDistortZ(a_Seed + 2000), + m_OceanFloorSelect(a_Seed + 3000), + m_MesaFloor(a_Seed + 4000), + m_BiomeGen(a_BiomeGen), + m_UnderlyingHeiGen(a_Seed, a_BiomeGen), + m_HeightGen(m_UnderlyingHeiGen, 64), + m_IsInitialized(false) +{ + m_NoiseDistortX.AddOctave((NOISE_DATATYPE)1, (NOISE_DATATYPE)0.5); + m_NoiseDistortX.AddOctave((NOISE_DATATYPE)0.5, (NOISE_DATATYPE)1); + m_NoiseDistortX.AddOctave((NOISE_DATATYPE)0.25, (NOISE_DATATYPE)2); + + m_NoiseDistortZ.AddOctave((NOISE_DATATYPE)1, (NOISE_DATATYPE)0.5); + m_NoiseDistortZ.AddOctave((NOISE_DATATYPE)0.5, (NOISE_DATATYPE)1); + m_NoiseDistortZ.AddOctave((NOISE_DATATYPE)0.25, (NOISE_DATATYPE)2); + + InitMesaPattern(a_Seed); +} + + + + + +void cDistortedHeightmap::Initialize(cIniFile & a_IniFile) +{ + if (m_IsInitialized) + { + return; + } + + // Read the params from the INI file: + m_SeaLevel = a_IniFile.GetValueSetI("Generator", "DistortedHeightmapSeaLevel", 62); + m_FrequencyX = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "DistortedHeightmapFrequencyX", 10); + m_FrequencyY = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "DistortedHeightmapFrequencyY", 10); + m_FrequencyZ = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "DistortedHeightmapFrequencyZ", 10); + + m_IsInitialized = true; +} + + + + + +void cDistortedHeightmap::InitMesaPattern(int a_Seed) +{ + // Stone in the bottom half of the pattern: + for (int i = cChunkDef::Height; i < 2 * cChunkDef::Height; i++) + { + m_MesaPattern[i].BlockMeta = 0; + m_MesaPattern[i].BlockType = E_BLOCK_STONE; + } + + // Stained and hardened clay in the top half of the pattern + // In a loop, choose whether to use one or two 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 = cChunkDef::Height - 1; + while (Idx >= 0) + { + // A layer group of 1 - 2 color stained clay: + int Random = PatternNoise.IntNoise1DInt(Idx) / 7; + int NumLayers = (Random % 2) + 1; + Random /= 2; + for (int Lay = 0; Lay < NumLayers; Lay++) + { + int NumBlocks = LayerSizes[(Random % ARRAYCOUNT(LayerSizes))]; + NIBBLETYPE Color = AllowedColors[(Random / 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 + Random /= 32; + for (int Block = 0; Block < NumBlocks; Block++, Idx--) + { + m_MesaPattern[Idx].BlockMeta = Color; + m_MesaPattern[Idx].BlockType = E_BLOCK_STAINED_CLAY; + } // for Block + } // for Lay + + // A layer of hardened clay in between the layer group: + int NumBlocks = (Random % 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].BlockMeta = 0; + m_MesaPattern[Idx].BlockType = E_BLOCK_HARDENED_CLAY; + } // for Block + } // while (Idx >= 0) +} + + + + + +void cDistortedHeightmap::PrepareState(int a_ChunkX, int a_ChunkZ) +{ + if ((m_CurChunkX == a_ChunkX) && (m_CurChunkZ == a_ChunkZ)) + { + return; + } + m_CurChunkX = a_ChunkX; + m_CurChunkZ = a_ChunkZ; + + + m_HeightGen.GenHeightMap(a_ChunkX, a_ChunkZ, m_CurChunkHeights); + UpdateDistortAmps(); + GenerateHeightArray(); +} + + + + + +void cDistortedHeightmap::GenerateHeightArray(void) +{ + // Generate distortion noise: + NOISE_DATATYPE DistortNoiseX[DIM_X * DIM_Y * DIM_Z]; + NOISE_DATATYPE DistortNoiseZ[DIM_X * DIM_Y * DIM_Z]; + NOISE_DATATYPE Workspace[DIM_X * DIM_Y * DIM_Z]; + NOISE_DATATYPE StartX = ((NOISE_DATATYPE)(m_CurChunkX * cChunkDef::Width)) / m_FrequencyX; + NOISE_DATATYPE EndX = ((NOISE_DATATYPE)((m_CurChunkX + 1) * cChunkDef::Width - 1)) / m_FrequencyX; + NOISE_DATATYPE StartY = 0; + NOISE_DATATYPE EndY = ((NOISE_DATATYPE)(257)) / m_FrequencyY; + NOISE_DATATYPE StartZ = ((NOISE_DATATYPE)(m_CurChunkZ * cChunkDef::Width)) / m_FrequencyZ; + NOISE_DATATYPE EndZ = ((NOISE_DATATYPE)((m_CurChunkZ + 1) * cChunkDef::Width - 1)) / m_FrequencyZ; + + m_NoiseDistortX.Generate3D(DistortNoiseX, DIM_X, DIM_Y, DIM_Z, StartX, EndX, StartY, EndY, StartZ, EndZ, Workspace); + m_NoiseDistortZ.Generate3D(DistortNoiseZ, DIM_X, DIM_Y, DIM_Z, StartX, EndX, StartY, EndY, StartZ, EndZ, Workspace); + + // The distorted heightmap, before linear upscaling + NOISE_DATATYPE DistHei[DIM_X * DIM_Y * DIM_Z]; + + // Distort the heightmap using the distortion: + for (int z = 0; z < DIM_Z; z++) + { + int AmpIdx = z * DIM_X; + for (int y = 0; y < DIM_Y; y++) + { + int NoiseArrayIdx = z * DIM_X * DIM_Y + y * DIM_X; + for (int x = 0; x < DIM_X; x++) + { + NOISE_DATATYPE DistX = DistortNoiseX[NoiseArrayIdx + x] * m_DistortAmpX[AmpIdx + x]; + NOISE_DATATYPE DistZ = DistortNoiseZ[NoiseArrayIdx + x] * m_DistortAmpZ[AmpIdx + x]; + DistX += (NOISE_DATATYPE)(m_CurChunkX * cChunkDef::Width + x * INTERPOL_X); + DistZ += (NOISE_DATATYPE)(m_CurChunkZ * cChunkDef::Width + z * INTERPOL_Z); + // Adding 0.5 helps alleviate the interpolation artifacts + DistHei[NoiseArrayIdx + x] = (NOISE_DATATYPE)GetHeightmapAt(DistX, DistZ) + (NOISE_DATATYPE)0.5; + } + } + } + + // Upscale the distorted heightmap into full dimensions: + LinearUpscale3DArray( + DistHei, DIM_X, DIM_Y, DIM_Z, + m_DistortedHeightmap, INTERPOL_X, INTERPOL_Y, INTERPOL_Z + ); + + // DEBUG: Debug3DNoise(m_DistortedHeightmap, 17, 257, 17, Printf("DistortedHeightmap_%d_%d", m_CurChunkX, m_CurChunkZ)); +} + + + + + +void cDistortedHeightmap::GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) +{ + PrepareState(a_ChunkX, a_ChunkZ); + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + int NoiseArrayIdx = x + 17 * 257 * z; + cChunkDef::SetHeight(a_HeightMap, x, z, m_SeaLevel - 1); + for (int y = cChunkDef::Height - 1; y > m_SeaLevel - 1; y--) + { + int HeightMapHeight = (int)m_DistortedHeightmap[NoiseArrayIdx + 17 * y]; + if (y < HeightMapHeight) + { + cChunkDef::SetHeight(a_HeightMap, x, z, y); + break; + } + } // for y + } // for x + } // for z +} + + + + + +void cDistortedHeightmap::InitializeHeightGen(cIniFile & a_IniFile) +{ + Initialize(a_IniFile); +} + + + + + +void cDistortedHeightmap::ComposeTerrain(cChunkDesc & a_ChunkDesc) +{ + // Prepare the internal state for generating this chunk: + PrepareState(a_ChunkDesc.GetChunkX(), a_ChunkDesc.GetChunkZ()); + + // Compose: + 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); + } // for x + } // for z +} + + + + + +void cDistortedHeightmap::InitializeCompoGen(cIniFile & a_IniFile) +{ + Initialize(a_IniFile); +} + + + + + +int cDistortedHeightmap::GetHeightmapAt(NOISE_DATATYPE a_X, NOISE_DATATYPE a_Z) +{ + int ChunkX = (int)floor(a_X / (NOISE_DATATYPE)16); + int ChunkZ = (int)floor(a_Z / (NOISE_DATATYPE)16); + int RelX = (int)(a_X - (NOISE_DATATYPE)ChunkX * cChunkDef::Width); + int RelZ = (int)(a_Z - (NOISE_DATATYPE)ChunkZ * cChunkDef::Width); + + // If we're withing the same chunk, return the pre-cached heightmap: + if ((ChunkX == m_CurChunkX) && (ChunkZ == m_CurChunkZ)) + { + return cChunkDef::GetHeight(m_CurChunkHeights, RelX, RelZ); + } + + // Ask the cache: + HEIGHTTYPE res = 0; + if (m_HeightGen.GetHeightAt(ChunkX, ChunkZ, RelX, RelZ, res)) + { + // The height was in the cache + return res; + } + + // The height is not in the cache, generate full heightmap and get it there: + cChunkDef::HeightMap Heightmap; + m_HeightGen.GenHeightMap(ChunkX, ChunkZ, Heightmap); + return cChunkDef::GetHeight(Heightmap, RelX, RelZ); +} + + + + + +void cDistortedHeightmap::UpdateDistortAmps(void) +{ + BiomeNeighbors Biomes; + for (int z = -1; z <= 1; z++) + { + for (int x = -1; x <= 1; x++) + { + m_BiomeGen.GenBiomes(m_CurChunkX + x, m_CurChunkZ + z, Biomes[x + 1][z + 1]); + } // for x + } // for z + + for (int z = 0; z < DIM_Z; z++) + { + for (int x = 0; x < DIM_Z; x++) + { + GetDistortAmpsAt(Biomes, x * INTERPOL_X, z * INTERPOL_Z, m_DistortAmpX[x + DIM_X * z], m_DistortAmpZ[x + DIM_X * z]); + } + } +} + + + + + +void cDistortedHeightmap::GetDistortAmpsAt(BiomeNeighbors & a_Neighbors, int a_RelX, int a_RelZ, NOISE_DATATYPE & a_DistortAmpX, NOISE_DATATYPE & a_DistortAmpZ) +{ + // Sum up how many biomes of each type there are in the neighborhood: + int BiomeCounts[256]; + memset(BiomeCounts, 0, sizeof(BiomeCounts)); + int Sum = 0; + for (int z = -8; z <= 8; z++) + { + int FinalZ = a_RelZ + z + cChunkDef::Width; + int IdxZ = FinalZ / cChunkDef::Width; + int ModZ = FinalZ % cChunkDef::Width; + int WeightZ = 9 - abs(z); + for (int x = -8; x <= 8; x++) + { + int FinalX = a_RelX + x + cChunkDef::Width; + int IdxX = FinalX / cChunkDef::Width; + int ModX = FinalX % cChunkDef::Width; + EMCSBiome Biome = cChunkDef::GetBiome(a_Neighbors[IdxX][IdxZ], ModX, ModZ); + int WeightX = 9 - abs(x); + BiomeCounts[Biome] += WeightX + WeightZ; + Sum += WeightX + WeightZ; + } // for x + } // for z + + if (Sum <= 0) + { + // No known biome around? Weird. Return a bogus value: + ASSERT(!"cHeiGenBiomal: Biome sum failed, no known biome around"); + a_DistortAmpX = 16; + a_DistortAmpZ = 16; + } + + // For each biome type that has a nonzero count, calc its amps and add it: + NOISE_DATATYPE AmpX = 0; + NOISE_DATATYPE AmpZ = 0; + for (unsigned int i = 0; i < ARRAYCOUNT(BiomeCounts); i++) + { + if (BiomeCounts[i] <= 0) + { + continue; + } + + /* + // Sanity checks for biome parameters, enable them to check the biome param table in runtime (slow): + ASSERT(m_GenParam[i].m_DistortAmpX >= 0); + ASSERT(m_GenParam[i].m_DistortAmpX < 100); + ASSERT(m_GenParam[i].m_DistortAmpX >= 0); + ASSERT(m_GenParam[i].m_DistortAmpX < 100); + */ + + AmpX += BiomeCounts[i] * m_GenParam[i].m_DistortAmpX; + AmpZ += BiomeCounts[i] * m_GenParam[i].m_DistortAmpZ; + } + a_DistortAmpX = AmpX / Sum; + a_DistortAmpZ = AmpZ / Sum; +} + + + + +void cDistortedHeightmap::ComposeColumn(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelZ) +{ + // 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 biExtremeHills: + case biForest: + case biTaiga: + case biSwampland: + case biRiver: + case biFrozenOcean: + case biFrozenRiver: + case biIcePlains: + case biIceMountains: + case biForestHills: + case biTaigaHills: + case biExtremeHillsEdge: + case biJungle: + case biJungleHills: + case biJungleEdge: + case biDeepOcean: + case biStoneBeach: + case biColdBeach: + case biBirchForest: + case biBirchForestHills: + case biRoofedForest: + case biColdTaiga: + case biColdTaigaHills: + case biExtremeHillsPlus: + case biSavanna: + case biSavannaPlateau: + case biSunflowerPlains: + case biExtremeHillsM: + case biFlowerForest: + case biTaigaM: + case biSwamplandM: + case biIcePlainsSpikes: + case biJungleM: + case biJungleEdgeM: + case biBirchForestM: + case biBirchForestHillsM: + case biRoofedForestM: + case biColdTaigaM: + case biExtremeHillsPlusM: + case biSavannaM: + case biSavannaPlateauM: + { + FillColumnPattern(a_ChunkDesc, a_RelX, a_RelZ, patGrass.Get()); + return; + } + + case biMegaTaiga: + case biMegaTaigaHills: + case biMegaSpruceTaiga: + case biMegaSpruceTaigaHills: + { + // Select the pattern to use - podzol, grass or grassless dirt: + NOISE_DATATYPE NoiseX = ((NOISE_DATATYPE)(m_CurChunkX * cChunkDef::Width + a_RelX)) / FrequencyX; + NOISE_DATATYPE NoiseY = ((NOISE_DATATYPE)(m_CurChunkZ * cChunkDef::Width + a_RelZ)) / FrequencyZ; + NOISE_DATATYPE Val = m_OceanFloorSelect.CubicNoise2D(NoiseX, NoiseY); + const sBlockInfo * Pattern = (Val < -0.9) ? patGrassLess.Get() : ((Val > 0) ? patPodzol.Get() : patGrass.Get()); + FillColumnPattern(a_ChunkDesc, a_RelX, a_RelZ, Pattern); + return; + } + + case biDesertHills: + case biDesert: + case biDesertM: + case biBeach: + { + FillColumnPattern(a_ChunkDesc, a_RelX, a_RelZ, patSand.Get()); + return; + } + + case biMushroomIsland: + case biMushroomShore: + { + FillColumnPattern(a_ChunkDesc, a_RelX, a_RelZ, patMycelium.Get()); + 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); + return; + } + + } // switch (Biome) + ASSERT(!"Unhandled biome"); +} + + + + + +void cDistortedHeightmap::FillColumnPattern(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelZ, const sBlockInfo * a_Pattern) +{ + int NoiseArrayIdx = a_RelX + 17 * 257 * a_RelZ; + bool HasHadWater = false; + int PatternIdx = 0; + for (int y = a_ChunkDesc.GetHeight(a_RelX, a_RelZ); y > 0; y--) + { + int HeightMapHeight = (int)m_DistortedHeightmap[NoiseArrayIdx + 17 * y]; + + if (y < HeightMapHeight) + { + // "ground" part, use the pattern: + a_ChunkDesc.SetBlockTypeMeta(a_RelX, y, a_RelZ, a_Pattern[PatternIdx].BlockType, a_Pattern[PatternIdx].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: + a_Pattern = ChooseOceanFloorPattern(a_RelX, a_RelZ); + HasHadWater = true; + } // for y + a_ChunkDesc.SetBlockType(a_RelX, 0, a_RelZ, E_BLOCK_BEDROCK); +} + + + + + +void cDistortedHeightmap::FillColumnMesa(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelZ) +{ + // 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: + FillColumnPattern(a_ChunkDesc, a_RelX, a_RelZ, patOFRedSand.Get()); + return; + } + + NOISE_DATATYPE NoiseX = ((NOISE_DATATYPE)(m_CurChunkX * cChunkDef::Width + a_RelX)) / FrequencyX; + NOISE_DATATYPE NoiseY = ((NOISE_DATATYPE)(m_CurChunkZ * 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 NoiseArrayIdx = a_RelX + 17 * 257 * a_RelZ; + int PatternIdx = cChunkDef::Height - (Top - ClayFloor); // We want the block at index ClayFloor to be pattern's 256th block (first stone) + const sBlockInfo * Pattern = m_MesaPattern; + bool HasHadWater = false; + for (int y = Top; y > 0; y--) + { + int HeightMapHeight = (int)m_DistortedHeightmap[NoiseArrayIdx + 17 * y]; + if (y < HeightMapHeight) + { + // "ground" part, use the pattern: + a_ChunkDesc.SetBlockTypeMeta(a_RelX, y, a_RelZ, Pattern[PatternIdx].BlockType, Pattern[PatternIdx].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_RelX, a_RelZ); + HasHadWater = true; + } // for y + a_ChunkDesc.SetBlockType(a_RelX, 0, a_RelZ, E_BLOCK_BEDROCK); +} + + + + + +const cDistortedHeightmap::sBlockInfo * cDistortedHeightmap::ChooseOceanFloorPattern(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)(m_CurChunkX * cChunkDef::Width + a_RelX)) / FrequencyX; + NOISE_DATATYPE NoiseY = ((NOISE_DATATYPE)(m_CurChunkZ * 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(); + } +} + + + + diff --git a/src/Generating/DistortedHeightmap.h b/src/Generating/DistortedHeightmap.h new file mode 100644 index 000000000..e6b3c9d3f --- /dev/null +++ b/src/Generating/DistortedHeightmap.h @@ -0,0 +1,136 @@ + +// DistortedHeightmap.h + +// Declares the cDistortedHeightmap class representing the height and composition generator capable of overhangs + + + + + +#pragma once + +#include "ComposableGenerator.h" +#include "HeiGen.h" +#include "../Noise.h" + + + + + +#define NOISE_SIZE_Y (257 + 32) + + + + + +class cDistortedHeightmap : + public cTerrainHeightGen, + public cTerrainCompositionGen +{ +public: + /// Structure used for storing block patterns for columns + struct sBlockInfo + { + BLOCKTYPE BlockType; + NIBBLETYPE BlockMeta; + } ; + + cDistortedHeightmap(int a_Seed, cBiomeGen & a_BiomeGen); + +protected: + typedef cChunkDef::BiomeMap BiomeNeighbors[3][3]; + + // Linear upscaling step sizes, must be divisors of cChunkDef::Width and cChunkDef::Height, respectively: + static const int INTERPOL_X = 8; + static const int INTERPOL_Y = 4; + static const int INTERPOL_Z = 8; + + // Linear upscaling buffer dimensions, calculated from the step sizes: + static const int DIM_X = 1 + (17 / INTERPOL_X); + static const int DIM_Y = 1 + (257 / INTERPOL_Y); + static const int DIM_Z = 1 + (17 / INTERPOL_Z); + + cPerlinNoise m_NoiseDistortX; + cPerlinNoise m_NoiseDistortZ; + cNoise m_OceanFloorSelect; ///< Used for selecting between dirt and sand on the ocean floor + cNoise m_MesaFloor; ///< Used for the floor of the clay blocks in mesa biomes + + int m_SeaLevel; + NOISE_DATATYPE m_FrequencyX; + NOISE_DATATYPE m_FrequencyY; + NOISE_DATATYPE m_FrequencyZ; + + int m_CurChunkX; + int m_CurChunkZ; + NOISE_DATATYPE m_DistortedHeightmap[17 * 257 * 17]; + + cBiomeGen & m_BiomeGen; + cHeiGenBiomal m_UnderlyingHeiGen; // This generator provides us with base heightmap (before distortion) + cHeiGenCache m_HeightGen; // Cache above m_UnderlyingHeiGen + + /// Heightmap for the current chunk, before distortion (from m_HeightGen). Used for optimization. + cChunkDef::HeightMap m_CurChunkHeights; + + // Per-biome terrain generator parameters: + struct sGenParam + { + NOISE_DATATYPE m_DistortAmpX; + NOISE_DATATYPE m_DistortAmpZ; + } ; + static const sGenParam m_GenParam[256]; + + // Distortion amplitudes for each direction, before linear upscaling + NOISE_DATATYPE m_DistortAmpX[DIM_X * DIM_Z]; + NOISE_DATATYPE m_DistortAmpZ[DIM_X * DIM_Z]; + + /// True if Initialize() has been called. Used to initialize-once even with multiple init entrypoints (HeiGen / CompoGen) + bool m_IsInitialized; + + /// The vertical pattern to be used for mesa biomes. Seed-dependant. + /// One Height of pattern and one Height of stone to avoid checking pattern dimensions + sBlockInfo m_MesaPattern[2 * cChunkDef::Height]; + + + /// Initializes m_MesaPattern with a reasonable pattern of stained clay / hardened clay, based on the seed + void InitMesaPattern(int a_Seed); + + /// Unless the LastChunk coords are equal to coords given, prepares the internal state (noise arrays, heightmap) + void PrepareState(int a_ChunkX, int a_ChunkZ); + + /// Generates the m_DistortedHeightmap array for the current chunk + void GenerateHeightArray(void); + + /// Calculates the heightmap value (before distortion) at the specified (floating-point) coords + int GetHeightmapAt(NOISE_DATATYPE a_X, NOISE_DATATYPE a_Z); + + /// Updates m_DistortAmpX/Z[] based on m_CurChunkX and m_CurChunkZ + void UpdateDistortAmps(void); + + /// Calculates the X and Z distortion amplitudes based on the neighbors' biomes + void GetDistortAmpsAt(BiomeNeighbors & a_Neighbors, int a_RelX, int a_RelZ, NOISE_DATATYPE & a_DistortAmpX, NOISE_DATATYPE & a_DistortAmpZ); + + /// Reads the settings from the ini file. Skips reading if already initialized + void Initialize(cIniFile & a_IniFile); + + /// 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); + + /// 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 sBlockInfo * a_Pattern); + + /// Fills the specified column with mesa pattern, based on the column height + void FillColumnMesa(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelZ); + + /// Returns the pattern to use for an ocean floor in the specified column + const sBlockInfo * ChooseOceanFloorPattern(int a_RelX, int a_RelZ); + + + // cTerrainHeightGen overrides: + virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override; + virtual void InitializeHeightGen(cIniFile & a_IniFile) override; + + // cTerrainCompositionGen overrides: + virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc) override; + virtual void InitializeCompoGen(cIniFile & a_IniFile) override; +} ; diff --git a/src/Generating/EndGen.cpp b/src/Generating/EndGen.cpp new file mode 100644 index 000000000..1fa0fa121 --- /dev/null +++ b/src/Generating/EndGen.cpp @@ -0,0 +1,217 @@ + +// EndGen.cpp + +// Implements the cEndGen class representing the generator for the End, both as a HeightGen and CompositionGen + +#include "Globals.h" +#include "EndGen.h" +#include "inifile/iniFile.h" +#include "../LinearUpscale.h" + + + + + +enum +{ + // Interpolation cell size: + INTERPOL_X = 4, + INTERPOL_Y = 4, + INTERPOL_Z = 4, + + // Size of chunk data, downscaled before interpolation: + DIM_X = 16 / INTERPOL_X + 1, + DIM_Y = 256 / INTERPOL_Y + 1, + DIM_Z = 16 / INTERPOL_Z + 1, +} ; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cEndGen: + +cEndGen::cEndGen(int a_Seed) : + m_Seed(a_Seed), + m_IslandSizeX(256), + m_IslandSizeY(96), + m_IslandSizeZ(256), + m_FrequencyX(80), + m_FrequencyY(80), + m_FrequencyZ(80) +{ + m_Perlin.AddOctave(1, 1); + m_Perlin.AddOctave(2, 0.5); + m_Perlin.AddOctave(4, 0.25); +} + + + + + +void cEndGen::Initialize(cIniFile & a_IniFile) +{ + m_IslandSizeX = a_IniFile.GetValueSetI("Generator", "EndGenIslandSizeX", m_IslandSizeX); + m_IslandSizeY = a_IniFile.GetValueSetI("Generator", "EndGenIslandSizeY", m_IslandSizeY); + m_IslandSizeZ = a_IniFile.GetValueSetI("Generator", "EndGenIslandSizeZ", m_IslandSizeZ); + + m_FrequencyX = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "EndGenFrequencyX", m_FrequencyX); + m_FrequencyY = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "EndGenFrequencyY", m_FrequencyY); + m_FrequencyZ = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "EndGenFrequencyZ", m_FrequencyZ); + + // Recalculate the min and max chunk coords of the island + m_MaxChunkX = (m_IslandSizeX + cChunkDef::Width - 1) / cChunkDef::Width; + m_MinChunkX = -m_MaxChunkX; + m_MaxChunkZ = (m_IslandSizeZ + cChunkDef::Width - 1) / cChunkDef::Width; + m_MinChunkZ = -m_MaxChunkZ; +} + + + + + +/// Unless the LastChunk coords are equal to coords given, prepares the internal state (noise array) +void cEndGen::PrepareState(int a_ChunkX, int a_ChunkZ) +{ + ASSERT(!IsChunkOutsideRange(a_ChunkX, a_ChunkZ)); // Should be filtered before calling this function + + if ((m_LastChunkX == a_ChunkX) && (m_LastChunkZ == a_ChunkZ)) + { + return; + } + + m_LastChunkX = a_ChunkX; + m_LastChunkZ = a_ChunkZ; + + GenerateNoiseArray(); +} + + + + + +/// Generates the m_NoiseArray array for the current chunk +void cEndGen::GenerateNoiseArray(void) +{ + NOISE_DATATYPE NoiseData[DIM_X * DIM_Y * DIM_Z]; // [x + DIM_X * z + DIM_X * DIM_Z * y] + NOISE_DATATYPE Workspace[DIM_X * DIM_Y * DIM_Z]; // [x + DIM_X * z + DIM_X * DIM_Z * y] + + // Generate the downscaled noise: + NOISE_DATATYPE StartX = ((NOISE_DATATYPE)(m_LastChunkX * cChunkDef::Width)) / m_FrequencyX; + NOISE_DATATYPE EndX = ((NOISE_DATATYPE)((m_LastChunkX + 1) * cChunkDef::Width)) / m_FrequencyX; + NOISE_DATATYPE StartZ = ((NOISE_DATATYPE)(m_LastChunkZ * cChunkDef::Width)) / m_FrequencyZ; + NOISE_DATATYPE EndZ = ((NOISE_DATATYPE)((m_LastChunkZ + 1) * cChunkDef::Width)) / m_FrequencyZ; + NOISE_DATATYPE StartY = 0; + NOISE_DATATYPE EndY = ((NOISE_DATATYPE)257) / m_FrequencyY; + m_Perlin.Generate3D(NoiseData, DIM_X, DIM_Z, DIM_Y, StartX, EndX, StartZ, EndZ, StartY, EndY, Workspace); + + // Add distance: + int idx = 0; + for (int y = 0; y < DIM_Y; y++) + { + NOISE_DATATYPE ValY = (NOISE_DATATYPE)(2 * INTERPOL_Y * y - m_IslandSizeY) / m_IslandSizeY; + ValY = ValY * ValY; + for (int z = 0; z < DIM_Z; z++) + { + NOISE_DATATYPE ValZ = (NOISE_DATATYPE)(m_LastChunkZ * cChunkDef::Width + (z * cChunkDef::Width / (DIM_Z - 1))) / m_IslandSizeZ; + ValZ = ValZ * ValZ; + for (int x = 0; x < DIM_X; x++) + { + // NOISE_DATATYPE ValX = StartX + (EndX - StartX) * x / (DIM_X - 1); + NOISE_DATATYPE ValX = (NOISE_DATATYPE)(m_LastChunkX * cChunkDef::Width + (x * cChunkDef::Width / (DIM_X - 1))) / m_IslandSizeX; + ValX = ValX * ValX; + NoiseData[idx++] += ValX + ValZ + ValY; + } // for x + } // for z + } // for y + + // Upscale into real chunk size: + LinearUpscale3DArray(NoiseData, DIM_X, DIM_Z, DIM_Y, m_NoiseArray, INTERPOL_X, INTERPOL_Z, INTERPOL_Y); +} + + + + + +/// Returns true if the chunk is outside of the island's dimensions +bool cEndGen::IsChunkOutsideRange(int a_ChunkX, int a_ChunkZ) +{ + return ( + (a_ChunkX < m_MinChunkX) || (a_ChunkX > m_MaxChunkX) || + (a_ChunkZ < m_MinChunkZ) || (a_ChunkZ > m_MaxChunkZ) + ); +} + + + + + +void cEndGen::GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) +{ + if (IsChunkOutsideRange(a_ChunkX, a_ChunkZ)) + { + for (unsigned int i = 0; i < ARRAYCOUNT(a_HeightMap); i++) + { + a_HeightMap[i] = 0; + } + return; + } + + PrepareState(a_ChunkX, a_ChunkZ); + + int MaxY = std::min((int)(1.75 * m_IslandSizeY + 1), cChunkDef::Height - 1); + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + cChunkDef::SetHeight(a_HeightMap, x, z, MaxY); + for (int y = MaxY; y > 0; y--) + { + if (m_NoiseArray[y * 17 * 17 + z * 17 + x] <= 0) + { + cChunkDef::SetHeight(a_HeightMap, x, z, y); + break; + } + } // for y + } // for x + } // for z +} + + + + + +void cEndGen::ComposeTerrain(cChunkDesc & a_ChunkDesc) +{ + if (IsChunkOutsideRange(a_ChunkDesc.GetChunkX(), a_ChunkDesc.GetChunkZ())) + { + a_ChunkDesc.FillBlocks(E_BLOCK_AIR, 0); + return; + } + + PrepareState(a_ChunkDesc.GetChunkX(), a_ChunkDesc.GetChunkZ()); + + int MaxY = std::min((int)(1.75 * m_IslandSizeY + 1), cChunkDef::Height - 1); + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + for (int y = MaxY; y > 0; y--) + { + if (m_NoiseArray[y * 17 * 17 + z * 17 + x] <= 0) + { + a_ChunkDesc.SetBlockTypeMeta(x, y, z, E_BLOCK_END_STONE, 0); + } + else + { + a_ChunkDesc.SetBlockTypeMeta(x, y, z, E_BLOCK_AIR, 0); + } + } // for y + } // for x + } // for z +} + + + + diff --git a/src/Generating/EndGen.h b/src/Generating/EndGen.h new file mode 100644 index 000000000..4904a0e3d --- /dev/null +++ b/src/Generating/EndGen.h @@ -0,0 +1,69 @@ + +// EndGen.h + +// Declares the cEndGen class representing the generator for the End, both as a HeightGen and CompositionGen + + + + + +#pragma once + +#include "ComposableGenerator.h" +#include "../Noise.h" + + + + + +class cEndGen : + public cTerrainHeightGen, + public cTerrainCompositionGen +{ +public: + cEndGen(int a_Seed); + + void Initialize(cIniFile & a_IniFile); + +protected: + + /// Seed for the noise + int m_Seed; + + /// The Perlin noise used for generating + cPerlinNoise m_Perlin; + + // XYZ size of the "island", in blocks: + int m_IslandSizeX; + int m_IslandSizeY; + int m_IslandSizeZ; + + // XYZ Frequencies of the noise functions: + NOISE_DATATYPE m_FrequencyX; + NOISE_DATATYPE m_FrequencyY; + NOISE_DATATYPE m_FrequencyZ; + + // Minimum and maximum chunk coords for chunks inside the island area. Chunks outside won't get calculated at all + int m_MinChunkX, m_MaxChunkX; + int m_MinChunkZ, m_MaxChunkZ; + + // Noise array for the last chunk (in the noise range) + int m_LastChunkX; + int m_LastChunkZ; + NOISE_DATATYPE m_NoiseArray[17 * 17 * 257]; // x + 17 * z + 17 * 17 * y + + /// Unless the LastChunk coords are equal to coords given, prepares the internal state (noise array) + void PrepareState(int a_ChunkX, int a_ChunkZ); + + /// Generates the m_NoiseArray array for the current chunk + void GenerateNoiseArray(void); + + /// Returns true if the chunk is outside of the island's dimensions + bool IsChunkOutsideRange(int a_ChunkX, int a_ChunkZ); + + // cTerrainHeightGen overrides: + virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override; + + // cTerrainCompositionGen overrides: + virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc) override; +} ; diff --git a/src/Generating/FinishGen.cpp b/src/Generating/FinishGen.cpp new file mode 100644 index 000000000..8899e4bd0 --- /dev/null +++ b/src/Generating/FinishGen.cpp @@ -0,0 +1,664 @@ + +// FinishGen.cpp + +/* Implements the various finishing generators: + - cFinishGenSnow + - cFinishGenIce + - cFinishGenSprinkleFoliage +*/ + +#include "Globals.h" + +#include "FinishGen.h" +#include "../Noise.h" +#include "../BlockID.h" +#include "../Simulator/FluidSimulator.h" // for cFluidSimulator::CanWashAway() +#include "../World.h" + + + + + +#define DEF_NETHER_WATER_SPRINGS "0, 1; 255, 1" +#define DEF_NETHER_LAVA_SPRINGS "0, 0; 30, 0; 31, 50; 120, 50; 127, 0" +#define DEF_OVERWORLD_WATER_SPRINGS "0, 0; 10, 10; 11, 75; 16, 83; 20, 83; 24, 78; 32, 62; 40, 40; 44, 15; 48, 7; 56, 2; 64, 1; 255, 0" +#define DEF_OVERWORLD_LAVA_SPRINGS "0, 0; 10, 5; 11, 45; 48, 2; 64, 1; 255, 0" +#define DEF_END_WATER_SPRINGS "0, 1; 255, 1" +#define DEF_END_LAVA_SPRINGS "0, 1; 255, 1" + + + + + +static inline bool IsWater(BLOCKTYPE a_BlockType) +{ + return (a_BlockType == E_BLOCK_STATIONARY_WATER) || (a_BlockType == E_BLOCK_WATER); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cFinishGenSprinkleFoliage: + +bool cFinishGenSprinkleFoliage::TryAddSugarcane(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelY, int a_RelZ) +{ + // We'll be doing comparison to neighbors, so require the coords to be 1 block away from the chunk edges: + if ( + (a_RelX < 1) || (a_RelX >= cChunkDef::Width - 1) || + (a_RelY < 1) || (a_RelY >= cChunkDef::Height - 2) || + (a_RelZ < 1) || (a_RelZ >= cChunkDef::Width - 1) + ) + { + return false; + } + + // Only allow dirt, grass or sand below sugarcane: + switch (a_ChunkDesc.GetBlockType(a_RelX, a_RelY, a_RelZ)) + { + case E_BLOCK_DIRT: + case E_BLOCK_GRASS: + case E_BLOCK_SAND: + { + break; + } + default: + { + return false; + } + } + + // Water is required next to the block below the sugarcane: + if ( + !IsWater(a_ChunkDesc.GetBlockType(a_RelX - 1, a_RelY, a_RelZ)) && + !IsWater(a_ChunkDesc.GetBlockType(a_RelX + 1, a_RelY, a_RelZ)) && + !IsWater(a_ChunkDesc.GetBlockType(a_RelX , a_RelY, a_RelZ - 1)) && + !IsWater(a_ChunkDesc.GetBlockType(a_RelX , a_RelY, a_RelZ + 1)) + ) + { + return false; + } + + // All conditions met, place a sugarcane here: + a_ChunkDesc.SetBlockType(a_RelX, a_RelY + 1, a_RelZ, E_BLOCK_SUGARCANE); + return true; +} + + + + + +void cFinishGenSprinkleFoliage::GenFinish(cChunkDesc & a_ChunkDesc) +{ + // Generate small foliage (1-block): + + // TODO: Update heightmap with 1-block-tall foliage + for (int z = 0; z < cChunkDef::Width; z++) + { + int BlockZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width + z; + const float zz = (float)BlockZ; + for (int x = 0; x < cChunkDef::Width; x++) + { + int BlockX = a_ChunkDesc.GetChunkX() * cChunkDef::Width + x; + if (((m_Noise.IntNoise2DInt(BlockX, BlockZ) / 8) % 128) < 124) + { + continue; + } + int Top = a_ChunkDesc.GetHeight(x, z); + if (Top > 250) + { + // Nothing grows above Y=250 + continue; + } + if (a_ChunkDesc.GetBlockType(x, Top + 1, z) != E_BLOCK_AIR) + { + // Space already taken by something else, don't grow here + // WEIRD, since we're using heightmap, so there should NOT be anything above it + continue; + } + + const float xx = (float)BlockX; + float val1 = m_Noise.CubicNoise2D(xx * 0.1f, zz * 0.1f ); + float val2 = m_Noise.CubicNoise2D(xx * 0.01f, zz * 0.01f ); + switch (a_ChunkDesc.GetBlockType(x, Top, z)) + { + case E_BLOCK_GRASS: + { + float val3 = m_Noise.CubicNoise2D(xx * 0.01f + 10, zz * 0.01f + 10 ); + float val4 = m_Noise.CubicNoise2D(xx * 0.05f + 20, zz * 0.05f + 20 ); + if (val1 + val2 > 0.2f) + { + a_ChunkDesc.SetBlockType(x, ++Top, z, E_BLOCK_YELLOW_FLOWER); + } + else if (val2 + val3 > 0.2f) + { + a_ChunkDesc.SetBlockType(x, ++Top, z, E_BLOCK_RED_ROSE); + } + else if (val3 + val4 > 0.2f) + { + a_ChunkDesc.SetBlockType(x, ++Top, z, E_BLOCK_RED_MUSHROOM); + } + else if (val1 + val4 > 0.2f) + { + a_ChunkDesc.SetBlockType(x, ++Top, z, E_BLOCK_BROWN_MUSHROOM); + } + else if (val1 + val2 + val3 + val4 < -0.1) + { + a_ChunkDesc.SetBlockTypeMeta(x, ++Top, z, E_BLOCK_TALL_GRASS, E_META_TALL_GRASS_GRASS); + } + else if (TryAddSugarcane(a_ChunkDesc, x, Top, z)) + { + ++Top; + } + else if ((val1 > 0.5) && (val2 < -0.5)) + { + a_ChunkDesc.SetBlockTypeMeta(x, ++Top, z, E_BLOCK_PUMPKIN, (int)(val3 * 8) % 4); + } + break; + } // case E_BLOCK_GRASS + + case E_BLOCK_SAND: + { + int y = Top + 1; + if ( + (x > 0) && (x < cChunkDef::Width - 1) && + (z > 0) && (z < cChunkDef::Width - 1) && + (val1 + val2 > 0.5f) && + (a_ChunkDesc.GetBlockType(x + 1, y, z) == E_BLOCK_AIR) && + (a_ChunkDesc.GetBlockType(x - 1, y, z) == E_BLOCK_AIR) && + (a_ChunkDesc.GetBlockType(x, y, z + 1) == E_BLOCK_AIR) && + (a_ChunkDesc.GetBlockType(x, y, z - 1) == E_BLOCK_AIR) + ) + { + a_ChunkDesc.SetBlockType(x, ++Top, z, E_BLOCK_CACTUS); + } + else if (TryAddSugarcane(a_ChunkDesc, x, Top, z)) + { + ++Top; + } + break; + } + } // switch (TopBlock) + a_ChunkDesc.SetHeight(x, z, Top); + } // for y + } // for z +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cFinishGenSnow: + +void cFinishGenSnow::GenFinish(cChunkDesc & a_ChunkDesc) +{ + // Add a snow block in snowy biomes onto blocks that can be snowed over + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + switch (a_ChunkDesc.GetBiome(x, z)) + { + case biIcePlains: + case biIceMountains: + case biTaiga: + case biTaigaHills: + case biFrozenRiver: + case biFrozenOcean: + { + int Height = a_ChunkDesc.GetHeight(x, z); + if (g_BlockIsSnowable[a_ChunkDesc.GetBlockType(x, Height, z)]) + { + a_ChunkDesc.SetBlockType(x, Height + 1, z, E_BLOCK_SNOW); + a_ChunkDesc.SetHeight(x, z, Height + 1); + } + break; + } + } + } + } // for z +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cFinishGenIce: + +void cFinishGenIce::GenFinish(cChunkDesc & a_ChunkDesc) +{ + // Turn surface water into ice in icy biomes + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + switch (a_ChunkDesc.GetBiome(x, z)) + { + case biIcePlains: + case biIceMountains: + case biTaiga: + case biTaigaHills: + case biFrozenRiver: + case biFrozenOcean: + { + int Height = a_ChunkDesc.GetHeight(x, z); + switch (a_ChunkDesc.GetBlockType(x, Height, z)) + { + case E_BLOCK_WATER: + case E_BLOCK_STATIONARY_WATER: + { + a_ChunkDesc.SetBlockType(x, Height, z, E_BLOCK_ICE); + break; + } + } + break; + } + } + } + } // for z +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cFinishGenLilypads: + +int cFinishGenSingleBiomeSingleTopBlock::GetNumToGen(const cChunkDef::BiomeMap & a_BiomeMap) +{ + int res = 0; + for (int i = 0; i < ARRAYCOUNT(a_BiomeMap); i++) + { + if (a_BiomeMap[i] == m_Biome) + { + res++; + } + } // for i - a_BiomeMap[] + return m_Amount * res / 256; +} + + + + + +void cFinishGenSingleBiomeSingleTopBlock::GenFinish(cChunkDesc & a_ChunkDesc) +{ + // Add Lilypads on top of water surface in Swampland + + int NumToGen = GetNumToGen(a_ChunkDesc.GetBiomeMap()); + int ChunkX = a_ChunkDesc.GetChunkX(); + int ChunkZ = a_ChunkDesc.GetChunkZ(); + for (int i = 0; i < NumToGen; i++) + { + int x = (m_Noise.IntNoise3DInt(ChunkX + ChunkZ, ChunkZ, i) / 13) % cChunkDef::Width; + int z = (m_Noise.IntNoise3DInt(ChunkX - ChunkZ, i, ChunkZ) / 11) % cChunkDef::Width; + + // Place the block at {x, z} if possible: + if (a_ChunkDesc.GetBiome(x, z) != m_Biome) + { + // Incorrect biome + continue; + } + int Height = a_ChunkDesc.GetHeight(x, z); + if (Height >= cChunkDef::Height) + { + // Too high up + continue; + } + if (a_ChunkDesc.GetBlockType(x, Height + 1, z) != E_BLOCK_AIR) + { + // Not an empty block + continue; + } + BLOCKTYPE BlockBelow = a_ChunkDesc.GetBlockType(x, Height, z); + if ((BlockBelow == m_AllowedBelow1) || (BlockBelow == m_AllowedBelow2)) + { + a_ChunkDesc.SetBlockType(x, Height + 1, z, m_BlockType); + a_ChunkDesc.SetHeight(x, z, Height + 1); + } + } // for i +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cFinishGenBottomLava: + +void cFinishGenBottomLava::GenFinish(cChunkDesc & a_ChunkDesc) +{ + cChunkDef::BlockTypes & BlockTypes = a_ChunkDesc.GetBlockTypes(); + for (int y = m_Level; y > 0; y--) + { + for (int z = 0; z < cChunkDef::Width; z++) for (int x = 0; x < cChunkDef::Width; x++) + { + int Index = cChunkDef::MakeIndexNoCheck(x, y, z); + if (BlockTypes[Index] == E_BLOCK_AIR) + { + BlockTypes[Index] = E_BLOCK_STATIONARY_LAVA; + } + } // for x, for z + } // for y +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cFinishGenPreSimulator: + +cFinishGenPreSimulator::cFinishGenPreSimulator(void) +{ + // Nothing needed yet +} + + + + + +void cFinishGenPreSimulator::GenFinish(cChunkDesc & a_ChunkDesc) +{ + CollapseSandGravel(a_ChunkDesc.GetBlockTypes(), a_ChunkDesc.GetHeightMap()); + StationarizeFluid(a_ChunkDesc.GetBlockTypes(), a_ChunkDesc.GetHeightMap(), E_BLOCK_WATER, E_BLOCK_STATIONARY_WATER); + StationarizeFluid(a_ChunkDesc.GetBlockTypes(), a_ChunkDesc.GetHeightMap(), E_BLOCK_LAVA, E_BLOCK_STATIONARY_LAVA); + // TODO: other operations +} + + + + + +void cFinishGenPreSimulator::CollapseSandGravel( + cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change + cChunkDef::HeightMap & a_HeightMap // Height map to update by the current data +) +{ + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + int LastY = -1; + int HeightY = 0; + for (int y = 0; y < cChunkDef::Height; y++) + { + BLOCKTYPE Block = cChunkDef::GetBlock(a_BlockTypes, x, y, z); + switch (Block) + { + default: + { + // Set the last block onto which stuff can fall to this height: + LastY = y; + HeightY = y; + break; + } + case E_BLOCK_AIR: + { + // Do nothing + break; + } + case E_BLOCK_FIRE: + case E_BLOCK_WATER: + case E_BLOCK_STATIONARY_WATER: + case E_BLOCK_LAVA: + case E_BLOCK_STATIONARY_LAVA: + { + // Do nothing, only remember this height as potentially highest + HeightY = y; + break; + } + case E_BLOCK_SAND: + case E_BLOCK_GRAVEL: + { + if (LastY < y - 1) + { + cChunkDef::SetBlock(a_BlockTypes, x, LastY + 1, z, Block); + cChunkDef::SetBlock(a_BlockTypes, x, y, z, E_BLOCK_AIR); + } + LastY++; + if (LastY > HeightY) + { + HeightY = LastY; + } + break; + } + } // switch (GetBlock) + } // for y + cChunkDef::SetHeight(a_HeightMap, x, z, HeightY); + } // for x + } // for z +} + + + + + +void cFinishGenPreSimulator::StationarizeFluid( + cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change + cChunkDef::HeightMap & a_HeightMap, // Height map to read + BLOCKTYPE a_Fluid, + BLOCKTYPE a_StationaryFluid +) +{ + // Turn fluid in the middle to stationary, unless it has air or washable block next to it: + for (int z = 1; z < cChunkDef::Width - 1; z++) + { + for (int x = 1; x < cChunkDef::Width - 1; x++) + { + for (int y = cChunkDef::GetHeight(a_HeightMap, x, z); y >= 0; y--) + { + BLOCKTYPE Block = cChunkDef::GetBlock(a_BlockTypes, x, y, z); + if ((Block != a_Fluid) && (Block != a_StationaryFluid)) + { + continue; + } + static const struct + { + int x, y, z; + } Coords[] = + { + {1, 0, 0}, + {-1, 0, 0}, + {0, 0, 1}, + {0, 0, -1}, + {0, -1, 0} + } ; + BLOCKTYPE BlockToSet = a_StationaryFluid; // By default, don't simulate this block + for (int i = 0; i < ARRAYCOUNT(Coords); i++) + { + if ((y == 0) && (Coords[i].y < 0)) + { + continue; + } + BLOCKTYPE Neighbor = cChunkDef::GetBlock(a_BlockTypes, x + Coords[i].x, y + Coords[i].y, z + Coords[i].z); + if ((Neighbor == E_BLOCK_AIR) || cFluidSimulator::CanWashAway(Neighbor)) + { + // There is an air / washable neighbor, simulate this block + BlockToSet = a_Fluid; + break; + } + } // for i - Coords[] + cChunkDef::SetBlock(a_BlockTypes, x, y, z, BlockToSet); + } // for y + } // for x + } // for z + + // Turn fluid at the chunk edges into non-stationary fluid: + for (int y = 0; y < cChunkDef::Height; y++) + { + for (int i = 0; i < cChunkDef::Width; i++) // i stands for both x and z here + { + if (cChunkDef::GetBlock(a_BlockTypes, 0, y, i) == a_StationaryFluid) + { + cChunkDef::SetBlock(a_BlockTypes, 0, y, i, a_Fluid); + } + if (cChunkDef::GetBlock(a_BlockTypes, i, y, 0) == a_StationaryFluid) + { + cChunkDef::SetBlock(a_BlockTypes, i, y, 0, a_Fluid); + } + if (cChunkDef::GetBlock(a_BlockTypes, cChunkDef::Width - 1, y, i) == a_StationaryFluid) + { + cChunkDef::SetBlock(a_BlockTypes, cChunkDef::Width - 1, y, i, a_Fluid); + } + if (cChunkDef::GetBlock(a_BlockTypes, i, y, cChunkDef::Width - 1) == a_StationaryFluid) + { + cChunkDef::SetBlock(a_BlockTypes, i, y, cChunkDef::Width - 1, a_Fluid); + } + } + } +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cFinishGenFluidSprings: + +cFinishGenFluidSprings::cFinishGenFluidSprings(int a_Seed, BLOCKTYPE a_Fluid, cIniFile & a_IniFile, const cWorld & a_World) : + m_Noise(a_Seed + a_Fluid * 100), // Need to take fluid into account, otherwise water and lava springs generate next to each other + m_HeightDistribution(255), + m_Fluid(a_Fluid) +{ + bool IsWater = (a_Fluid == E_BLOCK_WATER); + AString SectionName = IsWater ? "WaterSprings" : "LavaSprings"; + AString DefaultHeightDistribution; + int DefaultChance; + switch (a_World.GetDimension()) + { + case dimNether: + { + DefaultHeightDistribution = IsWater ? DEF_NETHER_WATER_SPRINGS : DEF_NETHER_LAVA_SPRINGS; + DefaultChance = IsWater ? 0 : 15; + break; + } + case dimOverworld: + { + DefaultHeightDistribution = IsWater ? DEF_OVERWORLD_WATER_SPRINGS : DEF_OVERWORLD_LAVA_SPRINGS; + DefaultChance = IsWater ? 24 : 9; + break; + } + case dimEnd: + { + DefaultHeightDistribution = IsWater ? DEF_END_WATER_SPRINGS : DEF_END_LAVA_SPRINGS; + DefaultChance = 0; + break; + } + default: + { + ASSERT(!"Unhandled world dimension"); + break; + } + } // switch (dimension) + AString HeightDistribution = a_IniFile.GetValueSet(SectionName, "HeightDistribution", DefaultHeightDistribution); + if (!m_HeightDistribution.SetDefString(HeightDistribution) || (m_HeightDistribution.GetSum() <= 0)) + { + LOGWARNING("[%sSprings]: HeightDistribution is invalid, using the default of \"%s\".", + (a_Fluid == E_BLOCK_WATER) ? "Water" : "Lava", + DefaultHeightDistribution.c_str() + ); + m_HeightDistribution.SetDefString(DefaultHeightDistribution); + } + m_Chance = a_IniFile.GetValueSetI(SectionName, "Chance", DefaultChance); +} + + + + + +void cFinishGenFluidSprings::GenFinish(cChunkDesc & a_ChunkDesc) +{ + int ChanceRnd = (m_Noise.IntNoise3DInt(128 * a_ChunkDesc.GetChunkX(), 512, 256 * a_ChunkDesc.GetChunkZ()) / 13) % 100; + if (ChanceRnd > m_Chance) + { + // Not in this chunk + return; + } + + // Get the height at which to try: + int Height = m_Noise.IntNoise3DInt(128 * a_ChunkDesc.GetChunkX(), 1024, 256 * a_ChunkDesc.GetChunkZ()) / 11; + Height %= m_HeightDistribution.GetSum(); + Height = m_HeightDistribution.MapValue(Height); + + // Try adding the spring at the height, if unsuccessful, move lower: + for (int y = Height; y > 1; y--) + { + // TODO: randomize the order in which the coords are being checked + for (int z = 1; z < cChunkDef::Width - 1; z++) + { + for (int x = 1; x < cChunkDef::Width - 1; x++) + { + switch (a_ChunkDesc.GetBlockType(x, y, z)) + { + case E_BLOCK_NETHERRACK: + case E_BLOCK_STONE: + { + if (TryPlaceSpring(a_ChunkDesc, x, y, z)) + { + // Succeeded, bail out + return; + } + } + } // switch (BlockType) + } // for x + } // for y + } // for y +} + + + + + +bool cFinishGenFluidSprings::TryPlaceSpring(cChunkDesc & a_ChunkDesc, int x, int y, int z) +{ + // In order to place a spring, it needs exactly one of the XZ neighbors or a below neighbor to be air + // Also, its neighbor on top of it must be non-air + if (a_ChunkDesc.GetBlockType(x, y + 1, z) == E_BLOCK_AIR) + { + return false; + } + + static const struct + { + int x, y, z; + } Coords[] = + { + {-1, 0, 0}, + { 1, 0, 0}, + { 0, -1, 0}, + { 0, 0, -1}, + { 0, 0, 1}, + } ; + int NumAirNeighbors = 0; + for (int i = 0; i < ARRAYCOUNT(Coords); i++) + { + switch (a_ChunkDesc.GetBlockType(x + Coords[i].x, y + Coords[i].y, z + Coords[i].z)) + { + case E_BLOCK_AIR: + { + NumAirNeighbors += 1; + if (NumAirNeighbors > 1) + { + return false; + } + } + } + } + if (NumAirNeighbors == 0) + { + return false; + } + + // Has exactly one air neighbor, place a spring: + a_ChunkDesc.SetBlockTypeMeta(x, y, z, m_Fluid, 0); + return true; +} + + + + diff --git a/src/Generating/FinishGen.h b/src/Generating/FinishGen.h new file mode 100644 index 000000000..ed7df5909 --- /dev/null +++ b/src/Generating/FinishGen.h @@ -0,0 +1,185 @@ + +// FinishGen.h + +/* Interfaces to the various finishing generators: + - cFinishGenSnow + - cFinishGenIce + - cFinishGenSprinkleFoliage + - cFinishGenLilypads + - cFinishGenBottomLava + - cFinishGenPreSimulator + - cFinishGenDeadBushes +*/ + + + + + +#include "ComposableGenerator.h" +#include "../Noise.h" +#include "../ProbabDistrib.h" + + + + + +class cFinishGenSnow : + public cFinishGen +{ +protected: + // cFinishGen override: + virtual void GenFinish(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +class cFinishGenIce : + public cFinishGen +{ +protected: + // cFinishGen override: + virtual void GenFinish(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +class cFinishGenSprinkleFoliage : + public cFinishGen +{ +public: + cFinishGenSprinkleFoliage(int a_Seed) : m_Noise(a_Seed), m_Seed(a_Seed) {} + +protected: + cNoise m_Noise; + int m_Seed; + + /// Tries to place sugarcane at the coords specified, returns true if successful + bool TryAddSugarcane(cChunkDesc & a_ChunkDesc, int a_RelX, int a_RelY, int a_RelZ); + + // cFinishGen override: + virtual void GenFinish(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +/** This class adds a single top block in random positions in the specified biome on top of specified allowed blocks. +Used for: +- Lilypads finisher +- DeadBushes finisher +*/ +class cFinishGenSingleBiomeSingleTopBlock : + public cFinishGen +{ +public: + cFinishGenSingleBiomeSingleTopBlock( + int a_Seed, BLOCKTYPE a_BlockType, EMCSBiome a_Biome, int a_Amount, + BLOCKTYPE a_AllowedBelow1, BLOCKTYPE a_AllowedBelow2 + ) : + m_Noise(a_Seed), + m_BlockType(a_BlockType), + m_Biome(a_Biome), + m_Amount(a_Amount), + m_AllowedBelow1(a_AllowedBelow1), + m_AllowedBelow2(a_AllowedBelow2) + { + } + +protected: + cNoise m_Noise; + BLOCKTYPE m_BlockType; + EMCSBiome m_Biome; + int m_Amount; ///< Relative amount of blocks to try adding. 1 = one block per 256 biome columns. + BLOCKTYPE m_AllowedBelow1; ///< First of the two blocktypes that are allowed below m_BlockType + BLOCKTYPE m_AllowedBelow2; ///< Second of the two blocktypes that are allowed below m_BlockType + + int GetNumToGen(const cChunkDef::BiomeMap & a_BiomeMap); + + // cFinishGen override: + virtual void GenFinish(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +class cFinishGenBottomLava : + public cFinishGen +{ +public: + cFinishGenBottomLava(int a_Level) : + m_Level(a_Level) + { + } + +protected: + int m_Level; + + // cFinishGen override: + virtual void GenFinish(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +class cFinishGenPreSimulator : + public cFinishGen +{ +public: + cFinishGenPreSimulator(void); + +protected: + // Drops hanging sand and gravel down to the ground, recalculates heightmap + void CollapseSandGravel( + cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change + cChunkDef::HeightMap & a_HeightMap // Height map to update by the current data + ); + + /** For each fluid block: + - if all surroundings are of the same fluid, makes it stationary; otherwise makes it flowing (excl. top) + - all fluid on the chunk's edge is made flowing + */ + void StationarizeFluid( + cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change + cChunkDef::HeightMap & a_HeightMap, // Height map to read + BLOCKTYPE a_Fluid, + BLOCKTYPE a_StationaryFluid + ); + + // cFinishGen override: + virtual void GenFinish(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +class cFinishGenFluidSprings : + public cFinishGen +{ +public: + cFinishGenFluidSprings(int a_Seed, BLOCKTYPE a_Fluid, cIniFile & a_IniFile, const cWorld & a_World); + +protected: + + cNoise m_Noise; + cProbabDistrib m_HeightDistribution; + BLOCKTYPE m_Fluid; + int m_Chance; ///< Chance, [0..100], that a spring will be generated in a chunk + + // cFinishGen override: + virtual void GenFinish(cChunkDesc & a_ChunkDesc) override; + + /// Tries to place a spring at the specified coords, checks neighbors. Returns true if successful + bool TryPlaceSpring(cChunkDesc & a_ChunkDesc, int x, int y, int z); +} ; + + + + diff --git a/src/Generating/HeiGen.cpp b/src/Generating/HeiGen.cpp new file mode 100644 index 000000000..ea6051371 --- /dev/null +++ b/src/Generating/HeiGen.cpp @@ -0,0 +1,458 @@ + +// HeiGen.cpp + +// Implements the various terrain height generators + +#include "Globals.h" +#include "HeiGen.h" +#include "../LinearUpscale.h" +#include "inifile/iniFile.h" + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHeiGenFlat: + +void cHeiGenFlat::GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) +{ + for (int i = 0; i < ARRAYCOUNT(a_HeightMap); i++) + { + a_HeightMap[i] = m_Height; + } +} + + + + + +void cHeiGenFlat::InitializeHeightGen(cIniFile & a_IniFile) +{ + m_Height = a_IniFile.GetValueSetI("Generator", "FlatHeight", m_Height); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHeiGenCache: + +cHeiGenCache::cHeiGenCache(cTerrainHeightGen & a_HeiGenToCache, int a_CacheSize) : + m_HeiGenToCache(a_HeiGenToCache), + m_CacheSize(a_CacheSize), + m_CacheOrder(new int[a_CacheSize]), + m_CacheData(new sCacheData[a_CacheSize]), + m_NumHits(0), + m_NumMisses(0), + m_TotalChain(0) +{ + for (int i = 0; i < m_CacheSize; i++) + { + m_CacheOrder[i] = i; + m_CacheData[i].m_ChunkX = 0x7fffffff; + m_CacheData[i].m_ChunkZ = 0x7fffffff; + } +} + + + + + +cHeiGenCache::~cHeiGenCache() +{ + delete[] m_CacheData; + delete[] m_CacheOrder; +} + + + + + +void cHeiGenCache::GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) +{ + /* + if (((m_NumHits + m_NumMisses) % 1024) == 10) + { + LOGD("HeiGenCache: %d hits, %d misses, saved %.2f %%", m_NumHits, m_NumMisses, 100.0 * m_NumHits / (m_NumHits + m_NumMisses)); + LOGD("HeiGenCache: Avg cache chain length: %.2f", (float)m_TotalChain / m_NumHits); + } + //*/ + + for (int i = 0; i < m_CacheSize; i++) + { + if ( + (m_CacheData[m_CacheOrder[i]].m_ChunkX != a_ChunkX) || + (m_CacheData[m_CacheOrder[i]].m_ChunkZ != a_ChunkZ) + ) + { + continue; + } + // Found it in the cache + int Idx = m_CacheOrder[i]; + + // Move to front: + for (int j = i; j > 0; j--) + { + m_CacheOrder[j] = m_CacheOrder[j - 1]; + } + m_CacheOrder[0] = Idx; + + // Use the cached data: + memcpy(a_HeightMap, m_CacheData[Idx].m_HeightMap, sizeof(a_HeightMap)); + + m_NumHits++; + m_TotalChain += i; + return; + } // for i - cache + + // Not in the cache: + m_NumMisses++; + m_HeiGenToCache.GenHeightMap(a_ChunkX, a_ChunkZ, a_HeightMap); + + // Insert it as the first item in the MRU order: + int Idx = m_CacheOrder[m_CacheSize - 1]; + for (int i = m_CacheSize - 1; i > 0; i--) + { + m_CacheOrder[i] = m_CacheOrder[i - 1]; + } // for i - m_CacheOrder[] + m_CacheOrder[0] = Idx; + memcpy(m_CacheData[Idx].m_HeightMap, a_HeightMap, sizeof(a_HeightMap)); + m_CacheData[Idx].m_ChunkX = a_ChunkX; + m_CacheData[Idx].m_ChunkZ = a_ChunkZ; +} + + + + + +void cHeiGenCache::InitializeHeightGen(cIniFile & a_IniFile) +{ + m_HeiGenToCache.InitializeHeightGen(a_IniFile); +} + + + + + +bool cHeiGenCache::GetHeightAt(int a_ChunkX, int a_ChunkZ, int a_RelX, int a_RelZ, HEIGHTTYPE & a_Height) +{ + for (int i = 0; i < m_CacheSize; i++) + { + if ((m_CacheData[i].m_ChunkX == a_ChunkX) && (m_CacheData[i].m_ChunkZ == a_ChunkZ)) + { + a_Height = cChunkDef::GetHeight(m_CacheData[i].m_HeightMap, a_RelX, a_RelZ); + return true; + } + } // for i - m_CacheData[] + return false; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHeiGenClassic: + +cHeiGenClassic::cHeiGenClassic(int a_Seed) : + m_Seed(a_Seed), + m_Noise(a_Seed) +{ +} + + + + + +float cHeiGenClassic::GetNoise(float x, float y) +{ + float oct1 = m_Noise.CubicNoise2D(x * m_HeightFreq1, y * m_HeightFreq1) * m_HeightAmp1; + float oct2 = m_Noise.CubicNoise2D(x * m_HeightFreq2, y * m_HeightFreq2) * m_HeightAmp2; + float oct3 = m_Noise.CubicNoise2D(x * m_HeightFreq3, y * m_HeightFreq3) * m_HeightAmp3; + + float height = m_Noise.CubicNoise2D(x * 0.1f, y * 0.1f ) * 2; + + float flatness = ((m_Noise.CubicNoise2D(x * 0.5f, y * 0.5f) + 1.f) * 0.5f) * 1.1f; // 0 ... 1.5 + flatness *= flatness * flatness; + + return (oct1 + oct2 + oct3) * flatness + height; +} + + + + + +void cHeiGenClassic::GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) +{ + for (int z = 0; z < cChunkDef::Width; z++) + { + const float zz = (float)(a_ChunkZ * cChunkDef::Width + z); + for (int x = 0; x < cChunkDef::Width; x++) + { + const float xx = (float)(a_ChunkX * cChunkDef::Width + x); + + int hei = 64 + (int)(GetNoise(xx * 0.05f, zz * 0.05f) * 16); + if (hei < 10) + { + hei = 10; + } + if (hei > 250) + { + hei = 250; + } + cChunkDef::SetHeight(a_HeightMap, x , z, hei); + } // for x + } // for z +} + + + + + +void cHeiGenClassic::InitializeHeightGen(cIniFile & a_IniFile) +{ + m_HeightFreq1 = (float)a_IniFile.GetValueSetF("Generator", "ClassicHeightFreq1", 0.1); + m_HeightFreq2 = (float)a_IniFile.GetValueSetF("Generator", "ClassicHeightFreq2", 1.0); + m_HeightFreq3 = (float)a_IniFile.GetValueSetF("Generator", "ClassicHeightFreq3", 2.0); + m_HeightAmp1 = (float)a_IniFile.GetValueSetF("Generator", "ClassicHeightAmp1", 1.0); + m_HeightAmp2 = (float)a_IniFile.GetValueSetF("Generator", "ClassicHeightAmp2", 0.5); + m_HeightAmp3 = (float)a_IniFile.GetValueSetF("Generator", "ClassicHeightAmp3", 0.5); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cHeiGenBiomal: + +const cHeiGenBiomal::sGenParam cHeiGenBiomal::m_GenParam[256] = +{ + /* Fast-changing | Middle-changing | Slow-changing |*/ + /* Biome | Freq1 | Amp1 | Freq2 | Amp2 | Freq3 | Amp3 | BaseHeight */ + /* biOcean */ { 0.1f, 2.0f, 0.05f, 10.0f, 0.01f, 8.0f, 50}, + /* biPlains */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 68}, + /* biDesert */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 68}, + /* biExtremeHills */ { 0.2f, 4.0f, 0.05f, 20.0f, 0.01f, 16.0f, 100}, + /* biForest */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 70}, + /* biTaiga */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 70}, + /* biSwampland */ { 0.1f, 1.1f, 0.05f, 1.5f, 0.02f, 2.5f, 61.5}, + /* biRiver */ { 0.2f, 0.1f, 0.05f, 0.1f, 0.01f, 0.1f, 56}, + /* biNether */ { 0.1f, 0.0f, 0.01f, 0.0f, 0.01f, 0.0f, 0}, // Unused, but must be here due to indexing + /* biSky */ { 0.1f, 0.0f, 0.01f, 0.0f, 0.01f, 0.0f, 0}, // Unused, but must be here due to indexing + /* biFrozenOcean */ { 0.1f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 40}, + /* biFrozenRiver */ { 0.2f, 0.1f, 0.05f, 0.1f, 0.01f, 0.1f, 56}, + /* biIcePlains */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 68}, + /* biIceMountains */ { 0.2f, 2.0f, 0.05f, 10.0f, 0.01f, 8.0f, 80}, + /* biMushroomIsland */ { 0.1f, 2.0f, 0.05f, 8.0f, 0.01f, 6.0f, 80}, + /* biMushroomShore */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 64}, + /* biBeach */ { 0.1f, 0.5f, 0.05f, 1.0f, 0.01f, 1.0f, 64}, + /* biDesertHills */ { 0.2f, 2.0f, 0.05f, 5.0f, 0.01f, 4.0f, 75}, + /* biForestHills */ { 0.2f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 80}, + /* biTaigaHills */ { 0.2f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 80}, + /* biExtremeHillsEdge */ { 0.2f, 3.0f, 0.05f, 16.0f, 0.01f, 12.0f, 80}, + /* biJungle */ { 0.1f, 3.0f, 0.05f, 6.0f, 0.01f, 6.0f, 70}, + /* biJungleHills */ { 0.2f, 3.0f, 0.05f, 12.0f, 0.01f, 10.0f, 80}, + /* biJungleEdge */ { 0.1f, 3.0f, 0.05f, 6.0f, 0.01f, 6.0f, 70}, + /* biDeepOcean */ { 0.1f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 40}, + /* biStoneBeach */ { 0.1f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 40}, + /* biColdBeach */ { 0.1f, 0.5f, 0.05f, 1.0f, 0.01f, 1.0f, 64}, + /* biBirchForest */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 70}, + /* biBirchForestHills */ { 0.2f, 2.0f, 0.05f, 10.0f, 0.01f, 8.0f, 80}, + /* biRoofedForest */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 70}, + /* biColdTaiga */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 70}, + /* biColdTaigaHills */ { 0.2f, 2.0f, 0.05f, 10.0f, 0.01f, 8.0f, 80}, + /* biMegaTaiga */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 70}, + /* biMegaTaigaHills */ { 0.2f, 2.0f, 0.05f, 10.0f, 0.01f, 8.0f, 80}, + /* biExtremeHillsPlus */ { 0.2f, 4.0f, 0.05f, 20.0f, 0.01f, 16.0f, 120}, + /* biSavanna */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 68}, + /* biSavannaPlateau */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 80}, + /* biMesa */ { 0.2f, 2.0f, 0.05f, 10.0f, 0.01f, 8.0f, 70}, // 165 + /* biMesaPlateauF */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 80}, + /* biMesaPlateau */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 80}, + + // biomes 40 .. 128 are unused, 89 empty placeholders here: + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 40 .. 49 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 50 .. 59 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 60 .. 69 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 70 .. 79 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 80 .. 89 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 90 .. 99 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 100 .. 109 + {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, // 110 .. 119 + {}, {}, {}, {}, {}, {}, {}, {}, {}, // 120 .. 128 + + /* biSunflowerPlains */ { 0.1f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 40}, // 129 + /* biDesertM */ { 0.1f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 40}, // 130 + /* biExtremeHillsM */ { 0.1f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 40}, // 131 + /* biFlowerForest */ { 0.1f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 40}, // 132 + /* biTaigaM */ { 0.1f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 40}, // 133 + /* biSwamplandM */ { 0.1f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 40}, // 134 + + // Biomes 135 .. 139 unused, 5 empty placeholders here: + {}, {}, {}, {}, {}, // 135 .. 139 + + /* biIcePlainsSpikes */ { 0.1f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 40}, // 140 + + // Biomes 141 .. 148 unused, 8 empty placeholders here: + {}, {}, {}, {}, {}, {}, {}, {}, // 141 .. 148 + + /* biJungleM */ { 0.1f, 3.0f, 0.05f, 6.0f, 0.01f, 6.0f, 70}, // 149 + {}, // 150 + /* biJungleEdgeM */ { 0.1f, 3.0f, 0.05f, 6.0f, 0.01f, 6.0f, 70}, // 151 + {}, {}, {}, // 152 .. 154 + /* biBirchForestM */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 70}, // 155 + /* biBirchForestHillsM */ { 0.2f, 2.0f, 0.05f, 10.0f, 0.01f, 8.0f, 80}, // 156 + /* biRoofedForestM */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 70}, // 157 + /* biColdTaigaM */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 70}, // 158 + {}, // 159 + /* biMegaSpruceTaiga */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 70}, // 160 + /* biMegaSpruceTaigaHills */ { 0.2f, 2.0f, 0.05f, 10.0f, 0.01f, 8.0f, 80}, // 161 + /* biExtremeHillsPlusM */ { 0.2f, 4.0f, 0.05f, 20.0f, 0.01f, 16.0f, 120}, // 162 + /* biSavannaM */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 68}, // 163 + /* biSavannaPlateauM */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 80}, // 164 + /* biMesaBryce */ { 0.2f, 2.0f, 0.1f, 30.0f, 0.01f, 8.0f, 80}, + /* biMesaPlateauFM */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 80}, // 166 + /* biMesaPlateauM */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 80}, // 167 +} ; + + + + + +void cHeiGenBiomal::GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) +{ + // Generate a 3x3 chunk area of biomes around this chunk: + BiomeNeighbors Biomes; + for (int z = -1; z <= 1; z++) + { + for (int x = -1; x <= 1; x++) + { + m_BiomeGen.GenBiomes(a_ChunkX + x, a_ChunkZ + z, Biomes[x + 1][z + 1]); + } // for x + } // for z + + /* + _X 2013_04_22: + There's no point in precalculating the entire perlin noise arrays, too many values are calculated uselessly, + resulting in speed DEcrease. + */ + + //* + // Linearly interpolate 4x4 blocks of heightmap: + // Must be done on a floating point datatype, else the results are ugly! + const int STEPZ = 4; // Must be a divisor of 16 + const int STEPX = 4; // Must be a divisor of 16 + NOISE_DATATYPE Height[17 * 17]; + for (int z = 0; z < 17; z += STEPZ) + { + for (int x = 0; x < 17; x += STEPX) + { + Height[x + 17 * z] = GetHeightAt(x, z, a_ChunkX, a_ChunkZ, Biomes); + } + } + LinearUpscale2DArrayInPlace(Height, 17, 17, STEPX, STEPZ); + + // Copy into the heightmap + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + cChunkDef::SetHeight(a_HeightMap, x, z, (int)Height[x + 17 * z]); + } + } + //*/ + + /* + // For each height, go through neighboring biomes and add up their idea of height: + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + cChunkDef::SetHeight(a_HeightMap, x, z, GetHeightAt(x, z, a_ChunkX, a_ChunkZ, Biomes)); + } // for x + } + //*/ +} + + + + + +void cHeiGenBiomal::InitializeHeightGen(cIniFile & a_IniFile) +{ + // No user-settable params +} + + + + + +NOISE_DATATYPE cHeiGenBiomal::GetHeightAt(int a_RelX, int a_RelZ, int a_ChunkX, int a_ChunkZ, const cHeiGenBiomal::BiomeNeighbors & a_BiomeNeighbors) +{ + // Sum up how many biomes of each type there are in the neighborhood: + int BiomeCounts[256]; + memset(BiomeCounts, 0, sizeof(BiomeCounts)); + int Sum = 0; + for (int z = -8; z <= 8; z++) + { + int FinalZ = a_RelZ + z + cChunkDef::Width; + int IdxZ = FinalZ / cChunkDef::Width; + int ModZ = FinalZ % cChunkDef::Width; + int WeightZ = 9 - abs(z); + for (int x = -8; x <= 8; x++) + { + int FinalX = a_RelX + x + cChunkDef::Width; + int IdxX = FinalX / cChunkDef::Width; + int ModX = FinalX % cChunkDef::Width; + EMCSBiome Biome = cChunkDef::GetBiome(a_BiomeNeighbors[IdxX][IdxZ], ModX, ModZ); + int WeightX = 9 - abs(x); + BiomeCounts[Biome] += WeightX + WeightZ; + Sum += WeightX + WeightZ; + } // for x + } // for z + + // For each biome type that has a nonzero count, calc its height and add it: + if (Sum > 0) + { + NOISE_DATATYPE Height = 0; + int BlockX = a_ChunkX * cChunkDef::Width + a_RelX; + int BlockZ = a_ChunkZ * cChunkDef::Width + a_RelZ; + for (int i = 0; i < ARRAYCOUNT(BiomeCounts); i++) + { + if (BiomeCounts[i] == 0) + { + continue; + } + + /* + // Sanity checks for biome parameters, enable them to check the biome param table in runtime (slow): + ASSERT(m_GenParam[i].m_HeightFreq1 >= 0); + ASSERT(m_GenParam[i].m_HeightFreq1 < 1000); + ASSERT(m_GenParam[i].m_HeightFreq2 >= 0); + ASSERT(m_GenParam[i].m_HeightFreq2 < 1000); + ASSERT(m_GenParam[i].m_HeightFreq3 >= 0); + ASSERT(m_GenParam[i].m_HeightFreq3 < 1000); + */ + + NOISE_DATATYPE oct1 = m_Noise.CubicNoise2D(BlockX * m_GenParam[i].m_HeightFreq1, BlockZ * m_GenParam[i].m_HeightFreq1) * m_GenParam[i].m_HeightAmp1; + NOISE_DATATYPE oct2 = m_Noise.CubicNoise2D(BlockX * m_GenParam[i].m_HeightFreq2, BlockZ * m_GenParam[i].m_HeightFreq2) * m_GenParam[i].m_HeightAmp2; + NOISE_DATATYPE oct3 = m_Noise.CubicNoise2D(BlockX * m_GenParam[i].m_HeightFreq3, BlockZ * m_GenParam[i].m_HeightFreq3) * m_GenParam[i].m_HeightAmp3; + Height += BiomeCounts[i] * (m_GenParam[i].m_BaseHeight + oct1 + oct2 + oct3); + } + NOISE_DATATYPE res = Height / Sum; + return std::min((NOISE_DATATYPE)250, std::max(res, (NOISE_DATATYPE)5)); + } + + // No known biome around? Weird. Return a bogus value: + ASSERT(!"cHeiGenBiomal: Biome sum failed, no known biome around"); + return 5; +} + + + + + diff --git a/src/Generating/HeiGen.h b/src/Generating/HeiGen.h new file mode 100644 index 000000000..1376f2a25 --- /dev/null +++ b/src/Generating/HeiGen.h @@ -0,0 +1,145 @@ + +// HeiGen.h + +/* +Interfaces to the various height generators: + - cHeiGenFlat + - cHeiGenClassic + - cHeiGenBiomal +*/ + + + + + +#pragma once + +#include "ComposableGenerator.h" +#include "../Noise.h" + + + + + +class cHeiGenFlat : + public cTerrainHeightGen +{ +public: + cHeiGenFlat(void) : m_Height(5) {} + +protected: + + int m_Height; + + // cTerrainHeightGen overrides: + virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override; + virtual void InitializeHeightGen(cIniFile & a_IniFile) override; +} ; + + + + + +/// A simple cache that stores N most recently generated chunks' heightmaps; N being settable upon creation +class cHeiGenCache : + public cTerrainHeightGen +{ +public: + cHeiGenCache(cTerrainHeightGen & a_HeiGenToCache, int a_CacheSize); // Doesn't take ownership of a_HeiGenToCache + ~cHeiGenCache(); + + // cTerrainHeightGen overrides: + virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override; + virtual void InitializeHeightGen(cIniFile & a_IniFile) override; + + /// Retrieves height at the specified point in the cache, returns true if found, false if not found + bool GetHeightAt(int a_ChunkX, int a_ChunkZ, int a_RelX, int a_RelZ, HEIGHTTYPE & a_Height); + +protected: + + cTerrainHeightGen & m_HeiGenToCache; + + struct sCacheData + { + int m_ChunkX; + int m_ChunkZ; + cChunkDef::HeightMap m_HeightMap; + } ; + + // To avoid moving large amounts of data for the MRU behavior, we MRU-ize indices to an array of the actual data + int m_CacheSize; + int * m_CacheOrder; // MRU-ized order, indices into m_CacheData array + sCacheData * m_CacheData; // m_CacheData[m_CacheOrder[0]] is the most recently used + + // Cache statistics + int m_NumHits; + int m_NumMisses; + int m_TotalChain; // Number of cache items walked to get to a hit (only added for hits) +} ; + + + + + +class cHeiGenClassic : + public cTerrainHeightGen +{ +public: + cHeiGenClassic(int a_Seed); + +protected: + + int m_Seed; + cNoise m_Noise; + float m_HeightFreq1, m_HeightAmp1; + float m_HeightFreq2, m_HeightAmp2; + float m_HeightFreq3, m_HeightAmp3; + + float GetNoise(float x, float y); + + // cTerrainHeightGen overrides: + virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override; + virtual void InitializeHeightGen(cIniFile & a_IniFile) override; +} ; + + + + + +class cHeiGenBiomal : + public cTerrainHeightGen +{ +public: + cHeiGenBiomal(int a_Seed, cBiomeGen & a_BiomeGen) : + m_Noise(a_Seed), + m_BiomeGen(a_BiomeGen) + { + } + +protected: + + typedef cChunkDef::BiomeMap BiomeNeighbors[3][3]; + + cNoise m_Noise; + cBiomeGen & m_BiomeGen; + + // Per-biome terrain generator parameters: + struct sGenParam + { + float m_HeightFreq1, m_HeightAmp1; + float m_HeightFreq2, m_HeightAmp2; + float m_HeightFreq3, m_HeightAmp3; + float m_BaseHeight; + } ; + static const sGenParam m_GenParam[256]; + + // cTerrainHeightGen overrides: + virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override; + virtual void InitializeHeightGen(cIniFile & a_IniFile) override; + + NOISE_DATATYPE GetHeightAt(int a_RelX, int a_RelZ, int a_ChunkX, int a_ChunkZ, const BiomeNeighbors & a_BiomeNeighbors); +} ; + + + + diff --git a/src/Generating/MineShafts.cpp b/src/Generating/MineShafts.cpp new file mode 100644 index 000000000..f42240e55 --- /dev/null +++ b/src/Generating/MineShafts.cpp @@ -0,0 +1,1424 @@ + +// MineShafts.cpp + +// Implements the cStructGenMineShafts class representing the structure generator for abandoned mineshafts + +/* +Algorithm: +The cStructGenMineShafts::cMineShaftSystem class is the main controller, which knows what mineshaft +classes there are and their random weights. It gets asked to produce a new class everytime a connection is to be made. +The cMineShaft class is a base class for each mineshaft structure. +Each cMineShaft descendant knows how large it is, how to imprint itself into the chunk data and where to connect to +other descendants. Its PivotPoint is always a walkable column. Its Direction determines in which direction the structure +is facing. + +The generation starts with the central dirt room, from there corridors, crossings and staircases are added +in a depth-first processing. Each of the descendants will branch randomly, if not beyond the allowed recursion level +*/ + +#include "Globals.h" +#include "MineShafts.h" +#include "../Cuboid.h" +#include "../BlockEntities/ChestEntity.h" + + + + + +static const int NEIGHBORHOOD_SIZE = 3; + + + + + +class cMineShaft abstract +{ +public: + enum eKind + { + mskDirtRoom, + mskCorridor, + mskCrossing, + mskStaircase, + } ; + + + enum eDirection + { + dirXP, + dirZP, + dirXM, + dirZM, + } ; + + + cStructGenMineShafts::cMineShaftSystem & m_ParentSystem; + eKind m_Kind; + cCuboid m_BoundingBox; + + + cMineShaft(cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, eKind a_Kind) : + m_ParentSystem(a_ParentSystem), + m_Kind(a_Kind) + { + } + + cMineShaft(cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, eKind a_Kind, const cCuboid & a_BoundingBox) : + m_ParentSystem(a_ParentSystem), + m_Kind(a_Kind), + m_BoundingBox(a_BoundingBox) + { + } + + /// Returns true if this mineshaft intersects the specified cuboid + bool DoesIntersect(const cCuboid & a_Other) + { + return m_BoundingBox.DoesIntersect(a_Other); + } + + /** If recursion level is not too large, appends more branches to the parent system, + using exit points specific to this class. + */ + virtual void AppendBranches(int a_RecursionLevel, cNoise & a_Noise) = 0; + + /// Imprints this shape into the specified chunk's data + virtual void ProcessChunk(cChunkDesc & a_ChunkDesc) = 0; +} ; + +typedef std::vector<cMineShaft *> cMineShafts; + + + + + +class cMineShaftDirtRoom : + public cMineShaft +{ + typedef cMineShaft super; + +public: + cMineShaftDirtRoom(cStructGenMineShafts::cMineShaftSystem & a_Parent, cNoise & a_Noise); + + // cMineShaft overrides: + virtual void AppendBranches(int a_RecursionLevel, cNoise & a_Noise) override; + virtual void ProcessChunk(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +class cMineShaftCorridor : + public cMineShaft +{ + typedef cMineShaft super; + +public: + /** Creates a new Corridor attached to the specified pivot point and direction. + Checks all ParentSystem's objects and disallows intersecting. Initializes the new object to fit. + May return NULL if cannot fit. + */ + static cMineShaft * CreateAndFit( + cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, + int a_PivotX, int a_PivotY, int a_PivotZ, eDirection a_Direction, + cNoise & a_Noise + ); + +protected: + static const int MAX_SEGMENTS = 5; + + int m_NumSegments; + eDirection m_Direction; + bool m_HasFullBeam[MAX_SEGMENTS]; ///< If true, segment at that index has a full beam support (planks in the top center block) + int m_ChestPosition; ///< If <0, no chest; otherwise an offset from m_BoundingBox's p1.x or p1.z, depenging on m_Direction + int m_SpawnerPosition; ///< If <0, no spawner; otherwise an offset from m_BoundingBox's p1.x or p1.z, depenging on m_Direction + bool m_HasTracks; ///< If true, random tracks will be placed on the floor + + cMineShaftCorridor( + cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, + const cCuboid & a_BoundingBox, int a_NumSegments, eDirection a_Direction, + cNoise & a_Noise + ); + + // cMineShaft overrides: + virtual void AppendBranches(int a_RecursionLevel, cNoise & a_Noise) override; + virtual void ProcessChunk(cChunkDesc & a_ChunkDesc) override; + + /// Places a chest, if the corridor has one + void PlaceChest(cChunkDesc & a_ChunkDesc); + + /// If this corridor has tracks, places them randomly + void PlaceTracks(cChunkDesc & a_ChunkDesc); + + /// If this corridor has a spawner, places the spawner + void PlaceSpawner(cChunkDesc & a_ChunkDesc); + + /// Randomly places torches around the central beam block + void PlaceTorches(cChunkDesc & a_ChunkDesc); +} ; + + + + + +class cMineShaftCrossing : + public cMineShaft +{ + typedef cMineShaft super; + +public: + /** Creates a new Crossing attached to the specified pivot point and direction. + Checks all ParentSystem's objects and disallows intersecting. Initializes the new object to fit. + May return NULL if cannot fit. + */ + static cMineShaft * CreateAndFit( + cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, + int a_PivotX, int a_PivotY, int a_PivotZ, eDirection a_Direction, + cNoise & a_Noise + ); + +protected: + cMineShaftCrossing(cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, const cCuboid & a_BoundingBox); + + // cMineShaft overrides: + virtual void AppendBranches(int a_RecursionLevel, cNoise & a_Noise) override; + virtual void ProcessChunk(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +class cMineShaftStaircase : + public cMineShaft +{ + typedef cMineShaft super; + +public: + enum eSlope + { + sUp, + sDown, + } ; + + /** Creates a new Staircase attached to the specified pivot point and direction. + Checks all ParentSystem's objects and disallows intersecting. Initializes the new object to fit. + May return NULL if cannot fit. + */ + static cMineShaft * CreateAndFit( + cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, + int a_PivotX, int a_PivotY, int a_PivotZ, eDirection a_Direction, + cNoise & a_Noise + ); + +protected: + eDirection m_Direction; + eSlope m_Slope; + + + cMineShaftStaircase( + cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, + const cCuboid & a_BoundingBox, + eDirection a_Direction, + eSlope a_Slope + ); + + // cMineShaft overrides: + virtual void AppendBranches(int a_RecursionLevel, cNoise & a_Noise) override; + virtual void ProcessChunk(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +class cStructGenMineShafts::cMineShaftSystem +{ +public: + int m_BlockX, m_BlockZ; ///< The pivot point on which the system is generated + int m_GridSize; ///< Maximum offset of the dirtroom from grid center, * 2, in each direction + int m_MaxRecursion; ///< Maximum recursion level (initialized from cStructGenMineShafts::m_MaxRecursion) + int m_ProbLevelCorridor; ///< Probability level of a branch object being the corridor + int m_ProbLevelCrossing; ///< Probability level of a branch object being the crossing, minus Corridor + int m_ProbLevelStaircase; ///< Probability level of a branch object being the staircase, minus Crossing + int m_ChanceChest; ///< Chance [0 .. 250] that a corridor has a chest in it + int m_ChanceSpawner; ///< Chance [0 .. 250] that a corridor has a spawner in it + int m_ChanceTorch; ///< Chance [0 .. 10k] for a torch appearing attached to a corridor's beam + cMineShafts m_MineShafts; ///< List of cMineShaft descendants that comprise this system + cCuboid m_BoundingBox; ///< Bounding box into which all of the components need to fit + + /// Creates and generates the entire system + cMineShaftSystem( + int a_BlockX, int a_BlockZ, int a_GridSize, int a_MaxSystemSize, cNoise & a_Noise, + int a_ProbLevelCorridor, int a_ProbLevelCrossing, int a_ProbLevelStaircase + ); + + ~cMineShaftSystem(); + + /// Carves the system into the chunk data + void ProcessChunk(cChunkDesc & a_Chunk); + + /** Creates new cMineShaft descendant connected at the specified point, heading the specified direction, + if it fits, appends it to the list and calls its AppendBranches() + */ + void AppendBranch( + int a_BlockX, int a_BlockY, int a_BlockZ, + cMineShaft::eDirection a_Direction, cNoise & a_Noise, + int a_RecursionLevel + ); + + /// Returns true if none of the objects in m_MineShafts intersect with the specified bounding box and the bounding box is valid + bool CanAppend(const cCuboid & a_BoundingBox); +} ; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenMineShafts::cMineShaftSystem: + +cStructGenMineShafts::cMineShaftSystem::cMineShaftSystem( + int a_BlockX, int a_BlockZ, int a_GridSize, int a_MaxSystemSize, cNoise & a_Noise, + int a_ProbLevelCorridor, int a_ProbLevelCrossing, int a_ProbLevelStaircase +) : + m_BlockX(a_BlockX), + m_BlockZ(a_BlockZ), + m_GridSize(a_GridSize), + m_MaxRecursion(8), // TODO: settable + m_ProbLevelCorridor(a_ProbLevelCorridor), + m_ProbLevelCrossing(a_ProbLevelCrossing), + m_ProbLevelStaircase(a_ProbLevelStaircase + 1), + m_ChanceChest(12), // TODO: settable + m_ChanceSpawner(12), // TODO: settable + m_ChanceTorch(1000) // TODO: settable +{ + m_MineShafts.reserve(100); + + cMineShaft * Start = new cMineShaftDirtRoom(*this, a_Noise); + m_MineShafts.push_back(Start); + + m_BoundingBox.Assign( + Start->m_BoundingBox.p1.x - a_MaxSystemSize / 2, 2, Start->m_BoundingBox.p1.z - a_MaxSystemSize / 2, + Start->m_BoundingBox.p2.x + a_MaxSystemSize / 2, 50, Start->m_BoundingBox.p2.z + a_MaxSystemSize / 2 + ); + + Start->AppendBranches(0, a_Noise); + + for (cMineShafts::const_iterator itr = m_MineShafts.begin(), end = m_MineShafts.end(); itr != end; ++itr) + { + ASSERT((*itr)->m_BoundingBox.IsSorted()); + } // for itr - m_MineShafts[] +} + + + + + +cStructGenMineShafts::cMineShaftSystem::~cMineShaftSystem() +{ + for (cMineShafts::iterator itr = m_MineShafts.begin(), end = m_MineShafts.end(); itr != end; ++itr) + { + delete *itr; + } // for itr - m_MineShafts[] + m_MineShafts.clear(); +} + + + + + +void cStructGenMineShafts::cMineShaftSystem::ProcessChunk(cChunkDesc & a_Chunk) +{ + for (cMineShafts::const_iterator itr = m_MineShafts.begin(), end = m_MineShafts.end(); itr != end; ++itr) + { + (*itr)->ProcessChunk(a_Chunk); + } // for itr - m_MineShafts[] +} + + + + + +void cStructGenMineShafts::cMineShaftSystem::AppendBranch( + int a_PivotX, int a_PivotY, int a_PivotZ, + cMineShaft::eDirection a_Direction, cNoise & a_Noise, + int a_RecursionLevel +) +{ + if (a_RecursionLevel > m_MaxRecursion) + { + return; + } + + cMineShaft * Next = NULL; + int rnd = (a_Noise.IntNoise3DInt(a_PivotX, a_PivotY + a_RecursionLevel * 16, a_PivotZ) / 13) % m_ProbLevelStaircase; + if (rnd < m_ProbLevelCorridor) + { + Next = cMineShaftCorridor::CreateAndFit(*this, a_PivotX, a_PivotY, a_PivotZ, a_Direction, a_Noise); + } + else if (rnd < m_ProbLevelCrossing) + { + Next = cMineShaftCrossing::CreateAndFit(*this, a_PivotX, a_PivotY, a_PivotZ, a_Direction, a_Noise); + } + else + { + Next = cMineShaftStaircase::CreateAndFit(*this, a_PivotX, a_PivotY, a_PivotZ, a_Direction, a_Noise); + } + if (Next == NULL) + { + return; + } + m_MineShafts.push_back(Next); + Next->AppendBranches(a_RecursionLevel + 1, a_Noise); +} + + + + + +bool cStructGenMineShafts::cMineShaftSystem::CanAppend(const cCuboid & a_BoundingBox) +{ + if (!a_BoundingBox.IsCompletelyInside(m_BoundingBox)) + { + // Too far away, or too low / too high + return false; + } + + // Check intersections: + for (cMineShafts::const_iterator itr = m_MineShafts.begin(), end = m_MineShafts.end(); itr != end; ++itr) + { + if ((*itr)->DoesIntersect(a_BoundingBox)) + { + return false; + } + } // for itr - m_MineShafts[] + return true; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cMineShaftDirtRoom: + +cMineShaftDirtRoom::cMineShaftDirtRoom(cStructGenMineShafts::cMineShaftSystem & a_Parent, cNoise & a_Noise) : + super(a_Parent, mskDirtRoom) +{ + // Make the room of random size, min 10 x 4 x 10; max 18 x 12 x 18: + int rnd = a_Noise.IntNoise3DInt(a_Parent.m_BlockX, 0, a_Parent.m_BlockZ) / 7; + int OfsX = (rnd % a_Parent.m_GridSize) - a_Parent.m_GridSize / 2; + rnd >>= 12; + int OfsZ = (rnd % a_Parent.m_GridSize) - a_Parent.m_GridSize / 2; + rnd = a_Noise.IntNoise3DInt(a_Parent.m_BlockX, 1000, a_Parent.m_BlockZ) / 11; + m_BoundingBox.p1.x = a_Parent.m_BlockX + OfsX; + m_BoundingBox.p2.x = m_BoundingBox.p1.x + 10 + (rnd % 8); + rnd >>= 4; + m_BoundingBox.p1.z = a_Parent.m_BlockZ + OfsZ; + m_BoundingBox.p2.z = m_BoundingBox.p1.z + 10 + (rnd % 8); + rnd >>= 4; + m_BoundingBox.p1.y = 20; + m_BoundingBox.p2.y = 24 + rnd % 8; +} + + + + + +void cMineShaftDirtRoom::AppendBranches(int a_RecursionLevel, cNoise & a_Noise) +{ + int Height = m_BoundingBox.DifY() - 3; + for (int x = m_BoundingBox.p1.x + 1; x < m_BoundingBox.p2.x; x += 4) + { + int rnd = a_Noise.IntNoise3DInt(x, a_RecursionLevel, m_BoundingBox.p1.z) / 7; + m_ParentSystem.AppendBranch(x, m_BoundingBox.p1.y + (rnd % Height), m_BoundingBox.p1.z - 1, dirZM, a_Noise, a_RecursionLevel); + rnd >>= 4; + m_ParentSystem.AppendBranch(x, m_BoundingBox.p1.y + (rnd % Height), m_BoundingBox.p2.z + 1, dirZP, a_Noise, a_RecursionLevel); + } + + for (int z = m_BoundingBox.p1.z + 1; z < m_BoundingBox.p2.z; z += 4) + { + int rnd = a_Noise.IntNoise3DInt(m_BoundingBox.p1.x, a_RecursionLevel, z) / 13; + m_ParentSystem.AppendBranch(m_BoundingBox.p1.x - 1, m_BoundingBox.p1.y + (rnd % Height), z, dirXM, a_Noise, a_RecursionLevel); + rnd >>= 4; + m_ParentSystem.AppendBranch(m_BoundingBox.p2.x + 1, m_BoundingBox.p1.y + (rnd % Height), z, dirXP, a_Noise, a_RecursionLevel); + } +} + + + + + +void cMineShaftDirtRoom::ProcessChunk(cChunkDesc & a_ChunkDesc) +{ + int BlockX = a_ChunkDesc.GetChunkX() * cChunkDef::Width; + int BlockZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width; + if ( + (m_BoundingBox.p1.x > BlockX + cChunkDef::Width) || + (m_BoundingBox.p1.z > BlockZ + cChunkDef::Width) || + (m_BoundingBox.p2.x < BlockX) || + (m_BoundingBox.p2.z < BlockZ) + ) + { + // Early bailout - cannot intersect this chunk + return; + } + + // Chunk-relative coords of the boundaries: + int MinX = std::max(BlockX, m_BoundingBox.p1.x) - BlockX; + int MaxX = std::min(BlockX + cChunkDef::Width, m_BoundingBox.p2.x + 1) - BlockX; + int MinZ = std::max(BlockZ, m_BoundingBox.p1.z) - BlockZ; + int MaxZ = std::min(BlockZ + cChunkDef::Width, m_BoundingBox.p2.z + 1) - BlockZ; + + // Carve the room out: + for (int z = MinZ; z < MaxZ; z++) + { + for (int x = MinX; x < MaxX; x++) + { + for (int y = m_BoundingBox.p1.y + 1; y < m_BoundingBox.p2.y; y++) + { + a_ChunkDesc.SetBlockType(x, y, z, E_BLOCK_AIR); + } + if (a_ChunkDesc.GetBlockType(x, m_BoundingBox.p1.y, z) != E_BLOCK_AIR) + { + a_ChunkDesc.SetBlockType(x, m_BoundingBox.p1.y, z, E_BLOCK_DIRT); + } + } // for x + } // for z +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cMineShaftCorridor: + +cMineShaftCorridor::cMineShaftCorridor( + cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, + const cCuboid & a_BoundingBox, int a_NumSegments, eDirection a_Direction, + cNoise & a_Noise +) : + super(a_ParentSystem, mskCorridor, a_BoundingBox), + m_NumSegments(a_NumSegments), + m_Direction(a_Direction), + m_ChestPosition(-1), + m_SpawnerPosition(-1) +{ + int rnd = a_Noise.IntNoise3DInt(a_BoundingBox.p1.x, a_BoundingBox.p1.y, a_BoundingBox.p1.z) / 7; + for (int i = 0; i < a_NumSegments; i++) + { + m_HasFullBeam[i] = (rnd % 4) < 3; // 75 % chance of full beam + rnd >>= 2; + } + m_HasTracks = ((rnd % 4) < 2); // 50 % chance of tracks + + rnd = a_Noise.IntNoise3DInt(a_BoundingBox.p1.z, a_BoundingBox.p1.x, a_BoundingBox.p1.y) / 7; + int ChestCheck = rnd % 250; + rnd >>= 8; + int SpawnerCheck = rnd % 250; + rnd >>= 8; + if (ChestCheck < a_ParentSystem.m_ChanceChest) + { + m_ChestPosition = rnd % (a_NumSegments * 5); + } + if ((a_NumSegments < 4) && (SpawnerCheck < a_ParentSystem.m_ChanceSpawner)) + { + m_SpawnerPosition = rnd % (a_NumSegments * 5); + } +} + + + + + +cMineShaft * cMineShaftCorridor::CreateAndFit( + cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, + int a_PivotX, int a_PivotY, int a_PivotZ, eDirection a_Direction, + cNoise & a_Noise +) +{ + cCuboid BoundingBox(a_PivotX, a_PivotY - 1, a_PivotZ); + BoundingBox.p2.y += 3; + int rnd = a_Noise.IntNoise3DInt(a_PivotX, a_PivotY + a_ParentSystem.m_MineShafts.size(), a_PivotZ) / 7; + int NumSegments = 2 + (rnd) % (MAX_SEGMENTS - 1); // 2 .. MAX_SEGMENTS + switch (a_Direction) + { + case dirXP: BoundingBox.p2.x += NumSegments * 5 - 1; BoundingBox.p1.z -= 1; BoundingBox.p2.z += 1; break; + case dirXM: BoundingBox.p1.x -= NumSegments * 5 - 1; BoundingBox.p1.z -= 1; BoundingBox.p2.z += 1; break; + case dirZP: BoundingBox.p2.z += NumSegments * 5 - 1; BoundingBox.p1.x -= 1; BoundingBox.p2.x += 1; break; + case dirZM: BoundingBox.p1.z -= NumSegments * 5 - 1; BoundingBox.p1.x -= 1; BoundingBox.p2.x += 1; break; + } + if (!a_ParentSystem.CanAppend(BoundingBox)) + { + return NULL; + } + return new cMineShaftCorridor(a_ParentSystem, BoundingBox, NumSegments, a_Direction, a_Noise); +} + + + + + +void cMineShaftCorridor::AppendBranches(int a_RecursionLevel, cNoise & a_Noise) +{ + int rnd = a_Noise.IntNoise3DInt(m_BoundingBox.p1.x, m_BoundingBox.p1.y + a_RecursionLevel, m_BoundingBox.p1.z) / 7; + // Prefer the same height, but allow for up to one block height displacement: + int Height = m_BoundingBox.p1.y + ((rnd % 4) + ((rnd >> 3) % 3)) / 2; + switch (m_Direction) + { + case dirXM: + { + m_ParentSystem.AppendBranch(m_BoundingBox.p1.x - 1, Height, m_BoundingBox.p1.z + 1, dirXM, a_Noise, a_RecursionLevel); + for (int i = m_NumSegments; i >= 0; i--) + { + int rnd = a_Noise.IntNoise3DInt(m_BoundingBox.p1.x + i + 10, m_BoundingBox.p1.y + a_RecursionLevel, m_BoundingBox.p1.z) / 11; + int Height = m_BoundingBox.p1.y + ((rnd % 4) + ((rnd >> 3) % 3)) / 2; + rnd >>= 6; + int Ofs = 1 + rnd % (m_NumSegments * 5 - 2); + m_ParentSystem.AppendBranch(m_BoundingBox.p1.x + Ofs, Height, m_BoundingBox.p1.z - 1, dirZM, a_Noise, a_RecursionLevel); + m_ParentSystem.AppendBranch(m_BoundingBox.p1.x + Ofs, Height, m_BoundingBox.p2.z + 1, dirZP, a_Noise, a_RecursionLevel); + } + break; + } + + case dirXP: + { + m_ParentSystem.AppendBranch(m_BoundingBox.p2.x + 1, Height, m_BoundingBox.p1.z + 1, dirXP, a_Noise, a_RecursionLevel); + for (int i = m_NumSegments; i >= 0; i--) + { + int rnd = a_Noise.IntNoise3DInt(m_BoundingBox.p1.x + i + 10, m_BoundingBox.p1.y + a_RecursionLevel, m_BoundingBox.p1.z) / 11; + int Height = m_BoundingBox.p1.y + ((rnd % 4) + ((rnd >> 3) % 3)) / 2; + rnd >>= 6; + int Ofs = 1 + rnd % (m_NumSegments * 5 - 2); + m_ParentSystem.AppendBranch(m_BoundingBox.p1.x + Ofs, Height, m_BoundingBox.p1.z - 1, dirZM, a_Noise, a_RecursionLevel); + m_ParentSystem.AppendBranch(m_BoundingBox.p1.x + Ofs, Height, m_BoundingBox.p2.z + 1, dirZP, a_Noise, a_RecursionLevel); + } + break; + } + + case dirZM: + { + m_ParentSystem.AppendBranch(m_BoundingBox.p1.x + 1, Height, m_BoundingBox.p1.z - 1, dirZM, a_Noise, a_RecursionLevel); + for (int i = m_NumSegments; i >= 0; i--) + { + int rnd = a_Noise.IntNoise3DInt(m_BoundingBox.p1.x + i + 10, m_BoundingBox.p1.y + a_RecursionLevel, m_BoundingBox.p1.z) / 11; + int Height = m_BoundingBox.p1.y + ((rnd % 4) + ((rnd >> 3) % 3)) / 2; + rnd >>= 6; + int Ofs = 1 + rnd % (m_NumSegments * 5 - 2); + m_ParentSystem.AppendBranch(m_BoundingBox.p1.x - 1, Height, m_BoundingBox.p1.z + Ofs, dirXM, a_Noise, a_RecursionLevel); + m_ParentSystem.AppendBranch(m_BoundingBox.p2.x + 1, Height, m_BoundingBox.p1.z + Ofs, dirXP, a_Noise, a_RecursionLevel); + } + break; + } + + case dirZP: + { + m_ParentSystem.AppendBranch(m_BoundingBox.p1.x + 1, Height, m_BoundingBox.p2.z + 1, dirZP, a_Noise, a_RecursionLevel); + for (int i = m_NumSegments; i >= 0; i--) + { + int rnd = a_Noise.IntNoise3DInt(m_BoundingBox.p1.x + i + 10, m_BoundingBox.p1.y + a_RecursionLevel, m_BoundingBox.p1.z) / 11; + int Height = m_BoundingBox.p1.y + ((rnd % 4) + ((rnd >> 3) % 3)) / 2; + rnd >>= 6; + int Ofs = 1 + rnd % (m_NumSegments * 5 - 2); + m_ParentSystem.AppendBranch(m_BoundingBox.p1.x - 1, Height, m_BoundingBox.p1.z + Ofs, dirXM, a_Noise, a_RecursionLevel); + m_ParentSystem.AppendBranch(m_BoundingBox.p2.x + 1, Height, m_BoundingBox.p1.z + Ofs, dirXP, a_Noise, a_RecursionLevel); + } + break; + } + } // switch (m_Direction) +} + + + + + +void cMineShaftCorridor::ProcessChunk(cChunkDesc & a_ChunkDesc) +{ + int BlockX = a_ChunkDesc.GetChunkX() * cChunkDef::Width; + int BlockZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width; + cCuboid RelBoundingBox(m_BoundingBox); + RelBoundingBox.Move(-BlockX, 0, -BlockZ); + RelBoundingBox.p1.y += 1; + RelBoundingBox.p2.y -= 1; + cCuboid Top(RelBoundingBox); + Top.p2.y += 1; + Top.p1.y = Top.p2.y; + a_ChunkDesc.FillRelCuboid(RelBoundingBox, E_BLOCK_AIR, 0); + a_ChunkDesc.RandomFillRelCuboid(Top, E_BLOCK_AIR, 0, (BlockX ^ (BlockZ + BlockX)), 8000); + if (m_SpawnerPosition >= 0) + { + // Cobwebs around the spider spawner + a_ChunkDesc.RandomFillRelCuboid(RelBoundingBox, E_BLOCK_COBWEB, 0, (BlockX ^ (BlockZ + BlockZ)), 8000); + a_ChunkDesc.RandomFillRelCuboid(Top, E_BLOCK_COBWEB, 0, (BlockX ^ (BlockZ + BlockX)), 5000); + } + a_ChunkDesc.RandomFillRelCuboid(Top, E_BLOCK_COBWEB, 0, (BlockX ^ (BlockZ + BlockX + 10)), 500); + RelBoundingBox.p1.y = m_BoundingBox.p1.y; + RelBoundingBox.p2.y = m_BoundingBox.p1.y; + a_ChunkDesc.FloorRelCuboid(RelBoundingBox, E_BLOCK_PLANKS, 0); + switch (m_Direction) + { + case dirXM: + case dirXP: + { + int y1 = m_BoundingBox.p1.y + 1; + int y2 = m_BoundingBox.p1.y + 2; + int y3 = m_BoundingBox.p1.y + 3; + int z1 = m_BoundingBox.p1.z - BlockZ; + int z2 = m_BoundingBox.p2.z - BlockZ; + for (int i = 0; i < m_NumSegments; i++) + { + int x = m_BoundingBox.p1.x + i * 5 + 2 - BlockX; + if ((x < 0) || (x >= cChunkDef::Width)) + { + continue; + } + if ((z1 >= 0) && (z1 < cChunkDef::Width)) + { + a_ChunkDesc.SetBlockTypeMeta(x, y1, z1, E_BLOCK_FENCE, 0); + a_ChunkDesc.SetBlockTypeMeta(x, y2, z1, E_BLOCK_FENCE, 0); + a_ChunkDesc.SetBlockTypeMeta(x, y3, z1, E_BLOCK_PLANKS, 0); + } + if ((z2 >= 0) && (z2 < cChunkDef::Width)) + { + a_ChunkDesc.SetBlockTypeMeta(x, y1, z2, E_BLOCK_FENCE, 0); + a_ChunkDesc.SetBlockTypeMeta(x, y2, z2, E_BLOCK_FENCE, 0); + a_ChunkDesc.SetBlockTypeMeta(x, y3, z2, E_BLOCK_PLANKS, 0); + } + if ((z1 >= -1) && (z1 < cChunkDef::Width - 1) && m_HasFullBeam[i]) + { + a_ChunkDesc.SetBlockTypeMeta(x, y3, z1 + 1, E_BLOCK_PLANKS, 0); + } + } // for i - NumSegments + break; + } + + case dirZM: + case dirZP: + { + int y1 = m_BoundingBox.p1.y + 1; + int y2 = m_BoundingBox.p1.y + 2; + int y3 = m_BoundingBox.p1.y + 3; + int x1 = m_BoundingBox.p1.x - BlockX; + int x2 = m_BoundingBox.p2.x - BlockX; + for (int i = 0; i < m_NumSegments; i++) + { + int z = m_BoundingBox.p1.z + i * 5 + 2 - BlockZ; + if ((z < 0) || (z >= cChunkDef::Width)) + { + continue; + } + if ((x1 >= 0) && (x1 < cChunkDef::Width)) + { + a_ChunkDesc.SetBlockTypeMeta(x1, y1, z, E_BLOCK_FENCE, 0); + a_ChunkDesc.SetBlockTypeMeta(x1, y2, z, E_BLOCK_FENCE, 0); + a_ChunkDesc.SetBlockTypeMeta(x1, y3, z, E_BLOCK_PLANKS, 0); + } + if ((x2 >= 0) && (x2 < cChunkDef::Width)) + { + a_ChunkDesc.SetBlockTypeMeta(x2, y1, z, E_BLOCK_FENCE, 0); + a_ChunkDesc.SetBlockTypeMeta(x2, y2, z, E_BLOCK_FENCE, 0); + a_ChunkDesc.SetBlockTypeMeta(x2, y3, z, E_BLOCK_PLANKS, 0); + } + if ((x1 >= -1) && (x1 < cChunkDef::Width - 1) && m_HasFullBeam[i]) + { + a_ChunkDesc.SetBlockTypeMeta(x1 + 1, y3, z, E_BLOCK_PLANKS, 0); + } + } // for i - NumSegments + break; + } // case dirZ? + } // for i + + PlaceChest(a_ChunkDesc); + PlaceTracks(a_ChunkDesc); + PlaceSpawner(a_ChunkDesc); // (must be after Tracks!) + PlaceTorches(a_ChunkDesc); +} + + + + + +void cMineShaftCorridor::PlaceChest(cChunkDesc & a_ChunkDesc) +{ + static const cLootProbab LootProbab[] = + { + // Item, MinAmount, MaxAmount, Weight + { cItem(E_ITEM_IRON), 1, 5, 10 }, + { cItem(E_ITEM_GOLD), 1, 3, 5 }, + { cItem(E_ITEM_REDSTONE_DUST), 4, 9, 5 }, + { cItem(E_ITEM_DIAMOND), 1, 2, 3 }, + { cItem(E_ITEM_DYE, 1, 4), 4, 9, 5 }, // lapis lazuli dye + { cItem(E_ITEM_COAL), 3, 8, 10 }, + { cItem(E_ITEM_BREAD), 1, 3, 15 }, + { cItem(E_ITEM_IRON_PICKAXE), 1, 1, 1 }, + { cItem(E_BLOCK_MINECART_TRACKS), 4, 8, 1 }, + { cItem(E_ITEM_MELON_SEEDS), 2, 4, 10 }, + { cItem(E_ITEM_PUMPKIN_SEEDS), 2, 4, 10 }, + } ; + + if (m_ChestPosition < 0) + { + return; + } + + int BlockX = a_ChunkDesc.GetChunkX() * cChunkDef::Width; + int BlockZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width; + int x, z; + NIBBLETYPE Meta = 0; + switch (m_Direction) + { + case dirXM: + case dirXP: + { + x = m_BoundingBox.p1.x + m_ChestPosition - BlockX; + z = m_BoundingBox.p1.z - BlockZ; + Meta = E_META_CHEST_FACING_ZP; + break; + } + + case dirZM: + case dirZP: + default: + { + x = m_BoundingBox.p1.x - BlockX; + z = m_BoundingBox.p1.z + m_ChestPosition - BlockZ; + Meta = E_META_CHEST_FACING_XP; + break; + } + } // switch (Dir) + + if ( + (x >= 0) && (x < cChunkDef::Width) && + (z >= 0) && (z < cChunkDef::Width) + ) + { + a_ChunkDesc.SetBlockTypeMeta(x, m_BoundingBox.p1.y + 1, z, E_BLOCK_CHEST, Meta); + cChestEntity * ChestEntity = (cChestEntity *)a_ChunkDesc.GetBlockEntity(x, m_BoundingBox.p1.y + 1, z); + ASSERT((ChestEntity != NULL) && (ChestEntity->GetBlockType() == E_BLOCK_CHEST)); + cNoise Noise(a_ChunkDesc.GetChunkX() ^ a_ChunkDesc.GetChunkZ()); + int NumSlots = 3 + ((Noise.IntNoise3DInt(x, m_BoundingBox.p1.y, z) / 11) % 4); + int Seed = Noise.IntNoise2DInt(x, z); + ChestEntity->GetContents().GenerateRandomLootWithBooks(LootProbab, ARRAYCOUNT(LootProbab), NumSlots, Seed); + } +} + + + + + +void cMineShaftCorridor::PlaceTracks(cChunkDesc & a_ChunkDesc) +{ + if (!m_HasTracks) + { + return; + } + cCuboid Box(m_BoundingBox); + Box.Move(-a_ChunkDesc.GetChunkX() * cChunkDef::Width, 1, -a_ChunkDesc.GetChunkZ() * cChunkDef::Width); + Box.p2.y = Box.p1.y; + Box.p1.x += 1; + Box.p2.x -= 1; + Box.p1.z += 1; + Box.p2.z -= 1; + NIBBLETYPE Meta = 0; + switch (m_Direction) + { + case dirXM: + case dirXP: + { + Meta = E_META_TRACKS_X; + break; + } + + case dirZM: + case dirZP: + { + Meta = E_META_TRACKS_Z; + break; + } + } // switch (direction) + a_ChunkDesc.RandomFillRelCuboid(Box, E_BLOCK_MINECART_TRACKS, Meta, a_ChunkDesc.GetChunkX() + a_ChunkDesc.GetChunkZ(), 6000); +} + + + + + +void cMineShaftCorridor::PlaceSpawner(cChunkDesc & a_ChunkDesc) +{ + if (m_SpawnerPosition < 0) + { + // No spawner in this corridor + return; + } + int SpawnerRelX = m_BoundingBox.p1.x + 1 - a_ChunkDesc.GetChunkX() * cChunkDef::Width; + int SpawnerRelZ = m_BoundingBox.p1.z + 1 - a_ChunkDesc.GetChunkZ() * cChunkDef::Width; + switch (m_Direction) + { + case dirXM: + case dirXP: + { + SpawnerRelX += m_SpawnerPosition - 1; + break; + } + case dirZM: + case dirZP: + { + SpawnerRelZ += m_SpawnerPosition - 1; + break; + } + } + if ( + (SpawnerRelX >= 0) && (SpawnerRelX < cChunkDef::Width) && + (SpawnerRelZ >= 0) && (SpawnerRelZ < cChunkDef::Width) + ) + { + a_ChunkDesc.SetBlockTypeMeta(SpawnerRelX, m_BoundingBox.p1.y + 1, SpawnerRelZ, E_BLOCK_MOB_SPAWNER, 0); + // TODO: The spawner needs its accompanying cMobSpawnerEntity, when implemented + } +} + + + + + +void cMineShaftCorridor::PlaceTorches(cChunkDesc & a_ChunkDesc) +{ + cNoise Noise(m_BoundingBox.p1.x); + switch (m_Direction) + { + case dirXM: + case dirXP: + { + int z = m_BoundingBox.p1.z + 1 - a_ChunkDesc.GetChunkZ() * cChunkDef::Width; + if ((z < 0) || (z >= cChunkDef::Width)) + { + return; + } + int BlockX = a_ChunkDesc.GetChunkX() * cChunkDef::Width; + for (int i = 0; i < m_NumSegments; i++) + { + if (!m_HasFullBeam[i]) + { + continue; + } + int x = m_BoundingBox.p1.x + i * 5 + 1 - BlockX; + if ((x >= 0) && (x < cChunkDef::Width)) + { + if (((Noise.IntNoise2DInt(x, z) / 7) % 10000) < m_ParentSystem.m_ChanceTorch) + { + a_ChunkDesc.SetBlockTypeMeta(x, m_BoundingBox.p2.y, z, E_BLOCK_TORCH, E_META_TORCH_XP); + } + } + x += 2; + if ((x >= 0) && (x < cChunkDef::Width)) + { + if (((Noise.IntNoise2DInt(x, z) / 7) % 10000) < m_ParentSystem.m_ChanceTorch) + { + a_ChunkDesc.SetBlockTypeMeta(x, m_BoundingBox.p2.y, z, E_BLOCK_TORCH, E_META_TORCH_XM); + } + } + } // for i + break; + } + + case dirZM: + case dirZP: + { + int x = m_BoundingBox.p1.x + 1 - a_ChunkDesc.GetChunkX() * cChunkDef::Width; + if ((x < 0) || (x >= cChunkDef::Width)) + { + return; + } + int BlockZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width; + for (int i = 0; i < m_NumSegments; i++) + { + if (!m_HasFullBeam[i]) + { + continue; + } + int z = m_BoundingBox.p1.z + i * 5 + 1 - BlockZ; + if ((z >= 0) && (z < cChunkDef::Width)) + { + if (((Noise.IntNoise2DInt(x, z) / 7) % 10000) < m_ParentSystem.m_ChanceTorch) + { + a_ChunkDesc.SetBlockTypeMeta(x, m_BoundingBox.p2.y, z, E_BLOCK_TORCH, E_META_TORCH_ZP); + } + } + z += 2; + if ((z >= 0) && (z < cChunkDef::Width)) + { + if (((Noise.IntNoise2DInt(x, z) / 7) % 10000) < m_ParentSystem.m_ChanceTorch) + { + a_ChunkDesc.SetBlockTypeMeta(x, m_BoundingBox.p2.y, z, E_BLOCK_TORCH, E_META_TORCH_ZM); + } + } + } // for i + break; + } + } // switch (direction) +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cMineShaftCrossing: + +cMineShaftCrossing::cMineShaftCrossing(cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, const cCuboid & a_BoundingBox) : + super(a_ParentSystem, mskCrossing, a_BoundingBox) +{ +} + + + + + +cMineShaft * cMineShaftCrossing::CreateAndFit( + cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, + int a_PivotX, int a_PivotY, int a_PivotZ, eDirection a_Direction, + cNoise & a_Noise +) +{ + cCuboid BoundingBox(a_PivotX, a_PivotY - 1, a_PivotZ); + int rnd = a_Noise.IntNoise3DInt(a_PivotX, a_PivotY + a_ParentSystem.m_MineShafts.size(), a_PivotZ) / 7; + BoundingBox.p2.y += 3; + if ((rnd % 4) < 2) + { + // 2-level crossing: + BoundingBox.p2.y += 4; + rnd >>= 2; + if ((rnd % 4) < 2) + { + // This is the higher level: + BoundingBox.p1.y -= 4; + BoundingBox.p2.y -= 4; + } + } + rnd >>= 2; + switch (a_Direction) + { + case dirXP: BoundingBox.p2.x += 4; BoundingBox.p1.z -= 2; BoundingBox.p2.z += 2; break; + case dirXM: BoundingBox.p1.x -= 4; BoundingBox.p1.z -= 2; BoundingBox.p2.z += 2; break; + case dirZP: BoundingBox.p2.z += 4; BoundingBox.p1.x -= 2; BoundingBox.p2.x += 2; break; + case dirZM: BoundingBox.p1.z -= 4; BoundingBox.p1.x -= 2; BoundingBox.p2.x += 2; break; + } + if (!a_ParentSystem.CanAppend(BoundingBox)) + { + return NULL; + } + return new cMineShaftCrossing(a_ParentSystem, BoundingBox); +} + + + + + +void cMineShaftCrossing::AppendBranches(int a_RecursionLevel, cNoise & a_Noise) +{ + struct + { + int x, y, z; + eDirection dir; + } Exits[] = + { + // Bottom level: + {-1, 1, 2, dirXM}, + { 2, 1, -1, dirZM}, + { 5, 1, 2, dirXP}, + { 2, 1, 5, dirZP}, + // Top level: + {-1, 5, 2, dirXM}, + { 2, 5, -1, dirZM}, + { 5, 5, 2, dirXP}, + { 2, 5, 5, dirZP}, + } ; + for (unsigned int i = 0; i < ARRAYCOUNT(Exits); i++) + { + if (m_BoundingBox.p1.y + Exits[i].y >= m_BoundingBox.p2.y) + { + // This exit is not available (two-level exit on a one-level crossing) + continue; + } + + int Height = m_BoundingBox.p1.y + Exits[i].y; + m_ParentSystem.AppendBranch(m_BoundingBox.p1.x + Exits[i].x, Height, m_BoundingBox.p1.z + Exits[i].z, Exits[i].dir, a_Noise, a_RecursionLevel); + } // for i +} + + + + + +void cMineShaftCrossing::ProcessChunk(cChunkDesc & a_ChunkDesc) +{ + int BlockX = a_ChunkDesc.GetChunkX() * cChunkDef::Width; + int BlockZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width; + cCuboid box(m_BoundingBox); + box.Move(-BlockX, 0, -BlockZ); + if ((box.p2.x < 0) || (box.p2.z < 0) || (box.p1.x >= cChunkDef::Width) || (box.p1.z > cChunkDef::Width)) + { + // Does not intersect this chunk + return; + } + int Floor = box.p1.y + 1; + int Ceil = box.p2.y; + + // The supports: + a_ChunkDesc.FillRelCuboid(box.p1.x + 1, box.p1.x + 1, Floor, Ceil, box.p1.z + 1, box.p1.z + 1, E_BLOCK_PLANKS, 0); + a_ChunkDesc.FillRelCuboid(box.p2.x - 1, box.p2.x - 1, Floor, Ceil, box.p1.z + 1, box.p1.z + 1, E_BLOCK_PLANKS, 0); + a_ChunkDesc.FillRelCuboid(box.p1.x + 1, box.p1.x + 1, Floor, Ceil, box.p2.z - 1, box.p2.z - 1, E_BLOCK_PLANKS, 0); + a_ChunkDesc.FillRelCuboid(box.p2.x - 1, box.p2.x - 1, Floor, Ceil, box.p2.z - 1, box.p2.z - 1, E_BLOCK_PLANKS, 0); + + // The air in between: + a_ChunkDesc.FillRelCuboid(box.p1.x + 2, box.p1.x + 2, Floor, Ceil, box.p1.z + 1, box.p2.z - 1, E_BLOCK_AIR, 0); + a_ChunkDesc.FillRelCuboid(box.p1.x + 1, box.p2.x - 1, Floor, Ceil, box.p1.z + 2, box.p1.z + 2, E_BLOCK_AIR, 0); + + // The air on the edges: + int Mid = Floor + 2; + a_ChunkDesc.FillRelCuboid(box.p1.x, box.p1.x, Floor, Mid, box.p1.z + 1, box.p2.z - 1, E_BLOCK_AIR, 0); + a_ChunkDesc.FillRelCuboid(box.p2.x, box.p2.x, Floor, Mid, box.p1.z + 1, box.p2.z - 1, E_BLOCK_AIR, 0); + a_ChunkDesc.FillRelCuboid(box.p1.x + 1, box.p2.x - 1, Floor, Mid, box.p1.z, box.p1.z, E_BLOCK_AIR, 0); + a_ChunkDesc.FillRelCuboid(box.p1.x + 1, box.p2.x - 1, Floor, Mid, box.p2.z, box.p2.z, E_BLOCK_AIR, 0); + Mid += 2; + if (Mid < Ceil) + { + a_ChunkDesc.FillRelCuboid(box.p1.x, box.p1.x, Mid, Ceil, box.p1.z + 1, box.p2.z - 1, E_BLOCK_AIR, 0); + a_ChunkDesc.FillRelCuboid(box.p2.x, box.p2.x, Mid, Ceil, box.p1.z + 1, box.p2.z - 1, E_BLOCK_AIR, 0); + a_ChunkDesc.FillRelCuboid(box.p1.x + 1, box.p2.x - 1, Mid, Ceil, box.p1.z, box.p1.z, E_BLOCK_AIR, 0); + a_ChunkDesc.FillRelCuboid(box.p1.x + 1, box.p2.x - 1, Mid, Ceil, box.p2.z, box.p2.z, E_BLOCK_AIR, 0); + } + + // The floor, if needed: + box.p2.y = box.p1.y; + a_ChunkDesc.FloorRelCuboid(box, E_BLOCK_PLANKS, 0); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cMineShaftStaircase: + +cMineShaftStaircase::cMineShaftStaircase( + cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, + const cCuboid & a_BoundingBox, + eDirection a_Direction, + eSlope a_Slope +) : + super(a_ParentSystem, mskStaircase, a_BoundingBox), + m_Direction(a_Direction), + m_Slope(a_Slope) +{ +} + + + + + +cMineShaft * cMineShaftStaircase::CreateAndFit( + cStructGenMineShafts::cMineShaftSystem & a_ParentSystem, + int a_PivotX, int a_PivotY, int a_PivotZ, eDirection a_Direction, + cNoise & a_Noise +) +{ + int rnd = a_Noise.IntNoise3DInt(a_PivotX, a_PivotY + a_ParentSystem.m_MineShafts.size(), a_PivotZ) / 7; + cCuboid Box; + switch (a_Direction) + { + case dirXM: + { + Box.Assign(a_PivotX - 7, a_PivotY - 1, a_PivotZ - 1, a_PivotX, a_PivotY + 6, a_PivotZ + 1); + break; + } + case dirXP: + { + Box.Assign(a_PivotX, a_PivotY - 1, a_PivotZ - 1, a_PivotX + 7, a_PivotY + 6, a_PivotZ + 1); + break; + } + case dirZM: + { + Box.Assign(a_PivotX - 1, a_PivotY - 1, a_PivotZ - 7, a_PivotX + 1, a_PivotY + 6, a_PivotZ); + break; + } + case dirZP: + { + Box.Assign(a_PivotX - 1, a_PivotY - 1, a_PivotZ, a_PivotX + 1, a_PivotY + 6, a_PivotZ + 7); + break; + } + } + eSlope Slope = sUp; + if ((rnd % 4) < 2) // 50 % + { + Slope = sDown; + Box.Move(0, -4, 0); + } + if (!a_ParentSystem.CanAppend(Box)) + { + return NULL; + } + return new cMineShaftStaircase(a_ParentSystem, Box, a_Direction, Slope); +} + + + + + +void cMineShaftStaircase::AppendBranches(int a_RecursionLevel, cNoise & a_Noise) +{ + int Height = m_BoundingBox.p1.y + ((m_Slope == sDown) ? 1 : 5); + switch (m_Direction) + { + case dirXM: m_ParentSystem.AppendBranch(m_BoundingBox.p1.x - 1, Height, m_BoundingBox.p1.z + 1, dirXM, a_Noise, a_RecursionLevel); break; + case dirXP: m_ParentSystem.AppendBranch(m_BoundingBox.p2.x + 1, Height, m_BoundingBox.p1.z + 1, dirXP, a_Noise, a_RecursionLevel); break; + case dirZM: m_ParentSystem.AppendBranch(m_BoundingBox.p1.x + 1, Height, m_BoundingBox.p1.z - 1, dirZM, a_Noise, a_RecursionLevel); break; + case dirZP: m_ParentSystem.AppendBranch(m_BoundingBox.p1.x + 1, Height, m_BoundingBox.p2.z + 1, dirZP, a_Noise, a_RecursionLevel); break; + } +} + + + + + +void cMineShaftStaircase::ProcessChunk(cChunkDesc & a_ChunkDesc) +{ + int BlockX = a_ChunkDesc.GetChunkX() * cChunkDef::Width; + int BlockZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width; + cCuboid RelB(m_BoundingBox); + RelB.Move(-BlockX, 0, -BlockZ); + if ( + (RelB.p1.x >= cChunkDef::Width) || + (RelB.p1.z >= cChunkDef::Width) || + (RelB.p2.x < 0) || + (RelB.p2.z < 0) + ) + { + // No intersection between this staircase and this chunk + return; + } + + int SFloor = RelB.p1.y + ((m_Slope == sDown) ? 5 : 1); + int DFloor = RelB.p1.y + ((m_Slope == sDown) ? 1 : 5); + int Add = (m_Slope == sDown) ? -1 : 1; + int InitAdd = (m_Slope == sDown) ? -1 : 0; + cCuboid Box; + switch (m_Direction) + { + case dirXM: + { + a_ChunkDesc.FillRelCuboid (RelB.p2.x - 1, RelB.p2.x, SFloor, SFloor + 2, RelB.p1.z, RelB.p2.z, E_BLOCK_AIR, 0); + a_ChunkDesc.FillRelCuboid (RelB.p1.x, RelB.p1.x + 1, DFloor, DFloor + 2, RelB.p1.z, RelB.p2.z, E_BLOCK_AIR, 0); + a_ChunkDesc.FloorRelCuboid(RelB.p2.x - 1, RelB.p2.x, SFloor - 1, SFloor - 1, RelB.p1.z, RelB.p2.z, E_BLOCK_PLANKS, 0); + a_ChunkDesc.FloorRelCuboid(RelB.p1.x, RelB.p1.x + 1, DFloor - 1, DFloor - 1, RelB.p1.z, RelB.p2.z, E_BLOCK_PLANKS, 0); + Box.Assign(RelB.p2.x - 2, SFloor + InitAdd, RelB.p1.z, RelB.p2.x - 2, SFloor + 3 + InitAdd, RelB.p2.z); + for (int i = 0; i < 4; i++) + { + a_ChunkDesc.FillRelCuboid(Box, E_BLOCK_AIR, 0); + a_ChunkDesc.FloorRelCuboid(Box.p1.x, Box.p2.x, Box.p1.y - 1, Box.p1.y - 1, Box.p1.z, Box.p2.z, E_BLOCK_PLANKS, 0); + Box.Move(-1, Add, 0); + } + break; + } + + case dirXP: + { + a_ChunkDesc.FillRelCuboid (RelB.p1.x, RelB.p1.x + 1, SFloor, SFloor + 2, RelB.p1.z, RelB.p2.z, E_BLOCK_AIR, 0); + a_ChunkDesc.FillRelCuboid (RelB.p2.x - 1, RelB.p2.x, DFloor, DFloor + 2, RelB.p1.z, RelB.p2.z, E_BLOCK_AIR, 0); + a_ChunkDesc.FloorRelCuboid(RelB.p1.x, RelB.p1.x + 1, SFloor - 1, SFloor - 1, RelB.p1.z, RelB.p2.z, E_BLOCK_PLANKS, 0); + a_ChunkDesc.FloorRelCuboid(RelB.p2.x - 1, RelB.p2.x, DFloor - 1, DFloor - 1, RelB.p1.z, RelB.p2.z, E_BLOCK_PLANKS, 0); + Box.Assign(RelB.p1.x + 2, SFloor + InitAdd, RelB.p1.z, RelB.p1.x + 2, SFloor + 3 + InitAdd, RelB.p2.z); + for (int i = 0; i < 4; i++) + { + a_ChunkDesc.FillRelCuboid(Box, E_BLOCK_AIR, 0); + a_ChunkDesc.FloorRelCuboid(Box.p1.x, Box.p2.x, Box.p1.y - 1, Box.p1.y - 1, Box.p1.z, Box.p2.z, E_BLOCK_PLANKS, 0); + Box.Move(1, Add, 0); + } + break; + } + + case dirZM: + { + a_ChunkDesc.FillRelCuboid (RelB.p1.x, RelB.p2.x, SFloor, SFloor + 2, RelB.p2.z - 1, RelB.p2.z, E_BLOCK_AIR, 0); + a_ChunkDesc.FillRelCuboid (RelB.p1.x, RelB.p2.x, DFloor, DFloor + 2, RelB.p1.z, RelB.p1.z + 1, E_BLOCK_AIR, 0); + a_ChunkDesc.FloorRelCuboid(RelB.p1.x, RelB.p2.x, SFloor - 1, SFloor - 1, RelB.p2.z - 1, RelB.p2.z, E_BLOCK_PLANKS, 0); + a_ChunkDesc.FloorRelCuboid(RelB.p1.x, RelB.p2.x, DFloor - 1, DFloor - 1, RelB.p1.z, RelB.p1.z + 1, E_BLOCK_PLANKS, 0); + Box.Assign(RelB.p1.x, SFloor + InitAdd, RelB.p2.z - 2, RelB.p2.x, SFloor + 3 + InitAdd, RelB.p2.z - 2); + for (int i = 0; i < 4; i++) + { + a_ChunkDesc.FillRelCuboid(Box, E_BLOCK_AIR, 0); + a_ChunkDesc.FloorRelCuboid(Box.p1.x, Box.p2.x, Box.p1.y - 1, Box.p1.y - 1, Box.p1.z, Box.p2.z, E_BLOCK_PLANKS, 0); + Box.Move(0, Add, -1); + } + break; + } + + case dirZP: + { + a_ChunkDesc.FillRelCuboid (RelB.p1.x, RelB.p2.x, SFloor, SFloor + 2, RelB.p1.z, RelB.p1.z + 1, E_BLOCK_AIR, 0); + a_ChunkDesc.FillRelCuboid (RelB.p1.x, RelB.p2.x, DFloor, DFloor + 2, RelB.p2.z - 1, RelB.p2.z, E_BLOCK_AIR, 0); + a_ChunkDesc.FloorRelCuboid(RelB.p1.x, RelB.p2.x, SFloor - 1, SFloor - 1, RelB.p1.z, RelB.p1.z + 1, E_BLOCK_PLANKS, 0); + a_ChunkDesc.FloorRelCuboid(RelB.p1.x, RelB.p2.x, DFloor - 1, DFloor - 1, RelB.p2.z - 1, RelB.p2.z, E_BLOCK_PLANKS, 0); + Box.Assign(RelB.p1.x, SFloor + InitAdd, RelB.p1.z + 2, RelB.p2.x, SFloor + 3 + InitAdd, RelB.p1.z + 2); + for (int i = 0; i < 4; i++) + { + a_ChunkDesc.FillRelCuboid(Box, E_BLOCK_AIR, 0); + a_ChunkDesc.FloorRelCuboid(Box.p1.x, Box.p2.x, Box.p1.y - 1, Box.p1.y - 1, Box.p1.z, Box.p2.z, E_BLOCK_PLANKS, 0); + Box.Move(0, Add, 1); + } + break; + } + + } // switch (m_Direction) +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenMineShafts: + +cStructGenMineShafts::cStructGenMineShafts( + int a_Seed, int a_GridSize, int a_MaxSystemSize, + int a_ChanceCorridor, int a_ChanceCrossing, int a_ChanceStaircase +) : + m_Noise(a_Seed), + m_GridSize(a_GridSize), + m_MaxSystemSize(a_MaxSystemSize), + m_ProbLevelCorridor(std::max(0, a_ChanceCorridor)), + m_ProbLevelCrossing(std::max(0, a_ChanceCorridor + a_ChanceCrossing)), + m_ProbLevelStaircase(std::max(0, a_ChanceCorridor + a_ChanceCrossing + a_ChanceStaircase)) +{ +} + + + + + +cStructGenMineShafts::~cStructGenMineShafts() +{ + ClearCache(); +} + + + + + +void cStructGenMineShafts::ClearCache(void) +{ + for (cMineShaftSystems::const_iterator itr = m_Cache.begin(), end = m_Cache.end(); itr != end; ++itr) + { + delete *itr; + } // for itr - m_Cache[] + m_Cache.clear(); +} + + + + + +void cStructGenMineShafts::GetMineShaftSystemsForChunk( + int a_ChunkX, int a_ChunkZ, + cStructGenMineShafts::cMineShaftSystems & a_MineShafts +) +{ + int BaseX = a_ChunkX * cChunkDef::Width / m_GridSize; + int BaseZ = a_ChunkZ * cChunkDef::Width / m_GridSize; + if (BaseX < 0) + { + --BaseX; + } + if (BaseZ < 0) + { + --BaseZ; + } + BaseX -= NEIGHBORHOOD_SIZE / 2; + BaseZ -= NEIGHBORHOOD_SIZE / 2; + + // Walk the cache, move each cave system that we want into a_Caves: + int StartX = BaseX * m_GridSize; + int EndX = (BaseX + NEIGHBORHOOD_SIZE + 1) * m_GridSize; + int StartZ = BaseZ * m_GridSize; + int EndZ = (BaseZ + NEIGHBORHOOD_SIZE + 1) * m_GridSize; + for (cMineShaftSystems::iterator itr = m_Cache.begin(), end = m_Cache.end(); itr != end;) + { + if ( + ((*itr)->m_BlockX >= StartX) && ((*itr)->m_BlockX < EndX) && + ((*itr)->m_BlockZ >= StartZ) && ((*itr)->m_BlockZ < EndZ) + ) + { + // want + a_MineShafts.push_back(*itr); + itr = m_Cache.erase(itr); + } + else + { + // don't want + ++itr; + } + } // for itr - m_Cache[] + + for (int x = 0; x < NEIGHBORHOOD_SIZE; x++) + { + int RealX = (BaseX + x) * m_GridSize; + for (int z = 0; z < NEIGHBORHOOD_SIZE; z++) + { + int RealZ = (BaseZ + z) * m_GridSize; + bool Found = false; + for (cMineShaftSystems::const_iterator itr = a_MineShafts.begin(), end = a_MineShafts.end(); itr != end; ++itr) + { + if (((*itr)->m_BlockX == RealX) && ((*itr)->m_BlockZ == RealZ)) + { + Found = true; + break; + } + } // for itr - a_Mineshafts + if (!Found) + { + a_MineShafts.push_back(new cMineShaftSystem(RealX, RealZ, m_GridSize, m_MaxSystemSize, m_Noise, m_ProbLevelCorridor, m_ProbLevelCrossing, m_ProbLevelStaircase)); + } + } // for z + } // for x + + // Copy a_MineShafts into m_Cache to the beginning: + cMineShaftSystems MineShaftsCopy(a_MineShafts); + m_Cache.splice(m_Cache.begin(), MineShaftsCopy, MineShaftsCopy.begin(), MineShaftsCopy.end()); + + // Trim the cache if it's too long: + if (m_Cache.size() > 100) + { + cMineShaftSystems::iterator itr = m_Cache.begin(); + std::advance(itr, 100); + for (cMineShaftSystems::iterator end = m_Cache.end(); itr != end; ++itr) + { + delete *itr; + } + itr = m_Cache.begin(); + std::advance(itr, 100); + m_Cache.erase(itr, m_Cache.end()); + } +} + + + + + + +void cStructGenMineShafts::GenStructures(cChunkDesc & a_ChunkDesc) +{ + int ChunkX = a_ChunkDesc.GetChunkX(); + int ChunkZ = a_ChunkDesc.GetChunkZ(); + cMineShaftSystems MineShafts; + GetMineShaftSystemsForChunk(ChunkX, ChunkZ, MineShafts); + for (cMineShaftSystems::const_iterator itr = MineShafts.begin(); itr != MineShafts.end(); ++itr) + { + (*itr)->ProcessChunk(a_ChunkDesc); + } // for itr - MineShafts[] +} + + + + diff --git a/src/Generating/MineShafts.h b/src/Generating/MineShafts.h new file mode 100644 index 000000000..c53d3bc53 --- /dev/null +++ b/src/Generating/MineShafts.h @@ -0,0 +1,61 @@ + +// MineShafts.h + +// Declares the cStructGenMineShafts class representing the structure generator for abandoned mineshafts + + + + + +#pragma once + +#include "ComposableGenerator.h" +#include "../Noise.h" + + + + + +class cStructGenMineShafts : + public cStructureGen +{ +public: + cStructGenMineShafts( + int a_Seed, int a_GridSize, int a_MaxSystemSize, + int a_ChanceCorridor, int a_ChanceCrossing, int a_ChanceStaircase + ); + + virtual ~cStructGenMineShafts(); + +protected: + friend class cMineShaft; + friend class cMineShaftDirtRoom; + friend class cMineShaftCorridor; + friend class cMineShaftCrossing; + friend class cMineShaftStaircase; + class cMineShaftSystem; // fwd: MineShafts.cpp + typedef std::list<cMineShaftSystem *> cMineShaftSystems; + + cNoise m_Noise; + int m_GridSize; ///< Average spacing of the systems + int m_MaxSystemSize; ///< Maximum blcok size of a mineshaft system + int m_ProbLevelCorridor; ///< Probability level of a branch object being the corridor + int m_ProbLevelCrossing; ///< Probability level of a branch object being the crossing, minus Corridor + int m_ProbLevelStaircase; ///< Probability level of a branch object being the staircase, minus Crossing + cMineShaftSystems m_Cache; ///< Cache of the most recently used systems. MoveToFront used. + + /// Clears everything from the cache + void ClearCache(void); + + /** Returns all systems that *may* intersect the given chunk. + All the systems are valid until the next call to this function (which may delete some of the pointers). + */ + void GetMineShaftSystemsForChunk(int a_ChunkX, int a_ChunkZ, cMineShaftSystems & a_MineShaftSystems); + + // cStructureGen overrides: + virtual void GenStructures(cChunkDesc & a_ChunkDesc) override; +} ; + + + + diff --git a/src/Generating/Noise3DGenerator.cpp b/src/Generating/Noise3DGenerator.cpp new file mode 100644 index 000000000..0c68957c0 --- /dev/null +++ b/src/Generating/Noise3DGenerator.cpp @@ -0,0 +1,576 @@ + +// Nosie3DGenerator.cpp + +// Generates terrain using 3D noise, rather than composing. Is a test. + +#include "Globals.h" +#include "Noise3DGenerator.h" +#include "../OSSupport/File.h" +#include "inifile/iniFile.h" +#include "../LinearInterpolation.h" +#include "../LinearUpscale.h" + + + + + +/* +// Perform an automatic test of upscaling upon program start (use breakpoints to debug): + +class Test +{ +public: + Test(void) + { + DoTest1(); + DoTest2(); + } + + + void DoTest1(void) + { + float In[3 * 3 * 3]; + for (int i = 0; i < ARRAYCOUNT(In); i++) + { + In[i] = (float)(i % 5); + } + Debug3DNoise(In, 3, 3, 3, "Upscale3D in"); + float Out[17 * 33 * 35]; + LinearUpscale3DArray(In, 3, 3, 3, Out, 8, 16, 17); + Debug3DNoise(Out, 17, 33, 35, "Upscale3D test"); + } + + + void DoTest2(void) + { + float In[3 * 3]; + for (int i = 0; i < ARRAYCOUNT(In); i++) + { + In[i] = (float)(i % 5); + } + Debug2DNoise(In, 3, 3, "Upscale2D in"); + float Out[17 * 33]; + LinearUpscale2DArray(In, 3, 3, Out, 8, 16); + Debug2DNoise(Out, 17, 33, "Upscale2D test"); + } + +} gTest; +//*/ + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cNoise3DGenerator: + +cNoise3DGenerator::cNoise3DGenerator(cChunkGenerator & a_ChunkGenerator) : + super(a_ChunkGenerator), + m_Perlin(1000), + m_Cubic(1000) +{ + m_Perlin.AddOctave(1, (NOISE_DATATYPE)0.5); + m_Perlin.AddOctave((NOISE_DATATYPE)0.5, 1); + m_Perlin.AddOctave((NOISE_DATATYPE)0.5, 2); + + #if 0 + // DEBUG: Test the noise generation: + // NOTE: In order to be able to run MCS with this code, you need to increase the default thread stack size + // In MSVC, it is done in Project Settings -> Configuration Properties -> Linker -> System, set Stack reserve size to at least 64M + m_SeaLevel = 62; + m_HeightAmplification = 0; + m_MidPoint = 75; + m_FrequencyX = 4; + m_FrequencyY = 4; + m_FrequencyZ = 4; + m_AirThreshold = 0.5; + + const int NumChunks = 4; + NOISE_DATATYPE Noise[NumChunks][cChunkDef::Width * cChunkDef::Width * cChunkDef::Height]; + for (int x = 0; x < NumChunks; x++) + { + GenerateNoiseArray(x, 5, Noise[x]); + } + + // Save in XY cuts: + cFile f1; + if (f1.Open("Test_XY.grab", cFile::fmWrite)) + { + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int y = 0; y < cChunkDef::Height; y++) + { + for (int i = 0; i < NumChunks; i++) + { + int idx = y * cChunkDef::Width + z * cChunkDef::Width * cChunkDef::Height; + unsigned char buf[cChunkDef::Width]; + for (int x = 0; x < cChunkDef::Width; x++) + { + buf[x] = (unsigned char)(std::min(256, std::max(0, (int)(128 + 32 * Noise[i][idx++])))); + } + f1.Write(buf, cChunkDef::Width); + } + } // for y + } // for z + } // if (XY file open) + + cFile f2; + if (f2.Open("Test_XZ.grab", cFile::fmWrite)) + { + for (int y = 0; y < cChunkDef::Height; y++) + { + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int i = 0; i < NumChunks; i++) + { + int idx = y * cChunkDef::Width + z * cChunkDef::Width * cChunkDef::Height; + unsigned char buf[cChunkDef::Width]; + for (int x = 0; x < cChunkDef::Width; x++) + { + buf[x] = (unsigned char)(std::min(256, std::max(0, (int)(128 + 32 * Noise[i][idx++])))); + } + f2.Write(buf, cChunkDef::Width); + } + } // for z + } // for y + } // if (XZ file open) + #endif // 0 +} + + + + + +cNoise3DGenerator::~cNoise3DGenerator() +{ + // Nothing needed yet +} + + + + + +void cNoise3DGenerator::Initialize(cWorld * a_World, cIniFile & a_IniFile) +{ + m_World = a_World; + + // Params: + m_SeaLevel = a_IniFile.GetValueSetI("Generator", "Noise3DSeaLevel", 62); + m_HeightAmplification = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "Noise3DHeightAmplification", 0); + m_MidPoint = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "Noise3DMidPoint", 75); + m_FrequencyX = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "Noise3DFrequencyX", 8); + m_FrequencyY = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "Noise3DFrequencyY", 8); + m_FrequencyZ = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "Noise3DFrequencyZ", 8); + m_AirThreshold = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "Noise3DAirThreshold", 0.5); +} + + + + + +void cNoise3DGenerator::GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) +{ + for (unsigned int i = 0; i < ARRAYCOUNT(a_BiomeMap); i++) + { + a_BiomeMap[i] = biExtremeHills; + } +} + + + + + +void cNoise3DGenerator::DoGenerate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) +{ + NOISE_DATATYPE Noise[17 * 257 * 17]; + GenerateNoiseArray(a_ChunkX, a_ChunkZ, Noise); + + // Output noise into chunk: + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int y = 0; y < cChunkDef::Height; y++) + { + int idx = z * 17 * 257 + y * 17; + for (int x = 0; x < cChunkDef::Width; x++) + { + NOISE_DATATYPE n = Noise[idx++]; + BLOCKTYPE BlockType; + if (n > m_AirThreshold) + { + BlockType = (y > m_SeaLevel) ? E_BLOCK_AIR : E_BLOCK_STATIONARY_WATER; + } + else + { + BlockType = E_BLOCK_STONE; + } + a_ChunkDesc.SetBlockType(x, y, z, BlockType); + } + } + } + + UpdateHeightmap(a_ChunkDesc); + ComposeTerrain (a_ChunkDesc); +} + + + + + +void cNoise3DGenerator::GenerateNoiseArray(int a_ChunkX, int a_ChunkZ, NOISE_DATATYPE * a_OutNoise) +{ + NOISE_DATATYPE NoiseO[DIM_X * DIM_Y * DIM_Z]; // Output for the Perlin noise + NOISE_DATATYPE NoiseW[DIM_X * DIM_Y * DIM_Z]; // Workspace that the noise calculation can use and trash + + // Our noise array has different layout, XZY, instead of regular chunk's XYZ, that's why the coords are "renamed" + NOISE_DATATYPE StartX = ((NOISE_DATATYPE)(a_ChunkX * cChunkDef::Width)) / m_FrequencyX; + NOISE_DATATYPE EndX = ((NOISE_DATATYPE)((a_ChunkX + 1) * cChunkDef::Width) - 1) / m_FrequencyX; + NOISE_DATATYPE StartZ = ((NOISE_DATATYPE)(a_ChunkZ * cChunkDef::Width)) / m_FrequencyZ; + NOISE_DATATYPE EndZ = ((NOISE_DATATYPE)((a_ChunkZ + 1) * cChunkDef::Width) - 1) / m_FrequencyZ; + NOISE_DATATYPE StartY = 0; + NOISE_DATATYPE EndY = ((NOISE_DATATYPE)256) / m_FrequencyY; + + m_Perlin.Generate3D(NoiseO, DIM_X, DIM_Y, DIM_Z, StartX, EndX, StartY, EndY, StartZ, EndZ, NoiseW); + + // DEBUG: Debug3DNoise(NoiseO, DIM_X, DIM_Y, DIM_Z, Printf("Chunk_%d_%d_orig", a_ChunkX, a_ChunkZ)); + + // Precalculate a "height" array: + NOISE_DATATYPE Height[DIM_X * DIM_Z]; // Output for the cubic noise heightmap ("source") + m_Cubic.Generate2D(Height, DIM_X, DIM_Z, StartX / 25, EndX / 25, StartZ / 25, EndZ / 25); + for (unsigned int i = 0; i < ARRAYCOUNT(Height); i++) + { + Height[i] = abs(Height[i]) * m_HeightAmplification + 1; + } + + // Modify the noise by height data: + for (int y = 0; y < DIM_Y; y++) + { + NOISE_DATATYPE AddHeight = (y * UPSCALE_Y - m_MidPoint) / 20; + AddHeight *= AddHeight * AddHeight; + for (int z = 0; z < DIM_Z; z++) + { + NOISE_DATATYPE * CurRow = &(NoiseO[y * DIM_X + z * DIM_X * DIM_Y]); + for (int x = 0; x < DIM_X; x++) + { + CurRow[x] += AddHeight / Height[x + DIM_X * z]; + } + } + } + + // DEBUG: Debug3DNoise(NoiseO, DIM_X, DIM_Y, DIM_Z, Printf("Chunk_%d_%d_hei", a_ChunkX, a_ChunkZ)); + + // Upscale the Perlin noise into full-blown chunk dimensions: + LinearUpscale3DArray( + NoiseO, DIM_X, DIM_Y, DIM_Z, + a_OutNoise, UPSCALE_X, UPSCALE_Y, UPSCALE_Z + ); + + // DEBUG: Debug3DNoise(a_OutNoise, 17, 257, 17, Printf("Chunk_%d_%d_lerp", a_ChunkX, a_ChunkZ)); +} + + + + + +void cNoise3DGenerator::UpdateHeightmap(cChunkDesc & a_ChunkDesc) +{ + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + for (int y = cChunkDef::Height - 1; y > 0; y--) + { + if (a_ChunkDesc.GetBlockType(x, y, z) != E_BLOCK_AIR) + { + a_ChunkDesc.SetHeight(x, z, y); + break; + } + } // for y + } // for x + } // for z +} + + + + + +void cNoise3DGenerator::ComposeTerrain(cChunkDesc & a_ChunkDesc) +{ + // Make basic terrain composition: + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + int LastAir = a_ChunkDesc.GetHeight(x, z) + 1; + bool HasHadWater = false; + for (int y = LastAir - 1; y > 0; y--) + { + switch (a_ChunkDesc.GetBlockType(x, y, z)) + { + case E_BLOCK_AIR: + { + LastAir = y; + break; + } + case E_BLOCK_STONE: + { + if (LastAir - y > 3) + { + break; + } + if (HasHadWater) + { + a_ChunkDesc.SetBlockType(x, y, z, E_BLOCK_SAND); + } + else + { + a_ChunkDesc.SetBlockType(x, y, z, (LastAir == y + 1) ? E_BLOCK_GRASS : E_BLOCK_DIRT); + } + break; + } + case E_BLOCK_STATIONARY_WATER: + { + LastAir = y; + HasHadWater = true; + break; + } + } // switch (GetBlockType()) + } // for y + a_ChunkDesc.SetBlockType(x, 0, z, E_BLOCK_BEDROCK); + } // for x + } // for z +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cNoise3DComposable: + +cNoise3DComposable::cNoise3DComposable(int a_Seed) : + m_Noise1(a_Seed + 1000), + m_Noise2(a_Seed + 2000), + m_Noise3(a_Seed + 3000) +{ +} + + + + + +void cNoise3DComposable::Initialize(cIniFile & a_IniFile) +{ + // Params: + m_SeaLevel = a_IniFile.GetValueSetI("Generator", "Noise3DSeaLevel", 62); + m_HeightAmplification = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "Noise3DHeightAmplification", 0); + m_MidPoint = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "Noise3DMidPoint", 75); + m_FrequencyX = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "Noise3DFrequencyX", 10); + m_FrequencyY = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "Noise3DFrequencyY", 10); + m_FrequencyZ = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "Noise3DFrequencyZ", 10); + m_AirThreshold = (NOISE_DATATYPE)a_IniFile.GetValueSetF("Generator", "Noise3DAirThreshold", 0.5); +} + + + + + +void cNoise3DComposable::GenerateNoiseArrayIfNeeded(int a_ChunkX, int a_ChunkZ) +{ + if ((a_ChunkX == m_LastChunkX) && (a_ChunkZ == m_LastChunkZ)) + { + // The noise for this chunk is already generated in m_Noise + return; + } + m_LastChunkX = a_ChunkX; + m_LastChunkZ = a_ChunkZ; + + // Upscaling parameters: + const int UPSCALE_X = 8; + const int UPSCALE_Y = 4; + const int UPSCALE_Z = 8; + + // Precalculate a "height" array: + NOISE_DATATYPE Height[17 * 17]; // x + 17 * z + for (int z = 0; z < 17; z += UPSCALE_Z) + { + NOISE_DATATYPE NoiseZ = ((NOISE_DATATYPE)(a_ChunkZ * cChunkDef::Width + z)) / m_FrequencyZ; + for (int x = 0; x < 17; x += UPSCALE_X) + { + NOISE_DATATYPE NoiseX = ((NOISE_DATATYPE)(a_ChunkX * cChunkDef::Width + x)) / m_FrequencyX; + NOISE_DATATYPE val = abs(m_Noise1.CubicNoise2D(NoiseX / 5, NoiseZ / 5)) * m_HeightAmplification + 1; + Height[x + 17 * z] = val * val * val; + } + } + + for (int y = 0; y < 257; y += UPSCALE_Y) + { + NOISE_DATATYPE NoiseY = ((NOISE_DATATYPE)y) / m_FrequencyY; + NOISE_DATATYPE AddHeight = (y - m_MidPoint) / 20; + AddHeight *= AddHeight * AddHeight; + NOISE_DATATYPE * CurFloor = &(m_NoiseArray[y * 17 * 17]); + for (int z = 0; z < 17; z += UPSCALE_Z) + { + NOISE_DATATYPE NoiseZ = ((NOISE_DATATYPE)(a_ChunkZ * cChunkDef::Width + z)) / m_FrequencyZ; + for (int x = 0; x < 17; x += UPSCALE_X) + { + NOISE_DATATYPE NoiseX = ((NOISE_DATATYPE)(a_ChunkX * cChunkDef::Width + x)) / m_FrequencyX; + CurFloor[x + 17 * z] = + m_Noise1.CubicNoise3D(NoiseX, NoiseY, NoiseZ) * (NOISE_DATATYPE)0.5 + + m_Noise2.CubicNoise3D(NoiseX / 2, NoiseY / 2, NoiseZ / 2) + + m_Noise3.CubicNoise3D(NoiseX / 4, NoiseY / 4, NoiseZ / 4) * 2 + + AddHeight / Height[x + 17 * z]; + } + } + // Linear-interpolate this XZ floor: + LinearUpscale2DArrayInPlace(CurFloor, 17, 17, UPSCALE_X, UPSCALE_Z); + } + + // Finish the 3D linear interpolation by interpolating between each XZ-floors on the Y axis + for (int y = 1; y < cChunkDef::Height; y++) + { + if ((y % UPSCALE_Y) == 0) + { + // This is the interpolation source floor, already calculated + continue; + } + int LoFloorY = (y / UPSCALE_Y) * UPSCALE_Y; + int HiFloorY = LoFloorY + UPSCALE_Y; + NOISE_DATATYPE * LoFloor = &(m_NoiseArray[LoFloorY * 17 * 17]); + NOISE_DATATYPE * HiFloor = &(m_NoiseArray[HiFloorY * 17 * 17]); + NOISE_DATATYPE * CurFloor = &(m_NoiseArray[y * 17 * 17]); + NOISE_DATATYPE Ratio = ((NOISE_DATATYPE)(y % UPSCALE_Y)) / UPSCALE_Y; + int idx = 0; + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + CurFloor[idx] = LoFloor[idx] + (HiFloor[idx] - LoFloor[idx]) * Ratio; + idx += 1; + } + idx += 1; // Skipping one X column + } + } + + // The noise array is now fully interpolated + /* + // DEBUG: Output two images of the array, sliced by XY and XZ: + cFile f1; + if (f1.Open(Printf("Chunk_%d_%d_XY.raw", a_ChunkX, a_ChunkZ), cFile::fmWrite)) + { + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int y = 0; y < cChunkDef::Height; y++) + { + int idx = y * 17 * 17 + z * 17; + unsigned char buf[16]; + for (int x = 0; x < cChunkDef::Width; x++) + { + buf[x] = (unsigned char)(std::min(256, std::max(0, (int)(128 + 128 * m_Noise[idx++])))); + } + f1.Write(buf, 16); + } // for y + } // for z + } // if (XY file open) + + cFile f2; + if (f2.Open(Printf("Chunk_%d_%d_XZ.raw", a_ChunkX, a_ChunkZ), cFile::fmWrite)) + { + for (int y = 0; y < cChunkDef::Height; y++) + { + for (int z = 0; z < cChunkDef::Width; z++) + { + int idx = y * 17 * 17 + z * 17; + unsigned char buf[16]; + for (int x = 0; x < cChunkDef::Width; x++) + { + buf[x] = (unsigned char)(std::min(256, std::max(0, (int)(128 + 128 * m_Noise[idx++])))); + } + f2.Write(buf, 16); + } // for z + } // for y + } // if (XZ file open) + */ +} + + + + + +void cNoise3DComposable::GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) +{ + GenerateNoiseArrayIfNeeded(a_ChunkX, a_ChunkZ); + + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + cChunkDef::SetHeight(a_HeightMap, x, z, m_SeaLevel); + for (int y = cChunkDef::Height - 1; y > m_SeaLevel; y--) + { + if (m_NoiseArray[y * 17 * 17 + z * 17 + x] <= m_AirThreshold) + { + cChunkDef::SetHeight(a_HeightMap, x, z, y); + break; + } + } // for y + } // for x + } // for z +} + + + + + +void cNoise3DComposable::ComposeTerrain(cChunkDesc & a_ChunkDesc) +{ + GenerateNoiseArrayIfNeeded(a_ChunkDesc.GetChunkX(), a_ChunkDesc.GetChunkZ()); + + a_ChunkDesc.FillBlocks(E_BLOCK_AIR, 0); + + // Make basic terrain composition: + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int x = 0; x < cChunkDef::Width; x++) + { + int LastAir = a_ChunkDesc.GetHeight(x, z) + 1; + bool HasHadWater = false; + for (int y = LastAir; y < m_SeaLevel; y++) + { + a_ChunkDesc.SetBlockType(x, y, z, E_BLOCK_STATIONARY_WATER); + } + for (int y = LastAir - 1; y > 0; y--) + { + if (m_NoiseArray[x + 17 * z + 17 * 17 * y] > m_AirThreshold) + { + // "air" part + LastAir = y; + if (y < m_SeaLevel) + { + a_ChunkDesc.SetBlockType(x, y, z, E_BLOCK_STATIONARY_WATER); + HasHadWater = true; + } + continue; + } + // "ground" part: + if (LastAir - y > 4) + { + a_ChunkDesc.SetBlockType(x, y, z, E_BLOCK_STONE); + continue; + } + if (HasHadWater) + { + a_ChunkDesc.SetBlockType(x, y, z, E_BLOCK_SAND); + } + else + { + a_ChunkDesc.SetBlockType(x, y, z, (LastAir == y + 1) ? E_BLOCK_GRASS : E_BLOCK_DIRT); + } + } // for y + a_ChunkDesc.SetBlockType(x, 0, z, E_BLOCK_BEDROCK); + } // for x + } // for z +} + + + + diff --git a/src/Generating/Noise3DGenerator.h b/src/Generating/Noise3DGenerator.h new file mode 100644 index 000000000..0d211cddc --- /dev/null +++ b/src/Generating/Noise3DGenerator.h @@ -0,0 +1,106 @@ + +// Noise3DGenerator.h + +// Generates terrain using 3D noise, rather than composing. Is a test. + + + + +#pragma once + +#include "ComposableGenerator.h" +#include "../Noise.h" + + + + + +class cNoise3DGenerator : + public cChunkGenerator::cGenerator +{ + typedef cChunkGenerator::cGenerator super; + +public: + cNoise3DGenerator(cChunkGenerator & a_ChunkGenerator); + virtual ~cNoise3DGenerator(); + + virtual void Initialize(cWorld * a_World, cIniFile & a_IniFile) override; + virtual void GenerateBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override; + virtual void DoGenerate(int a_ChunkX, int a_ChunkZ, cChunkDesc & a_ChunkDesc) override; + +protected: + // Linear interpolation step sizes, must be divisors of cChunkDef::Width and cChunkDef::Height, respectively: + static const int UPSCALE_X = 8; + static const int UPSCALE_Y = 4; + static const int UPSCALE_Z = 8; + + // Linear interpolation buffer dimensions, calculated from the step sizes: + static const int DIM_X = 1 + cChunkDef::Width / UPSCALE_X; + static const int DIM_Y = 1 + cChunkDef::Height / UPSCALE_Y; + static const int DIM_Z = 1 + cChunkDef::Width / UPSCALE_Z; + + cPerlinNoise m_Perlin; // The base 3D noise source for the actual composition + cCubicNoise m_Cubic; // The noise used for heightmap directing + + int m_SeaLevel; + NOISE_DATATYPE m_HeightAmplification; + NOISE_DATATYPE m_MidPoint; // Where the vertical "center" of the noise should be + NOISE_DATATYPE m_FrequencyX; + NOISE_DATATYPE m_FrequencyY; + NOISE_DATATYPE m_FrequencyZ; + NOISE_DATATYPE m_AirThreshold; + + /// Generates the 3D noise array used for terrain generation; a_Noise is of ChunkData-size + void GenerateNoiseArray(int a_ChunkX, int a_ChunkZ, NOISE_DATATYPE * a_Noise); + + /// Updates heightmap based on the chunk's contents + void UpdateHeightmap(cChunkDesc & a_ChunkDesc); + + /// Composes terrain - adds dirt, grass and sand + void ComposeTerrain(cChunkDesc & a_ChunkDesc); +} ; + + + + + +class cNoise3DComposable : + public cTerrainHeightGen, + public cTerrainCompositionGen +{ +public: + cNoise3DComposable(int a_Seed); + + void Initialize(cIniFile & a_IniFile); + +protected: + cNoise m_Noise1; + cNoise m_Noise2; + cNoise m_Noise3; + + int m_SeaLevel; + NOISE_DATATYPE m_HeightAmplification; + NOISE_DATATYPE m_MidPoint; // Where the vertical "center" of the noise should be + NOISE_DATATYPE m_FrequencyX; + NOISE_DATATYPE m_FrequencyY; + NOISE_DATATYPE m_FrequencyZ; + NOISE_DATATYPE m_AirThreshold; + + int m_LastChunkX; + int m_LastChunkZ; + NOISE_DATATYPE m_NoiseArray[17 * 17 * 257]; // x + 17 * z + 17 * 17 * y + + + /// Generates the 3D noise array used for terrain generation, unless the LastChunk coords are equal to coords given + void GenerateNoiseArrayIfNeeded(int a_ChunkX, int a_ChunkZ); + + // cTerrainHeightGen overrides: + virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override; + + // cTerrainCompositionGen overrides: + virtual void ComposeTerrain(cChunkDesc & a_ChunkDesc) override; +} ; + + + + diff --git a/src/Generating/Ravines.cpp b/src/Generating/Ravines.cpp new file mode 100644 index 000000000..6413b963b --- /dev/null +++ b/src/Generating/Ravines.cpp @@ -0,0 +1,531 @@ + +// Ravines.cpp + +// Implements the cStructGenRavines class representing the ravine structure generator + +#include "Globals.h" +#include "Ravines.h" + + + + +/// How many ravines in each direction are generated for a given chunk. Must be an even number +static const int NEIGHBORHOOD_SIZE = 8; + +static const int NUM_RAVINE_POINTS = 4; + + + + + +struct cRavDefPoint +{ + int m_BlockX; + int m_BlockZ; + int m_Radius; + int m_Top; + int m_Bottom; + + cRavDefPoint(int a_BlockX, int a_BlockZ, int a_Radius, int a_Top, int a_Bottom) : + m_BlockX(a_BlockX), + m_BlockZ(a_BlockZ), + m_Radius(a_Radius), + m_Top (a_Top), + m_Bottom(a_Bottom) + { + } +} ; + +typedef std::vector<cRavDefPoint> cRavDefPoints; + + + + + +class cStructGenRavines::cRavine +{ + cRavDefPoints m_Points; + + /// Generates the shaping defpoints for the ravine, based on the ravine block coords and noise + void GenerateBaseDefPoints(int a_BlockX, int a_BlockZ, int a_Size, cNoise & a_Noise); + + /// Refines (adds and smooths) defpoints from a_Src into a_Dst + void RefineDefPoints(const cRavDefPoints & a_Src, cRavDefPoints & a_Dst); + + /// Does one round of smoothing, two passes of RefineDefPoints() + void Smooth(void); + + /// Linearly interpolates the points so that the maximum distance between two neighbors is max 1 block + void FinishLinear(void); + +public: + // Coords for which the ravine was generated (not necessarily the center) + int m_BlockX; + int m_BlockZ; + + cRavine(int a_BlockX, int a_BlockZ, int a_Size, cNoise & a_Noise); + + /// Carves the ravine into the chunk specified + void ProcessChunk( + int a_ChunkX, int a_ChunkZ, + cChunkDef::BlockTypes & a_BlockTypes, + cChunkDef::HeightMap & a_HeightMap + ); + + #ifdef _DEBUG + /// Exports itself as a SVG line definition + AString ExportAsSVG(int a_Color, int a_OffsetX = 0, int a_OffsetZ = 0) const; + #endif // _DEBUG +} ; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenRavines: + +cStructGenRavines::cStructGenRavines(int a_Seed, int a_Size) : + m_Noise(a_Seed), + m_Size(a_Size) +{ +} + + + + + +cStructGenRavines::~cStructGenRavines() +{ + ClearCache(); +} + + + + + +void cStructGenRavines::ClearCache(void) +{ + for (cRavines::const_iterator itr = m_Cache.begin(), end = m_Cache.end(); itr != end; ++itr) + { + delete *itr; + } // for itr - m_Cache[] + m_Cache.clear(); +} + + + + + +void cStructGenRavines::GenStructures(cChunkDesc & a_ChunkDesc) +{ + int ChunkX = a_ChunkDesc.GetChunkX(); + int ChunkZ = a_ChunkDesc.GetChunkZ(); + cRavines Ravines; + GetRavinesForChunk(ChunkX, ChunkZ, Ravines); + for (cRavines::const_iterator itr = Ravines.begin(), end = Ravines.end(); itr != end; ++itr) + { + (*itr)->ProcessChunk(ChunkX, ChunkZ, a_ChunkDesc.GetBlockTypes(), a_ChunkDesc.GetHeightMap()); + } // for itr - Ravines[] +} + + + + + +void cStructGenRavines::GetRavinesForChunk(int a_ChunkX, int a_ChunkZ, cStructGenRavines::cRavines & a_Ravines) +{ + int BaseX = a_ChunkX * cChunkDef::Width / m_Size; + int BaseZ = a_ChunkZ * cChunkDef::Width / m_Size; + if (BaseX < 0) + { + --BaseX; + } + if (BaseZ < 0) + { + --BaseZ; + } + BaseX -= 4; + BaseZ -= 4; + + // Walk the cache, move each ravine that we want into a_Ravines: + int StartX = BaseX * m_Size; + int EndX = (BaseX + NEIGHBORHOOD_SIZE + 1) * m_Size; + int StartZ = BaseZ * m_Size; + int EndZ = (BaseZ + NEIGHBORHOOD_SIZE + 1) * m_Size; + for (cRavines::iterator itr = m_Cache.begin(), end = m_Cache.end(); itr != end;) + { + if ( + ((*itr)->m_BlockX >= StartX) && ((*itr)->m_BlockX < EndX) && + ((*itr)->m_BlockZ >= StartZ) && ((*itr)->m_BlockZ < EndZ) + ) + { + // want + a_Ravines.push_back(*itr); + itr = m_Cache.erase(itr); + } + else + { + // don't want + ++itr; + } + } // for itr - m_Cache[] + + for (int x = 0; x < NEIGHBORHOOD_SIZE; x++) + { + int RealX = (BaseX + x) * m_Size; + for (int z = 0; z < NEIGHBORHOOD_SIZE; z++) + { + int RealZ = (BaseZ + z) * m_Size; + bool Found = false; + for (cRavines::const_iterator itr = a_Ravines.begin(), end = a_Ravines.end(); itr != end; ++itr) + { + if (((*itr)->m_BlockX == RealX) && ((*itr)->m_BlockZ == RealZ)) + { + Found = true; + break; + } + } + if (!Found) + { + a_Ravines.push_back(new cRavine(RealX, RealZ, m_Size, m_Noise)); + } + } + } + + // Copy a_Ravines into m_Cache to the beginning: + cRavines RavinesCopy(a_Ravines); + m_Cache.splice(m_Cache.begin(), RavinesCopy, RavinesCopy.begin(), RavinesCopy.end()); + + // Trim the cache if it's too long: + if (m_Cache.size() > 100) + { + cRavines::iterator itr = m_Cache.begin(); + std::advance(itr, 100); + for (cRavines::iterator end = m_Cache.end(); itr != end; ++itr) + { + delete *itr; + } + itr = m_Cache.begin(); + std::advance(itr, 100); + m_Cache.erase(itr, m_Cache.end()); + } + + /* + #ifdef _DEBUG + // DEBUG: Export as SVG into a file specific for the chunk, for visual verification: + AString SVG; + SVG.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1024\" height = \"1024\">\n"); + for (cRavines::const_iterator itr = a_Ravines.begin(), end = a_Ravines.end(); itr != end; ++itr) + { + SVG.append((*itr)->ExportAsSVG(0, 512, 512)); + } + SVG.append("</svg>\n"); + + AString fnam; + Printf(fnam, "ravines\\%03d_%03d.svg", a_ChunkX, a_ChunkZ); + cFile File(fnam, cFile::fmWrite); + File.Write(SVG.c_str(), SVG.size()); + #endif // _DEBUG + //*/ +} + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenRavines::cRavine + +cStructGenRavines::cRavine::cRavine(int a_BlockX, int a_BlockZ, int a_Size, cNoise & a_Noise) : + m_BlockX(a_BlockX), + m_BlockZ(a_BlockZ) +{ + // Calculate the ravine shape-defining points: + GenerateBaseDefPoints(a_BlockX, a_BlockZ, a_Size, a_Noise); + + // Smooth the ravine. A two passes are needed: + Smooth(); + Smooth(); + + // Linearly interpolate the neighbors so that they're close enough together: + FinishLinear(); +} + + + + + +void cStructGenRavines::cRavine::GenerateBaseDefPoints(int a_BlockX, int a_BlockZ, int a_Size, cNoise & a_Noise) +{ + // Modify the size slightly to have different-sized ravines (1/2 to 1/1 of a_Size): + a_Size = (512 + ((a_Noise.IntNoise3DInt(19 * a_BlockX, 11 * a_BlockZ, a_BlockX + a_BlockZ) / 17) % 512)) * a_Size / 1024; + + // The complete offset of the ravine from its cellpoint, up to 2 * a_Size in each direction + int OffsetX = (((a_Noise.IntNoise3DInt(50 * a_BlockX, 30 * a_BlockZ, 0) / 9) % (2 * a_Size)) + ((a_Noise.IntNoise3DInt(30 * a_BlockX, 50 * m_BlockZ, 1000) / 7) % (2 * a_Size)) - 2 * a_Size) / 2; + int OffsetZ = (((a_Noise.IntNoise3DInt(50 * a_BlockX, 30 * a_BlockZ, 2000) / 7) % (2 * a_Size)) + ((a_Noise.IntNoise3DInt(30 * a_BlockX, 50 * m_BlockZ, 3000) / 9) % (2 * a_Size)) - 2 * a_Size) / 2; + int CenterX = a_BlockX + OffsetX; + int CenterZ = a_BlockZ + OffsetZ; + + // Get the base angle in which the ravine "axis" goes: + float Angle = (float)(((float)((a_Noise.IntNoise3DInt(20 * a_BlockX, 70 * a_BlockZ, 6000) / 9) % 16384)) / 16384.0 * 3.141592653); + float xc = sin(Angle); + float zc = cos(Angle); + + // Calculate the definition points and radii: + int MaxRadius = (int)(sqrt(12.0 + ((a_Noise.IntNoise2DInt(61 * a_BlockX, 97 * a_BlockZ) / 13) % a_Size) / 16)); + int Top = 32 + ((a_Noise.IntNoise2DInt(13 * a_BlockX, 17 * a_BlockZ) / 23) % 32); + int Bottom = 5 + ((a_Noise.IntNoise2DInt(17 * a_BlockX, 29 * a_BlockZ) / 13) % 32); + int Mid = (Top + Bottom) / 2; + int PointX = CenterX - (int)(xc * a_Size / 2); + int PointZ = CenterZ - (int)(zc * a_Size / 2); + m_Points.push_back(cRavDefPoint(PointX, PointZ, 0, (Mid + Top) / 2, (Mid + Bottom) / 2)); + for (int i = 1; i < NUM_RAVINE_POINTS - 1; i++) + { + int LineX = CenterX + (int)(xc * a_Size * (i - NUM_RAVINE_POINTS / 2) / NUM_RAVINE_POINTS); + int LineZ = CenterZ + (int)(zc * a_Size * (i - NUM_RAVINE_POINTS / 2) / NUM_RAVINE_POINTS); + // Amplitude is the amount of blocks that this point is away from the ravine "axis" + int Amplitude = (a_Noise.IntNoise3DInt(70 * a_BlockX, 20 * a_BlockZ + 31 * i, 10000 * i) / 9) % a_Size; + Amplitude = Amplitude / 4 - a_Size / 8; // Amplitude is in interval [-a_Size / 4, a_Size / 4] + int PointX = LineX + (int)(zc * Amplitude); + int PointZ = LineZ - (int)(xc * Amplitude); + int Radius = MaxRadius - abs(i - NUM_RAVINE_POINTS / 2); // TODO: better radius function + int ThisTop = Top + ((a_Noise.IntNoise3DInt(7 * a_BlockX, 19 * a_BlockZ, i * 31) / 13) % 8) - 4; + int ThisBottom = Bottom + ((a_Noise.IntNoise3DInt(19 * a_BlockX, 7 * a_BlockZ, i * 31) / 13) % 8) - 4; + m_Points.push_back(cRavDefPoint(PointX, PointZ, Radius, ThisTop, ThisBottom)); + } // for i - m_Points[] + PointX = CenterX + (int)(xc * a_Size / 2); + PointZ = CenterZ + (int)(zc * a_Size / 2); + m_Points.push_back(cRavDefPoint(PointX, PointZ, 0, Mid, Mid)); +} + + + + + +void cStructGenRavines::cRavine::RefineDefPoints(const cRavDefPoints & a_Src, cRavDefPoints & a_Dst) +{ + // Smoothing: for each line segment, add points on its 1/4 lengths + int Num = a_Src.size() - 2; // this many intermediary points + a_Dst.clear(); + a_Dst.reserve(Num * 2 + 2); + cRavDefPoints::const_iterator itr = a_Src.begin() + 1; + a_Dst.push_back(a_Src.front()); + int PrevX = a_Src.front().m_BlockX; + int PrevZ = a_Src.front().m_BlockZ; + int PrevR = a_Src.front().m_Radius; + int PrevT = a_Src.front().m_Top; + int PrevB = a_Src.front().m_Bottom; + for (int i = 0; i <= Num; ++i, ++itr) + { + int dx = itr->m_BlockX - PrevX; + int dz = itr->m_BlockZ - PrevZ; + if (abs(dx) + abs(dz) < 4) + { + // Too short a segment to smooth-subdivide into quarters + continue; + } + int dr = itr->m_Radius - PrevR; + int dt = itr->m_Top - PrevT; + int db = itr->m_Bottom - PrevB; + int Rad1 = std::max(PrevR + 1 * dr / 4, 1); + int Rad2 = std::max(PrevR + 3 * dr / 4, 1); + a_Dst.push_back(cRavDefPoint(PrevX + 1 * dx / 4, PrevZ + 1 * dz / 4, Rad1, PrevT + 1 * dt / 4, PrevB + 1 * db / 4)); + a_Dst.push_back(cRavDefPoint(PrevX + 3 * dx / 4, PrevZ + 3 * dz / 4, Rad2, PrevT + 3 * dt / 4, PrevB + 3 * db / 4)); + PrevX = itr->m_BlockX; + PrevZ = itr->m_BlockZ; + PrevR = itr->m_Radius; + PrevT = itr->m_Top; + PrevB = itr->m_Bottom; + } + a_Dst.push_back(a_Src.back()); +} + + + + + +void cStructGenRavines::cRavine::Smooth(void) +{ + cRavDefPoints Pts; + RefineDefPoints(m_Points, Pts); // Refine m_Points -> Pts + RefineDefPoints(Pts, m_Points); // Refine Pts -> m_Points +} + + + + + +void cStructGenRavines::cRavine::FinishLinear(void) +{ + // For each segment, use Bresenham's line algorithm to draw a "line" of defpoints + // _X 2012_07_20: I tried modifying this algorithm to produce "thick" lines (only one coord change per point) + // But the results were about the same as the original, so I disposed of it again - no need to use twice the count of points + + cRavDefPoints Pts; + std::swap(Pts, m_Points); + + m_Points.reserve(Pts.size() * 3); + int PrevX = Pts.front().m_BlockX; + int PrevZ = Pts.front().m_BlockZ; + for (cRavDefPoints::const_iterator itr = Pts.begin() + 1, end = Pts.end(); itr != end; ++itr) + { + int x1 = itr->m_BlockX; + int z1 = itr->m_BlockZ; + int dx = abs(x1 - PrevX); + int dz = abs(z1 - PrevZ); + int sx = (PrevX < x1) ? 1 : -1; + int sz = (PrevZ < z1) ? 1 : -1; + int err = dx - dz; + int R = itr->m_Radius; + int T = itr->m_Top; + int B = itr->m_Bottom; + while (true) + { + m_Points.push_back(cRavDefPoint(PrevX, PrevZ, R, T, B)); + if ((PrevX == x1) && (PrevZ == z1)) + { + break; + } + int e2 = 2 * err; + if (e2 > -dz) + { + err -= dz; + PrevX += sx; + } + if (e2 < dx) + { + err += dx; + PrevZ += sz; + } + } // while (true) + } // for itr +} + + + + + +#ifdef _DEBUG +AString cStructGenRavines::cRavine::ExportAsSVG(int a_Color, int a_OffsetX, int a_OffsetZ) const +{ + AString SVG; + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#%06x;stroke-width:1px;\"\nd=\"", a_Color); + char Prefix = 'M'; // The first point needs "M" prefix, all the others need "L" + for (cRavDefPoints::const_iterator itr = m_Points.begin(); itr != m_Points.end(); ++itr) + { + AppendPrintf(SVG, "%c %d,%d ", Prefix, a_OffsetX + itr->m_BlockX, a_OffsetZ + itr->m_BlockZ); + Prefix = 'L'; + } + SVG.append("\"/>\n"); + + // Base point highlight: + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#ff0000;stroke-width:1px;\"\nd=\"M %d,%d L %d,%d\"/>\n", + a_OffsetX + m_BlockX - 5, a_OffsetZ + m_BlockZ, a_OffsetX + m_BlockX + 5, a_OffsetZ + m_BlockZ + ); + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#ff0000;stroke-width:1px;\"\nd=\"M %d,%d L %d,%d\"/>\n", + a_OffsetX + m_BlockX, a_OffsetZ + m_BlockZ - 5, a_OffsetX + m_BlockX, a_OffsetZ + m_BlockZ + 5 + ); + + // A gray line from the base point to the first point of the ravine, for identification: + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#cfcfcf;stroke-width:1px;\"\nd=\"M %d,%d L %d,%d\"/>\n", + a_OffsetX + m_BlockX, a_OffsetZ + m_BlockZ, a_OffsetX + m_Points.front().m_BlockX, a_OffsetZ + m_Points.front().m_BlockZ + ); + + // Offset guides: + if (a_OffsetX > 0) + { + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#0000ff;stroke-width:1px;\"\nd=\"M %d,0 L %d,1024\"/>\n", + a_OffsetX, a_OffsetX + ); + } + if (a_OffsetZ > 0) + { + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#0000ff;stroke-width:1px;\"\nd=\"M 0,%d L 1024,%d\"/>\n", + a_OffsetZ, a_OffsetZ + ); + } + return SVG; +} +#endif // _DEBUG + + + + + +void cStructGenRavines::cRavine::ProcessChunk( + int a_ChunkX, int a_ChunkZ, + cChunkDef::BlockTypes & a_BlockTypes, + cChunkDef::HeightMap & a_HeightMap +) +{ + int BlockStartX = a_ChunkX * cChunkDef::Width; + int BlockStartZ = a_ChunkZ * cChunkDef::Width; + int BlockEndX = BlockStartX + cChunkDef::Width; + int BlockEndZ = BlockStartZ + cChunkDef::Width; + for (cRavDefPoints::const_iterator itr = m_Points.begin(), end = m_Points.end(); itr != end; ++itr) + { + if ( + (itr->m_BlockX + itr->m_Radius < BlockStartX) || + (itr->m_BlockX - itr->m_Radius > BlockEndX) || + (itr->m_BlockZ + itr->m_Radius < BlockStartZ) || + (itr->m_BlockZ - itr->m_Radius > BlockEndZ) + ) + { + // Cannot intersect, bail out early + continue; + } + + // Carve out a cylinder around the xz point, m_Radius in diameter, from Bottom to Top: + int RadiusSq = itr->m_Radius * itr->m_Radius; // instead of doing sqrt for each distance, we do sqr of the radius + int DifX = BlockStartX - itr->m_BlockX; // substitution for faster calc + int DifZ = BlockStartZ - itr->m_BlockZ; // substitution for faster calc + for (int x = 0; x < cChunkDef::Width; x++) for (int z = 0; z < cChunkDef::Width; z++) + { + #ifdef _DEBUG + // DEBUG: Make the ravine shapepoints visible on a single layer (so that we can see with Minutor what's going on) + if ((DifX + x == 0) && (DifZ + z == 0)) + { + cChunkDef::SetBlock(a_BlockTypes, x, 4, z, E_BLOCK_LAPIS_ORE); + } + #endif // _DEBUG + + int DistSq = (DifX + x) * (DifX + x) + (DifZ + z) * (DifZ + z); + if (DistSq <= RadiusSq) + { + int Top = std::min(itr->m_Top, (int)(cChunkDef::Height)); // Stupid gcc needs int cast + for (int y = std::max(itr->m_Bottom, 1); y <= Top; y++) + { + switch (cChunkDef::GetBlock(a_BlockTypes, x, y, z)) + { + // Only carve out these specific block types + case E_BLOCK_DIRT: + case E_BLOCK_GRASS: + case E_BLOCK_STONE: + case E_BLOCK_COBBLESTONE: + case E_BLOCK_GRAVEL: + case E_BLOCK_SAND: + case E_BLOCK_SANDSTONE: + case E_BLOCK_NETHERRACK: + case E_BLOCK_COAL_ORE: + case E_BLOCK_IRON_ORE: + case E_BLOCK_GOLD_ORE: + case E_BLOCK_DIAMOND_ORE: + case E_BLOCK_REDSTONE_ORE: + case E_BLOCK_REDSTONE_ORE_GLOWING: + { + cChunkDef::SetBlock(a_BlockTypes, x, y, z, E_BLOCK_AIR); + break; + } + default: break; + } + } + } + } // for x, z - a_BlockTypes + } // for itr - m_Points[] +} + + + + diff --git a/src/Generating/Ravines.h b/src/Generating/Ravines.h new file mode 100644 index 000000000..05164a5b2 --- /dev/null +++ b/src/Generating/Ravines.h @@ -0,0 +1,46 @@ + +// Ravines.h + +// Interfaces to the cStructGenRavines class representing the ravine structure generator + + + + + +#pragma once + +#include "ComposableGenerator.h" +#include "../Noise.h" + + + + + +class cStructGenRavines : + public cStructureGen +{ +public: + cStructGenRavines(int a_Seed, int a_Size); + ~cStructGenRavines(); + +protected: + class cRavine; // fwd: Ravines.cpp + typedef std::list<cRavine *> cRavines; + + cNoise m_Noise; + int m_Size; // Max size, in blocks, of the ravines generated + cRavines m_Cache; + + /// Clears everything from the cache + void ClearCache(void); + + /// Returns all ravines that *may* intersect the given chunk. All the ravines are valid until the next call to this function. + void GetRavinesForChunk(int a_ChunkX, int a_ChunkZ, cRavines & a_Ravines); + + // cStructureGen override: + virtual void GenStructures(cChunkDesc & a_ChunkDesc) override; +} ; + + + + diff --git a/src/Generating/StructGen.cpp b/src/Generating/StructGen.cpp new file mode 100644 index 000000000..504c26238 --- /dev/null +++ b/src/Generating/StructGen.cpp @@ -0,0 +1,712 @@ + +// StructGen.h + +#include "Globals.h" +#include "StructGen.h" +#include "../BlockID.h" +#include "Trees.h" +#include "../BlockArea.h" +#include "../LinearUpscale.h" + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenOreNests configuration: + +const int MAX_HEIGHT_COAL = 127; +const int NUM_NESTS_COAL = 50; +const int NEST_SIZE_COAL = 10; + +const int MAX_HEIGHT_IRON = 64; +const int NUM_NESTS_IRON = 14; +const int NEST_SIZE_IRON = 6; + +const int MAX_HEIGHT_REDSTONE = 16; +const int NUM_NESTS_REDSTONE = 4; +const int NEST_SIZE_REDSTONE = 6; + +const int MAX_HEIGHT_GOLD = 32; +const int NUM_NESTS_GOLD = 2; +const int NEST_SIZE_GOLD = 6; + +const int MAX_HEIGHT_DIAMOND = 15; +const int NUM_NESTS_DIAMOND = 1; +const int NEST_SIZE_DIAMOND = 4; + +const int MAX_HEIGHT_LAPIS = 30; +const int NUM_NESTS_LAPIS = 2; +const int NEST_SIZE_LAPIS = 5; + +const int MAX_HEIGHT_DIRT = 127; +const int NUM_NESTS_DIRT = 20; +const int NEST_SIZE_DIRT = 32; + +const int MAX_HEIGHT_GRAVEL = 70; +const int NUM_NESTS_GRAVEL = 15; +const int NEST_SIZE_GRAVEL = 32; + + + + + +template <typename T> T Clamp(T a_Value, T a_Min, T a_Max) +{ + return (a_Value < a_Min) ? a_Min : ((a_Value > a_Max) ? a_Max : a_Value); +} + + + + + +static bool SortTreeBlocks(const sSetBlock & a_First, const sSetBlock & a_Second) +{ + return (a_First.BlockType == E_BLOCK_LOG) && (a_Second.BlockType != E_BLOCK_LOG); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenTrees: + +void cStructGenTrees::GenStructures(cChunkDesc & a_ChunkDesc) +{ + int ChunkX = a_ChunkDesc.GetChunkX(); + int ChunkZ = a_ChunkDesc.GetChunkZ(); + + cChunkDesc WorkerDesc(ChunkX, ChunkZ); + + // Generate trees: + for (int x = 0; x <= 2; x++) + { + int BaseX = ChunkX + x - 1; + for (int z = 0; z <= 2; z++) + { + int BaseZ = ChunkZ + z - 1; + + cChunkDesc * Dest; + + if ((x != 1) || (z != 1)) + { + Dest = &WorkerDesc; + WorkerDesc.SetChunkCoords(BaseX, BaseZ); + + m_BiomeGen->GenBiomes (BaseX, BaseZ, WorkerDesc.GetBiomeMap()); + m_HeightGen->GenHeightMap (BaseX, BaseZ, WorkerDesc.GetHeightMap()); + m_CompositionGen->ComposeTerrain(WorkerDesc); + // TODO: Free the entity lists + } + else + { + Dest = &a_ChunkDesc; + } + + int NumTrees = GetNumTrees(BaseX, BaseZ, Dest->GetBiomeMap()); + + sSetBlockVector OutsideLogs, OutsideOther; + for (int i = 0; i < NumTrees; i++) + { + GenerateSingleTree(BaseX, BaseZ, i, *Dest, OutsideLogs, OutsideOther); + } + + sSetBlockVector IgnoredOverflow; + IgnoredOverflow.reserve(OutsideOther.size()); + ApplyTreeImage(ChunkX, ChunkZ, a_ChunkDesc, OutsideOther, IgnoredOverflow); + IgnoredOverflow.clear(); + IgnoredOverflow.reserve(OutsideLogs.size()); + ApplyTreeImage(ChunkX, ChunkZ, a_ChunkDesc, OutsideLogs, IgnoredOverflow); + } // for z + } // for x + + // Update the heightmap: + for (int x = 0; x < cChunkDef::Width; x++) + { + for (int z = 0; z < cChunkDef::Width; z++) + { + for (int y = cChunkDef::Height - 1; y >= 0; y--) + { + if (a_ChunkDesc.GetBlockType(x, y, z) != E_BLOCK_AIR) + { + a_ChunkDesc.SetHeight(x, z, y); + break; + } + } // for y + } // for z + } // for x +} + + + + + +void cStructGenTrees::GenerateSingleTree( + int a_ChunkX, int a_ChunkZ, int a_Seq, + cChunkDesc & a_ChunkDesc, + sSetBlockVector & a_OutsideLogs, + sSetBlockVector & a_OutsideOther +) +{ + int x = (m_Noise.IntNoise3DInt(a_ChunkX + a_ChunkZ, a_ChunkZ, a_Seq) / 19) % cChunkDef::Width; + int z = (m_Noise.IntNoise3DInt(a_ChunkX - a_ChunkZ, a_Seq, a_ChunkZ) / 19) % cChunkDef::Width; + + int Height = a_ChunkDesc.GetHeight(x, z); + + if ((Height <= 0) || (Height > 240)) + { + return; + } + + // Check the block underneath the tree: + BLOCKTYPE TopBlock = a_ChunkDesc.GetBlockType(x, Height, z); + if ((TopBlock != E_BLOCK_DIRT) && (TopBlock != E_BLOCK_GRASS) && (TopBlock != E_BLOCK_FARMLAND)) + { + return; + } + + sSetBlockVector TreeLogs, TreeOther; + GetTreeImageByBiome( + a_ChunkX * cChunkDef::Width + x, Height + 1, a_ChunkZ * cChunkDef::Width + z, + m_Noise, a_Seq, + a_ChunkDesc.GetBiome(x, z), + TreeLogs, TreeOther + ); + + // Check if the generated image fits the terrain. Only the logs are checked: + for (sSetBlockVector::const_iterator itr = TreeLogs.begin(); itr != TreeLogs.end(); ++itr) + { + if ((itr->ChunkX != a_ChunkX) || (itr->ChunkZ != a_ChunkZ)) + { + // Outside the chunk + continue; + } + + BLOCKTYPE Block = a_ChunkDesc.GetBlockType(itr->x, itr->y, itr->z); + switch (Block) + { + CASE_TREE_ALLOWED_BLOCKS: + { + break; + } + default: + { + // There's something in the way, abort this tree altogether + return; + } + } + } + + ApplyTreeImage(a_ChunkX, a_ChunkZ, a_ChunkDesc, TreeOther, a_OutsideOther); + ApplyTreeImage(a_ChunkX, a_ChunkZ, a_ChunkDesc, TreeLogs, a_OutsideLogs); +} + + + + + +void cStructGenTrees::ApplyTreeImage( + int a_ChunkX, int a_ChunkZ, + cChunkDesc & a_ChunkDesc, + const sSetBlockVector & a_Image, + sSetBlockVector & a_Overflow +) +{ + // Put the generated image into a_BlockTypes, push things outside this chunk into a_Blocks + for (sSetBlockVector::const_iterator itr = a_Image.begin(), end = a_Image.end(); itr != end; ++itr) + { + if ((itr->ChunkX == a_ChunkX) && (itr->ChunkZ == a_ChunkZ)) + { + // Inside this chunk, integrate into a_ChunkDesc: + switch (a_ChunkDesc.GetBlockType(itr->x, itr->y, itr->z)) + { + case E_BLOCK_LEAVES: + { + if (itr->BlockType != E_BLOCK_LOG) + { + break; + } + // fallthrough: + } + CASE_TREE_OVERWRITTEN_BLOCKS: + { + a_ChunkDesc.SetBlockTypeMeta(itr->x, itr->y, itr->z, itr->BlockType, itr->BlockMeta); + break; + } + + } // switch (GetBlock()) + continue; + } + + // Outside the chunk, push into a_Overflow. + // Don't check if already present there, by separating logs and others we don't need the checks anymore: + a_Overflow.push_back(*itr); + } +} + + + + + +int cStructGenTrees::GetNumTrees( + int a_ChunkX, int a_ChunkZ, + const cChunkDef::BiomeMap & a_Biomes +) +{ + int NumTrees = 0; + for (int x = 0; x < cChunkDef::Width; x++) for (int z = 0; z < cChunkDef::Width; z++) + { + int Add = 0; + switch (cChunkDef::GetBiome(a_Biomes, x, z)) + { + case biOcean: Add = 2; break; + case biDesert: Add = 0; break; + case biPlains: Add = 1; break; + case biExtremeHills: Add = 3; break; + case biForest: Add = 30; break; + case biTaiga: Add = 30; break; + case biSwampland: Add = 8; break; + case biIcePlains: Add = 1; break; + case biIceMountains: Add = 1; break; + case biMushroomIsland: Add = 3; break; + case biMushroomShore: Add = 3; break; + case biForestHills: Add = 20; break; + case biTaigaHills: Add = 20; break; + case biExtremeHillsEdge: Add = 5; break; + case biJungle: Add = 120; break; + case biJungleHills: Add = 90; break; + case biJungleEdge: Add = 90; break; + case biBirchForest: Add = 30; break; + case biBirchForestHills: Add = 20; break; + case biRoofedForest: Add = 50; break; + case biColdTaiga: Add = 20; break; + case biColdTaigaHills: Add = 15; break; + case biMegaTaiga: Add = 30; break; + case biMegaTaigaHills: Add = 25; break; + case biExtremeHillsPlus: Add = 3; break; + case biSavanna: Add = 8; break; + case biSavannaPlateau: Add = 12; break; + case biMesa: Add = 2; break; + case biMesaPlateauF: Add = 8; break; + case biMesaPlateau: Add = 8; break; + case biSunflowerPlains: Add = 1; break; + case biDesertM: Add = 0; break; + case biExtremeHillsM: Add = 4; break; + case biFlowerForest: Add = 30; break; + case biTaigaM: Add = 30; break; + case biSwamplandM: Add = 8; break; + case biIcePlainsSpikes: Add = 1; break; + case biJungleM: Add = 120; break; + case biJungleEdgeM: Add = 90; break; + case biBirchForestM: Add = 30; break; + case biBirchForestHillsM: Add = 20; break; + case biRoofedForestM: Add = 40; break; + case biColdTaigaM: Add = 30; break; + case biMegaSpruceTaiga: Add = 30; break; + case biMegaSpruceTaigaHills: Add = 30; break; + case biExtremeHillsPlusM: Add = 4; break; + case biSavannaM: Add = 8; break; + case biSavannaPlateauM: Add = 12; break; + case biMesaBryce: Add = 4; break; + case biMesaPlateauFM: Add = 12; break; + case biMesaPlateauM: Add = 12; break; + } + NumTrees += Add; + } + return NumTrees / 1024; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenOreNests: + +void cStructGenOreNests::GenStructures(cChunkDesc & a_ChunkDesc) +{ + int ChunkX = a_ChunkDesc.GetChunkX(); + int ChunkZ = a_ChunkDesc.GetChunkZ(); + cChunkDef::BlockTypes & BlockTypes = a_ChunkDesc.GetBlockTypes(); + GenerateOre(ChunkX, ChunkZ, E_BLOCK_COAL_ORE, MAX_HEIGHT_COAL, NUM_NESTS_COAL, NEST_SIZE_COAL, BlockTypes, 1); + GenerateOre(ChunkX, ChunkZ, E_BLOCK_IRON_ORE, MAX_HEIGHT_IRON, NUM_NESTS_IRON, NEST_SIZE_IRON, BlockTypes, 2); + GenerateOre(ChunkX, ChunkZ, E_BLOCK_REDSTONE_ORE, MAX_HEIGHT_REDSTONE, NUM_NESTS_REDSTONE, NEST_SIZE_REDSTONE, BlockTypes, 3); + GenerateOre(ChunkX, ChunkZ, E_BLOCK_GOLD_ORE, MAX_HEIGHT_GOLD, NUM_NESTS_GOLD, NEST_SIZE_GOLD, BlockTypes, 4); + GenerateOre(ChunkX, ChunkZ, E_BLOCK_DIAMOND_ORE, MAX_HEIGHT_DIAMOND, NUM_NESTS_DIAMOND, NEST_SIZE_DIAMOND, BlockTypes, 5); + GenerateOre(ChunkX, ChunkZ, E_BLOCK_LAPIS_ORE, MAX_HEIGHT_LAPIS, NUM_NESTS_LAPIS, NEST_SIZE_LAPIS, BlockTypes, 6); + GenerateOre(ChunkX, ChunkZ, E_BLOCK_DIRT, MAX_HEIGHT_DIRT, NUM_NESTS_DIRT, NEST_SIZE_DIRT, BlockTypes, 10); + GenerateOre(ChunkX, ChunkZ, E_BLOCK_GRAVEL, MAX_HEIGHT_GRAVEL, NUM_NESTS_GRAVEL, NEST_SIZE_GRAVEL, BlockTypes, 11); +} + + + + + +void cStructGenOreNests::GenerateOre(int a_ChunkX, int a_ChunkZ, BLOCKTYPE a_OreType, int a_MaxHeight, int a_NumNests, int a_NestSize, cChunkDef::BlockTypes & a_BlockTypes, int a_Seq) +{ + // This function generates several "nests" of ore, each nest consisting of number of ore blocks relatively adjacent to each other. + // It does so by making a random XYZ walk and adding ore along the way in cuboids of different (random) sizes + // Only stone gets replaced with ore, all other blocks stay (so the nest can actually be smaller than specified). + + for (int i = 0; i < a_NumNests; i++) + { + int rnd = m_Noise.IntNoise3DInt(a_ChunkX + i, a_Seq, a_ChunkZ + 64 * i) / 8; + int BaseX = rnd % cChunkDef::Width; + rnd /= cChunkDef::Width; + int BaseZ = rnd % cChunkDef::Width; + rnd /= cChunkDef::Width; + int BaseY = rnd % a_MaxHeight; + rnd /= a_MaxHeight; + int NestSize = a_NestSize + (rnd % (a_NestSize / 4)); // The actual nest size may be up to 1/4 larger + int Num = 0; + while (Num < NestSize) + { + // Put a cuboid around [BaseX, BaseY, BaseZ] + int rnd = m_Noise.IntNoise3DInt(a_ChunkX + 64 * i, 2 * a_Seq + Num, a_ChunkZ + 32 * i) / 8; + int xsize = rnd % 2; + int ysize = (rnd / 4) % 2; + int zsize = (rnd / 16) % 2; + rnd >>= 8; + for (int x = xsize; x >= 0; --x) + { + int BlockX = BaseX + x; + if ((BlockX < 0) || (BlockX >= cChunkDef::Width)) + { + Num++; // So that the cycle finishes even if the base coords wander away from the chunk + continue; + } + for (int y = ysize; y >= 0; --y) + { + int BlockY = BaseY + y; + if ((BlockY < 0) || (BlockY >= cChunkDef::Height)) + { + Num++; // So that the cycle finishes even if the base coords wander away from the chunk + continue; + } + for (int z = zsize; z >= 0; --z) + { + int BlockZ = BaseZ + z; + if ((BlockZ < 0) || (BlockZ >= cChunkDef::Width)) + { + Num++; // So that the cycle finishes even if the base coords wander away from the chunk + continue; + } + + int Index = cChunkDef::MakeIndexNoCheck(BlockX, BlockY, BlockZ); + if (a_BlockTypes[Index] == E_BLOCK_STONE) + { + a_BlockTypes[Index] = a_OreType; + } + Num++; + } // for z + } // for y + } // for x + + // Move the base to a neighbor voxel + switch (rnd % 4) + { + case 0: BaseX--; break; + case 1: BaseX++; break; + } + switch ((rnd >> 3) % 4) + { + case 0: BaseY--; break; + case 1: BaseY++; break; + } + switch ((rnd >> 6) % 4) + { + case 0: BaseZ--; break; + case 1: BaseZ++; break; + } + } // while (Num < NumBlocks) + } // for i - NumNests +} + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenLakes: + +void cStructGenLakes::GenStructures(cChunkDesc & a_ChunkDesc) +{ + int ChunkX = a_ChunkDesc.GetChunkX(); + int ChunkZ = a_ChunkDesc.GetChunkZ(); + + for (int z = -1; z < 2; z++) for (int x = -1; x < 2; x++) + { + if (((m_Noise.IntNoise2DInt(ChunkX + x, ChunkZ + z) / 17) % 100) > m_Probability) + { + continue; + } + + cBlockArea Lake; + CreateLakeImage(ChunkX + x, ChunkZ + z, Lake); + + int OfsX = Lake.GetOriginX() + x * cChunkDef::Width; + int OfsZ = Lake.GetOriginZ() + z * cChunkDef::Width; + + // Merge the lake into the current data + a_ChunkDesc.WriteBlockArea(Lake, OfsX, Lake.GetOriginY(), OfsZ, cBlockArea::msLake); + } // for x, z - neighbor chunks +} + + + + + +void cStructGenLakes::CreateLakeImage(int a_ChunkX, int a_ChunkZ, cBlockArea & a_Lake) +{ + a_Lake.Create(16, 8, 16); + a_Lake.Fill(cBlockArea::baTypes, E_BLOCK_SPONGE); // Sponge is the NOP blocktype for lake merging strategy + + // Find the minimum height in this chunk: + cChunkDef::HeightMap HeightMap; + m_HeiGen.GenHeightMap(a_ChunkX, a_ChunkZ, HeightMap); + HEIGHTTYPE MinHeight = HeightMap[0]; + for (int i = 1; i < ARRAYCOUNT(HeightMap); i++) + { + if (HeightMap[i] < MinHeight) + { + MinHeight = HeightMap[i]; + } + } + + // Make a random position in the chunk by using a random 16 block XZ offset and random height up to chunk's max height minus 6 + MinHeight = std::max(MinHeight - 6, 2); + int Rnd = m_Noise.IntNoise3DInt(a_ChunkX, 128, a_ChunkZ) / 11; + // Random offset [-8 .. 8], with higher probability around 0; add up four three-bit-wide randoms [0 .. 28], divide and subtract to get range + int OffsetX = 4 * ((Rnd & 0x07) + ((Rnd & 0x38) >> 3) + ((Rnd & 0x1c0) >> 6) + ((Rnd & 0xe00) >> 9)) / 7 - 8; + Rnd >>= 12; + // Random offset [-8 .. 8], with higher probability around 0; add up four three-bit-wide randoms [0 .. 28], divide and subtract to get range + int OffsetZ = 4 * ((Rnd & 0x07) + ((Rnd & 0x38) >> 3) + ((Rnd & 0x1c0) >> 6) + ((Rnd & 0xe00) >> 9)) / 7 - 8; + Rnd = m_Noise.IntNoise3DInt(a_ChunkX, 512, a_ChunkZ) / 13; + // Random height [1 .. MinHeight] with preference to center heights + int HeightY = 1 + (((Rnd & 0x1ff) % MinHeight) + (((Rnd >> 9) & 0x1ff) % MinHeight)) / 2; + + a_Lake.SetOrigin(OffsetX, HeightY, OffsetZ); + + // Hollow out a few bubbles inside the blockarea: + int NumBubbles = 4 + ((Rnd >> 18) & 0x03); // 4 .. 7 bubbles + BLOCKTYPE * BlockTypes = a_Lake.GetBlockTypes(); + for (int i = 0; i < NumBubbles; i++) + { + int Rnd = m_Noise.IntNoise3DInt(a_ChunkX, i, a_ChunkZ) / 13; + const int BubbleR = 2 + (Rnd & 0x03); // 2 .. 5 + const int Range = 16 - 2 * BubbleR; + const int BubbleX = BubbleR + (Rnd % Range); + Rnd >>= 4; + const int BubbleY = 4 + (Rnd & 0x01); // 4 .. 5 + Rnd >>= 1; + const int BubbleZ = BubbleR + (Rnd % Range); + Rnd >>= 4; + const int HalfR = BubbleR / 2; // 1 .. 2 + const int RSquared = BubbleR * BubbleR; + for (int y = -HalfR; y <= HalfR; y++) + { + // BubbleY + y is in the [0, 7] bounds + int DistY = 4 * y * y / 3; + int IdxY = (BubbleY + y) * 16 * 16; + for (int z = -BubbleR; z <= BubbleR; z++) + { + int DistYZ = DistY + z * z; + if (DistYZ >= RSquared) + { + continue; + } + int IdxYZ = BubbleX + IdxY + (BubbleZ + z) * 16; + for (int x = -BubbleR; x <= BubbleR; x++) + { + if (x * x + DistYZ < RSquared) + { + BlockTypes[x + IdxYZ] = E_BLOCK_AIR; + } + } // for x + } // for z + } // for y + } // for i - bubbles + + // Turn air in the bottom half into liquid: + for (int y = 0; y < 4; y++) + { + for (int z = 0; z < 16; z++) for (int x = 0; x < 16; x++) + { + if (BlockTypes[x + z * 16 + y * 16 * 16] == E_BLOCK_AIR) + { + BlockTypes[x + z * 16 + y * 16 * 16] = m_Fluid; + } + } // for z, x + } // for y + + // TODO: Turn sponge next to lava into stone + + // a_Lake.SaveToSchematicFile(Printf("Lake_%d_%d.schematic", a_ChunkX, a_ChunkZ)); +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenDirectOverhangs: + +cStructGenDirectOverhangs::cStructGenDirectOverhangs(int a_Seed) : + m_Noise1(a_Seed), + m_Noise2(a_Seed + 1000) +{ +} + + + + + +void cStructGenDirectOverhangs::GenStructures(cChunkDesc & a_ChunkDesc) +{ + // If there is no column of the wanted biome, bail out: + if (!HasWantedBiome(a_ChunkDesc)) + { + return; + } + + HEIGHTTYPE MaxHeight = a_ChunkDesc.GetMaxHeight(); + + const int SEGMENT_HEIGHT = 8; + const int INTERPOL_X = 16; // Must be a divisor of 16 + const int INTERPOL_Z = 16; // Must be a divisor of 16 + // Interpolate the chunk in 16 * SEGMENT_HEIGHT * 16 "segments", each SEGMENT_HEIGHT blocks high and each linearly interpolated separately. + // Have two buffers, one for the lowest floor and one for the highest floor, so that Y-interpolation can be done between them + // Then swap the buffers and use the previously-top one as the current-bottom, without recalculating it. + + int FloorBuf1[17 * 17]; + int FloorBuf2[17 * 17]; + int * FloorHi = FloorBuf1; + int * FloorLo = FloorBuf2; + int BaseX = a_ChunkDesc.GetChunkX() * cChunkDef::Width; + int BaseZ = a_ChunkDesc.GetChunkZ() * cChunkDef::Width; + int BaseY = 63; + + // Interpolate the lowest floor: + for (int z = 0; z <= 16 / INTERPOL_Z; z++) for (int x = 0; x <= 16 / INTERPOL_X; x++) + { + FloorLo[INTERPOL_X * x + 17 * INTERPOL_Z * z] = + m_Noise1.IntNoise3DInt(BaseX + INTERPOL_X * x, BaseY, BaseZ + INTERPOL_Z * z) * + m_Noise2.IntNoise3DInt(BaseX + INTERPOL_X * x, BaseY, BaseZ + INTERPOL_Z * z) / + 256; + } // for x, z - FloorLo[] + LinearUpscale2DArrayInPlace(FloorLo, 17, 17, INTERPOL_X, INTERPOL_Z); + + // Interpolate segments: + for (int Segment = BaseY; Segment < MaxHeight; Segment += SEGMENT_HEIGHT) + { + // First update the high floor: + for (int z = 0; z <= 16 / INTERPOL_Z; z++) for (int x = 0; x <= 16 / INTERPOL_X; x++) + { + FloorHi[INTERPOL_X * x + 17 * INTERPOL_Z * z] = + m_Noise1.IntNoise3DInt(BaseX + INTERPOL_X * x, Segment + SEGMENT_HEIGHT, BaseZ + INTERPOL_Z * z) * + m_Noise2.IntNoise3DInt(BaseX + INTERPOL_Z * x, Segment + SEGMENT_HEIGHT, BaseZ + INTERPOL_Z * z) / + 256; + } // for x, z - FloorLo[] + LinearUpscale2DArrayInPlace(FloorHi, 17, 17, INTERPOL_X, INTERPOL_Z); + + // Interpolate between FloorLo and FloorHi: + for (int z = 0; z < 16; z++) for (int x = 0; x < 16; x++) + { + switch (a_ChunkDesc.GetBiome(x, z)) + { + case biExtremeHills: + case biExtremeHillsEdge: + { + int Lo = FloorLo[x + 17 * z] / 256; + int Hi = FloorHi[x + 17 * z] / 256; + for (int y = 0; y < SEGMENT_HEIGHT; y++) + { + int Val = Lo + (Hi - Lo) * y / SEGMENT_HEIGHT; + if (Val < 0) + { + a_ChunkDesc.SetBlockType(x, y + Segment, z, E_BLOCK_AIR); + } + } // for y + break; + } + } // switch (biome) + } // for z, x + + // Swap the floors: + std::swap(FloorLo, FloorHi); + } +} + + + + + +bool cStructGenDirectOverhangs::HasWantedBiome(cChunkDesc & a_ChunkDesc) const +{ + cChunkDef::BiomeMap & Biomes = a_ChunkDesc.GetBiomeMap(); + for (int i = 0; i < ARRAYCOUNT(Biomes); i++) + { + switch (Biomes[i]) + { + case biExtremeHills: + case biExtremeHillsEdge: + { + return true; + } + } + } // for i + return false; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenDistortedMembraneOverhangs: + +cStructGenDistortedMembraneOverhangs::cStructGenDistortedMembraneOverhangs(int a_Seed) : + m_NoiseX(a_Seed + 1000), + m_NoiseY(a_Seed + 2000), + m_NoiseZ(a_Seed + 3000), + m_NoiseH(a_Seed + 4000) +{ +} + + + + + +void cStructGenDistortedMembraneOverhangs::GenStructures(cChunkDesc & a_ChunkDesc) +{ + const NOISE_DATATYPE Frequency = (NOISE_DATATYPE)16; + const NOISE_DATATYPE Amount = (NOISE_DATATYPE)1; + for (int y = 50; y < 128; y++) + { + NOISE_DATATYPE NoiseY = (NOISE_DATATYPE)y / 32; + // TODO: proper water level - where to get? + BLOCKTYPE ReplacementBlock = (y > 62) ? E_BLOCK_AIR : E_BLOCK_STATIONARY_WATER; + for (int z = 0; z < cChunkDef::Width; z++) + { + NOISE_DATATYPE NoiseZ = ((NOISE_DATATYPE)(a_ChunkDesc.GetChunkZ() * cChunkDef::Width + z)) / Frequency; + for (int x = 0; x < cChunkDef::Width; x++) + { + NOISE_DATATYPE NoiseX = ((NOISE_DATATYPE)(a_ChunkDesc.GetChunkX() * cChunkDef::Width + x)) / Frequency; + NOISE_DATATYPE DistortX = m_NoiseX.CubicNoise3D(NoiseX, NoiseY, NoiseZ) * Amount; + NOISE_DATATYPE DistortY = m_NoiseY.CubicNoise3D(NoiseX, NoiseY, NoiseZ) * Amount; + NOISE_DATATYPE DistortZ = m_NoiseZ.CubicNoise3D(NoiseX, NoiseY, NoiseZ) * Amount; + int MembraneHeight = 96 - (int)((DistortY + m_NoiseH.CubicNoise2D(NoiseX + DistortX, NoiseZ + DistortZ)) * 30); + if (MembraneHeight < y) + { + a_ChunkDesc.SetBlockType(x, y, z, ReplacementBlock); + } + } // for y + } // for x + } // for z +} + + + + diff --git a/src/Generating/StructGen.h b/src/Generating/StructGen.h new file mode 100644 index 000000000..853748bb8 --- /dev/null +++ b/src/Generating/StructGen.h @@ -0,0 +1,165 @@ + +// StructGen.h + +/* Interfaces to the various structure generators: + - cStructGenTrees + - cStructGenMarbleCaves + - cStructGenOres +*/ + + + + + +#pragma once + +#include "ComposableGenerator.h" +#include "../Noise.h" + + + + + +class cStructGenTrees : + public cStructureGen +{ +public: + cStructGenTrees(int a_Seed, cBiomeGen * a_BiomeGen, cTerrainHeightGen * a_HeightGen, cTerrainCompositionGen * a_CompositionGen) : + m_Seed(a_Seed), + m_Noise(a_Seed), + m_BiomeGen(a_BiomeGen), + m_HeightGen(a_HeightGen), + m_CompositionGen(a_CompositionGen) + {} + +protected: + + int m_Seed; + cNoise m_Noise; + cBiomeGen * m_BiomeGen; + cTerrainHeightGen * m_HeightGen; + cTerrainCompositionGen * m_CompositionGen; + + /** Generates and applies an image of a single tree. + Parts of the tree inside the chunk are applied to a_BlockX. + Parts of the tree outside the chunk are stored in a_OutsideX + */ + void GenerateSingleTree( + int a_ChunkX, int a_ChunkZ, int a_Seq, + cChunkDesc & a_ChunkDesc, + sSetBlockVector & a_OutsideLogs, + sSetBlockVector & a_OutsideOther + ) ; + + /// Applies an image into chunk blockdata; all blocks outside the chunk will be appended to a_Overflow + void ApplyTreeImage( + int a_ChunkX, int a_ChunkZ, + cChunkDesc & a_ChunkDesc, + const sSetBlockVector & a_Image, + sSetBlockVector & a_Overflow + ); + + int GetNumTrees( + int a_ChunkX, int a_ChunkZ, + const cChunkDef::BiomeMap & a_Biomes + ); + + // cStructureGen override: + virtual void GenStructures(cChunkDesc & a_ChunkDesc) override; +} ; + + + + + +class cStructGenOreNests : + public cStructureGen +{ +public: + cStructGenOreNests(int a_Seed) : m_Noise(a_Seed), m_Seed(a_Seed) {} + +protected: + cNoise m_Noise; + int m_Seed; + + // cStructureGen override: + virtual void GenStructures(cChunkDesc & a_ChunkDesc) override; + + void GenerateOre(int a_ChunkX, int a_ChunkZ, BLOCKTYPE a_OreType, int a_MaxHeight, int a_NumNests, int a_NestSize, cChunkDef::BlockTypes & a_BlockTypes, int a_Seq); +} ; + + + + + +class cStructGenLakes : + public cStructureGen +{ +public: + cStructGenLakes(int a_Seed, BLOCKTYPE a_Fluid, cTerrainHeightGen & a_HeiGen, int a_Probability) : + m_Noise(a_Seed), + m_Seed(a_Seed), + m_Fluid(a_Fluid), + m_HeiGen(a_HeiGen), + m_Probability(a_Probability) + { + } + +protected: + cNoise m_Noise; + int m_Seed; + BLOCKTYPE m_Fluid; + cTerrainHeightGen & m_HeiGen; + int m_Probability; ///< Chance, 0 .. 100, of a chunk having the lake + + // cStructureGen override: + virtual void GenStructures(cChunkDesc & a_ChunkDesc) override; + + /// Creates a lake image for the specified chunk into a_Lake + void CreateLakeImage(int a_ChunkX, int a_ChunkZ, cBlockArea & a_Lake); +} ; + + + + + + +class cStructGenDirectOverhangs : + public cStructureGen +{ +public: + cStructGenDirectOverhangs(int a_Seed); + +protected: + cNoise m_Noise1; + cNoise m_Noise2; + + // cStructureGen override: + virtual void GenStructures(cChunkDesc & a_ChunkDesc) override; + + bool HasWantedBiome(cChunkDesc & a_ChunkDesc) const; +} ; + + + + + +class cStructGenDistortedMembraneOverhangs : + public cStructureGen +{ +public: + cStructGenDistortedMembraneOverhangs(int a_Seed); + +protected: + cNoise m_NoiseX; + cNoise m_NoiseY; + cNoise m_NoiseZ; + cNoise m_NoiseH; + + // cStructureGen override: + virtual void GenStructures(cChunkDesc & a_ChunkDesc) override; +} ; + + + + diff --git a/src/Generating/Trees.cpp b/src/Generating/Trees.cpp new file mode 100644 index 000000000..fbed57cb6 --- /dev/null +++ b/src/Generating/Trees.cpp @@ -0,0 +1,734 @@ + +// Trees.cpp + +// Implements helper functions used for generating trees + +#include "Globals.h" +#include "Trees.h" +#include "../BlockID.h" + + + + +// DEBUG: +int gTotalLargeJungleTrees = 0; +int gOversizeLargeJungleTrees = 0; + + + + + +typedef struct +{ + int x, z; +} sCoords; + +typedef struct +{ + int x, z; + NIBBLETYPE Meta; +} sMetaCoords; + +static const sCoords Corners[] = +{ + {-1, -1}, + {-1, 1}, + {1, -1}, + {1, 1}, +} ; + +// BigO = a big ring of blocks, used for generating horz slices of treetops, the number indicates the radius + +static const sCoords BigO1[] = +{ + {0, -1}, + {-1, 0}, {1, 0}, + {0, 1}, +} ; + +static const sCoords BigO2[] = +{ + {-1, -2}, {0, -2}, {1, -2}, + {-2, -1}, {-1, -1}, {0, -1}, {1, -1}, {2, -1}, + {-2, 0}, {-1, 0}, {1, 0}, {2, 0}, + {-2, 1}, {-1, 1}, {0, 1}, {1, 1}, {2, 1}, + {-1, 2}, {0, 2}, {1, 2}, +} ; + +static const sCoords BigO3[] = +{ + {-2, -3}, {-1, -3}, {0, -3}, {1, -3}, {2, -3}, + {-3, -2}, {-2, -2}, {-1, -2}, {0, -2}, {1, -2}, {2, -2}, {3, -2}, + {-3, -1}, {-2, -1}, {-1, -1}, {0, -1}, {1, -1}, {2, -1}, {3, -1}, + {-3, 0}, {-2, 0}, {-1, 0}, {1, 0}, {2, 0}, {3, 0}, + {-3, 1}, {-2, 1}, {-1, 1}, {0, 1}, {1, 1}, {2, 1}, {3, 1}, + {-3, 2}, {-2, 2}, {-1, 2}, {0, 2}, {1, 2}, {2, 2}, {3, 2}, + {-2, 3}, {-1, 3}, {0, 3}, {1, 3}, {2, 3}, +} ; + +static const sCoords BigO4[] = // Part of Big Jungle tree +{ + {-2, -4}, {-1, -4}, {0, -4}, {1, -4}, {2, -4}, + {-3, -3}, {-2, -3}, {-1, -3}, {0, -3}, {1, -3}, {2, -3}, {3, -3}, + {-4, -2}, {-3, -2}, {-2, -2}, {-1, -2}, {0, -2}, {1, -2}, {2, -2}, {3, -2}, {4, -2}, + {-4, -1}, {-3, -1}, {-2, -1}, {-1, -1}, {0, -1}, {1, -1}, {2, -1}, {3, -1}, {4, -1}, + {-4, 0}, {-3, 0}, {-2, 0}, {-1, 0}, {1, 0}, {2, 0}, {3, 0}, {4, 0}, + {-4, 1}, {-3, 1}, {-2, 1}, {-1, 1}, {0, 1}, {1, 1}, {2, 1}, {3, 1}, {4, 1}, + {-4, 2}, {-3, 2}, {-2, 2}, {-1, 2}, {0, 2}, {1, 2}, {2, 2}, {3, 2}, {4, 2}, + {-3, 3}, {-2, 3}, {-1, 3}, {0, 3}, {1, 3}, {2, 3}, {3, 3}, + {-2, 4}, {-1, 4}, {0, 4}, {1, 4}, {2, 4}, +} ; + + + + + +typedef struct +{ + const sCoords * Coords; + size_t Count; +} sCoordsArr; + +static const sCoordsArr BigOs[] = +{ + {BigO1, ARRAYCOUNT(BigO1)}, + {BigO2, ARRAYCOUNT(BigO2)}, + {BigO3, ARRAYCOUNT(BigO3)}, + {BigO4, ARRAYCOUNT(BigO4)}, +} ; + + + + + +/// Pushes a specified layer of blocks of the same type around (x, h, z) into a_Blocks +inline void PushCoordBlocks(int a_BlockX, int a_Height, int a_BlockZ, sSetBlockVector & a_Blocks, const sCoords * a_Coords, size_t a_NumCoords, BLOCKTYPE a_BlockType, NIBBLETYPE a_Meta) +{ + for (size_t i = 0; i < a_NumCoords; i++) + { + a_Blocks.push_back(sSetBlock(a_BlockX + a_Coords[i].x, a_Height, a_BlockZ + a_Coords[i].z, a_BlockType, a_Meta)); + } +} + + + + +inline void PushCornerBlocks(int a_BlockX, int a_Height, int a_BlockZ, int a_Seq, cNoise & a_Noise, int a_Chance, sSetBlockVector & a_Blocks, int a_CornersDist, BLOCKTYPE a_BlockType, NIBBLETYPE a_Meta) +{ + for (size_t i = 0; i < ARRAYCOUNT(Corners); i++) + { + int x = a_BlockX + Corners[i].x; + int z = a_BlockZ + Corners[i].z; + if (a_Noise.IntNoise3DInt(x + 64 * a_Seq, a_Height, z + 64 * a_Seq) <= a_Chance) + { + a_Blocks.push_back(sSetBlock(x, a_Height, z, a_BlockType, a_Meta)); + } + } // for i - Corners[] +} + + + + + +inline void PushSomeColumns(int a_BlockX, int a_Height, int a_BlockZ, int a_ColumnHeight, int a_Seq, cNoise & a_Noise, int a_Chance, sSetBlockVector & a_Blocks, const sMetaCoords * a_Coords, size_t a_NumCoords, BLOCKTYPE a_BlockType) +{ + for (size_t i = 0; i < a_NumCoords; i++) + { + int x = a_BlockX + a_Coords[i].x; + int z = a_BlockZ + a_Coords[i].z; + if (a_Noise.IntNoise3DInt(x + 64 * a_Seq, a_Height + i, z + 64 * a_Seq) <= a_Chance) + { + for (int j = 0; j < a_ColumnHeight; j++) + { + a_Blocks.push_back(sSetBlock(x, a_Height - j, z, a_BlockType, a_Coords[i].Meta)); + } + } + } // for i - a_Coords[] +} + + + + + +void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, EMCSBiome a_Biome, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + switch (a_Biome) + { + case biPlains: + case biExtremeHills: + case biExtremeHillsEdge: + case biForest: + case biMushroomIsland: + case biMushroomShore: + case biForestHills: + case biDeepOcean: + case biStoneBeach: + case biColdBeach: + { + // Apple or birch trees: + if (a_Noise.IntNoise3DInt(a_BlockX, a_BlockY + 16 * a_Seq, a_BlockZ + 16 * a_Seq) < 0x5fffffff) + { + GetAppleTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + } + else + { + GetBirchTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + } + break; + } + + case biTaiga: + case biIcePlains: + case biIceMountains: + case biTaigaHills: + { + // Conifers + GetConiferTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + break; + } + + case biSwampland: + { + // Swamp trees: + GetSwampTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + break; + } + + case biJungle: + case biJungleHills: + case biJungleEdge: + { + // Apple bushes, large jungle trees, small jungle trees + if (a_Noise.IntNoise3DInt(a_BlockX, a_BlockY + 16 * a_Seq, a_BlockZ + 16 * a_Seq) < 0x6fffffff) + { + GetAppleBushImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + } + else + { + GetJungleTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + } + break; + } + + case biBirchForest: + case biBirchForestHills: + { + GetBirchTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + break; + } + + case biRoofedForest: + case biColdTaiga: + case biColdTaigaHills: + case biMegaTaiga: + case biMegaTaigaHills: + case biExtremeHillsPlus: + case biSavanna: + case biSavannaPlateau: + case biMesa: + case biMesaPlateauF: + case biMesaPlateau: + case biSunflowerPlains: + case biDesertM: + case biExtremeHillsM: + case biFlowerForest: + case biTaigaM: + case biSwamplandM: + case biIcePlainsSpikes: + case biJungleM: + case biJungleEdgeM: + case biBirchForestM: + case biBirchForestHillsM: + case biRoofedForestM: + case biColdTaigaM: + case biMegaSpruceTaiga: + case biMegaSpruceTaigaHills: + case biExtremeHillsPlusM: + case biSavannaM: + case biSavannaPlateauM: + case biMesaBryce: + case biMesaPlateauFM: + case biMesaPlateauM: + { + // TODO: These need their special trees + GetBirchTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + break; + } + } +} + + + + + +void GetAppleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + if (a_Noise.IntNoise3DInt(a_BlockX + 32 * a_Seq, a_BlockY + 32 * a_Seq, a_BlockZ) < 0x60000000) + { + GetSmallAppleTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + } + else + { + GetLargeAppleTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + } +} + + + + + +void GetSmallAppleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + /* Small apple tree has: + - a top plus (no log) + - optional BigO1 + random corners (log) + - 2 layers of BigO2 + random corners (log) + - 1 to 3 blocks of trunk + */ + + int Random = a_Noise.IntNoise3DInt(a_BlockX + 64 * a_Seq, a_BlockY, a_BlockZ) >> 3; + + int Heights[] = {1, 2, 2, 3} ; + int Height = 1 + Heights[Random & 3]; + Random >>= 2; + + // Pre-alloc so that we don't realloc too often later: + a_LogBlocks.reserve(Height + 5); + a_OtherBlocks.reserve(ARRAYCOUNT(BigO2) * 2 + ARRAYCOUNT(BigO1) + ARRAYCOUNT(Corners) * 3 + 3 + 5); + + // Trunk: + for (int i = 0; i < Height; i++) + { + a_LogBlocks.push_back(sSetBlock(a_BlockX, a_BlockY + i, a_BlockZ, E_BLOCK_LOG, E_META_LOG_APPLE)); + } + int Hei = a_BlockY + Height; + + // 2 BigO2 + corners layers: + for (int i = 0; i < 2; i++) + { + PushCoordBlocks (a_BlockX, Hei, a_BlockZ, a_OtherBlocks, BigO2, ARRAYCOUNT(BigO2), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + PushCornerBlocks(a_BlockX, Hei, a_BlockZ, a_Seq, a_Noise, 0x5000000 - i * 0x10000000, a_OtherBlocks, 2, E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + a_LogBlocks.push_back(sSetBlock(a_BlockX, Hei, a_BlockZ, E_BLOCK_LOG, E_META_LOG_APPLE)); + Hei++; + } // for i - 2* + + // Optional BigO1 + corners layer: + if ((Random & 1) == 0) + { + PushCoordBlocks (a_BlockX, Hei, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + PushCornerBlocks(a_BlockX, Hei, a_BlockZ, a_Seq, a_Noise, 0x6000000, a_OtherBlocks, 1, E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + a_LogBlocks.push_back(sSetBlock(a_BlockX, Hei, a_BlockZ, E_BLOCK_LOG, E_META_LOG_APPLE)); + Hei++; + } + + // Top plus: + PushCoordBlocks(a_BlockX, Hei, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + a_OtherBlocks.push_back(sSetBlock(a_BlockX, Hei, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_APPLE)); +} + + + + + +void GetLargeAppleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + // TODO +} + + + + + +void GetBirchTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + int Height = 5 + (a_Noise.IntNoise3DInt(a_BlockX + 64 * a_Seq, a_BlockY, a_BlockZ) % 3); + + // Prealloc, so that we don't realloc too often later: + a_LogBlocks.reserve(Height); + a_OtherBlocks.reserve(80); + + // The entire trunk, out of logs: + for (int i = Height - 1; i >= 0; --i) + { + a_LogBlocks.push_back(sSetBlock(a_BlockX, a_BlockY + i, a_BlockZ, E_BLOCK_LOG, E_META_LOG_BIRCH)); + } + int h = a_BlockY + Height; + + // Top layer - just the Plus: + PushCoordBlocks(a_BlockX, h, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_BIRCH); + a_OtherBlocks.push_back(sSetBlock(a_BlockX, h, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_BIRCH)); // There's no log at this layer + h--; + + // Second layer - log, Plus and maybe Corners: + PushCoordBlocks (a_BlockX, h, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_BIRCH); + PushCornerBlocks(a_BlockX, h, a_BlockZ, a_Seq, a_Noise, 0x5fffffff, a_OtherBlocks, 1, E_BLOCK_LEAVES, E_META_LEAVES_BIRCH); + h--; + + // Third and fourth layers - BigO2 and maybe 2*Corners: + for (int Row = 0; Row < 2; Row++) + { + PushCoordBlocks (a_BlockX, h, a_BlockZ, a_OtherBlocks, BigO2, ARRAYCOUNT(BigO2), E_BLOCK_LEAVES, E_META_LEAVES_BIRCH); + PushCornerBlocks(a_BlockX, h, a_BlockZ, a_Seq, a_Noise, 0x3fffffff + Row * 0x10000000, a_OtherBlocks, 2, E_BLOCK_LEAVES, E_META_LEAVES_BIRCH); + h--; + } // for Row - 2* +} + + + + + +void GetConiferTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + // Half chance for a spruce, half for a pine: + if (a_Noise.IntNoise3DInt(a_BlockX + 64 * a_Seq, a_BlockY, a_BlockZ + 32 * a_Seq) < 0x40000000) + { + GetSpruceTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + } + else + { + GetPineTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + } +} + + + + + +void GetSpruceTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + // Spruces have a top section with layer sizes of (0, 1, 0) or only (1, 0), + // then 1 - 3 sections of ascending sizes (1, 2) [most often], (1, 3) or (1, 2, 3) + // and an optional bottom section of size 1, followed by 1 - 3 clear trunk blocks + + // We'll use bits from this number as partial random numbers; but the noise function has mod8 irregularities + // (each of the mod8 remainders has a very different chance of occurrence) - that's why we divide by 8 + int MyRandom = a_Noise.IntNoise3DInt(a_BlockX + 32 * a_Seq, a_BlockY + 32 * a_Seq, a_BlockZ) / 8; + + static const int sHeights[] = {1, 2, 2, 3}; + int Height = sHeights[MyRandom & 3]; + MyRandom >>= 2; + + // Prealloc, so that we don't realloc too often later: + a_LogBlocks.reserve(Height); + a_OtherBlocks.reserve(180); + + // Clear trunk blocks: + for (int i = 0; i < Height; i++) + { + a_LogBlocks.push_back(sSetBlock(a_BlockX, a_BlockY + i, a_BlockZ, E_BLOCK_LOG, E_META_LOG_CONIFER)); + } + Height += a_BlockY; + + // Optional size-1 bottom leaves layer: + if ((MyRandom & 1) == 0) + { + PushCoordBlocks(a_BlockX, Height, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_CONIFER); + a_OtherBlocks.push_back(sSetBlock(a_BlockX, Height, a_BlockZ, E_BLOCK_LOG, E_META_LOG_CONIFER)); + Height++; + } + MyRandom >>= 1; + + // 1 to 3 sections of leaves layers: + static const int sNumSections[] = {1, 2, 2, 3}; + int NumSections = sNumSections[MyRandom & 3]; + MyRandom >>= 2; + for (int i = 0; i < NumSections; i++) + { + switch (MyRandom & 3) // SectionType; (1, 2) twice as often as the other two + { + case 0: + case 1: + { + PushCoordBlocks(a_BlockX, Height, a_BlockZ, a_OtherBlocks, BigO2, ARRAYCOUNT(BigO2), E_BLOCK_LEAVES, E_META_LEAVES_CONIFER); + PushCoordBlocks(a_BlockX, Height + 1, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_CONIFER); + a_LogBlocks.push_back(sSetBlock(a_BlockX, Height, a_BlockZ, E_BLOCK_LOG, E_META_LOG_CONIFER)); + a_LogBlocks.push_back(sSetBlock(a_BlockX, Height + 1, a_BlockZ, E_BLOCK_LOG, E_META_LOG_CONIFER)); + Height += 2; + break; + } + case 2: + { + PushCoordBlocks(a_BlockX, Height, a_BlockZ, a_OtherBlocks, BigO3, ARRAYCOUNT(BigO3), E_BLOCK_LEAVES, E_META_LEAVES_CONIFER); + PushCoordBlocks(a_BlockX, Height + 1, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_CONIFER); + a_LogBlocks.push_back(sSetBlock(a_BlockX, Height, a_BlockZ, E_BLOCK_LOG, E_META_LOG_CONIFER)); + a_LogBlocks.push_back(sSetBlock(a_BlockX, Height + 1, a_BlockZ, E_BLOCK_LOG, E_META_LOG_CONIFER)); + Height += 2; + break; + } + case 3: + { + PushCoordBlocks(a_BlockX, Height, a_BlockZ, a_OtherBlocks, BigO3, ARRAYCOUNT(BigO3), E_BLOCK_LEAVES, E_META_LEAVES_CONIFER); + PushCoordBlocks(a_BlockX, Height + 1, a_BlockZ, a_OtherBlocks, BigO2, ARRAYCOUNT(BigO2), E_BLOCK_LEAVES, E_META_LEAVES_CONIFER); + PushCoordBlocks(a_BlockX, Height + 2, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_CONIFER); + a_LogBlocks.push_back(sSetBlock(a_BlockX, Height, a_BlockZ, E_BLOCK_LOG, E_META_LOG_CONIFER)); + a_LogBlocks.push_back(sSetBlock(a_BlockX, Height + 1, a_BlockZ, E_BLOCK_LOG, E_META_LOG_CONIFER)); + a_LogBlocks.push_back(sSetBlock(a_BlockX, Height + 2, a_BlockZ, E_BLOCK_LOG, E_META_LOG_CONIFER)); + Height += 3; + break; + } + } // switch (SectionType) + MyRandom >>= 2; + } // for i - Sections + + if ((MyRandom & 1) == 0) + { + // (0, 1, 0) top: + a_LogBlocks.push_back (sSetBlock(a_BlockX, Height, a_BlockZ, E_BLOCK_LOG, E_META_LOG_CONIFER)); + PushCoordBlocks (a_BlockX, Height + 1, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_CONIFER); + a_OtherBlocks.push_back(sSetBlock(a_BlockX, Height + 1, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_CONIFER)); + a_OtherBlocks.push_back(sSetBlock(a_BlockX, Height + 2, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_CONIFER)); + } + else + { + // (1, 0) top: + a_OtherBlocks.push_back(sSetBlock(a_BlockX, Height, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_CONIFER)); + PushCoordBlocks (a_BlockX, Height + 1, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_CONIFER); + a_OtherBlocks.push_back(sSetBlock(a_BlockX, Height + 1, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_CONIFER)); + } +} + + + + + +void GetPineTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + // Tall, little leaves on top. The top leaves are arranged in a shape of two cones joined by their bases. + // There can be one or two layers representing the cone bases (SameSizeMax) + + int MyRandom = a_Noise.IntNoise3DInt(a_BlockX + 32 * a_Seq, a_BlockY, a_BlockZ + 32 * a_Seq) / 8; + int TrunkHeight = 8 + (MyRandom % 3); + int SameSizeMax = ((MyRandom & 8) == 0) ? 1 : 0; + MyRandom >>= 3; + int NumLeavesLayers = 2 + (MyRandom % 3); // Number of layers that have leaves in them + if (NumLeavesLayers == 2) + { + SameSizeMax = 0; + } + + // Pre-allocate the vector: + a_LogBlocks.reserve(TrunkHeight); + a_OtherBlocks.reserve(NumLeavesLayers * 25); + + // The entire trunk, out of logs: + for (int i = TrunkHeight; i >= 0; --i) + { + a_LogBlocks.push_back(sSetBlock(a_BlockX, a_BlockY + i, a_BlockZ, E_BLOCK_LOG, E_META_LOG_CONIFER)); + } + int h = a_BlockY + TrunkHeight + 2; + + // Top layer - just a single leaves block: + a_OtherBlocks.push_back(sSetBlock(a_BlockX, h, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_CONIFER)); + h--; + + // One more layer is above the trunk, push the central leaves: + a_OtherBlocks.push_back(sSetBlock(a_BlockX, h, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_CONIFER)); + + // Layers expanding in size, then collapsing again: + // LOGD("Generating %d layers of pine leaves, SameSizeMax = %d", NumLeavesLayers, SameSizeMax); + for (int i = 0; i < NumLeavesLayers; ++i) + { + int LayerSize = std::min(i, NumLeavesLayers - i + SameSizeMax - 1); + // LOGD("LayerSize %d: %d", i, LayerSize); + if (LayerSize < 0) + { + break; + } + ASSERT(LayerSize < ARRAYCOUNT(BigOs)); + PushCoordBlocks(a_BlockX, h, a_BlockZ, a_OtherBlocks, BigOs[LayerSize].Coords, BigOs[LayerSize].Count, E_BLOCK_LEAVES, E_META_LEAVES_CONIFER); + h--; + } +} + + + + + +void GetSwampTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + // Vines are around the BigO3, but not in the corners; need proper meta for direction + static const sMetaCoords Vines[] = + { + {-2, -4, 1}, {-1, -4, 1}, {0, -4, 1}, {1, -4, 1}, {2, -4, 1}, // North face + {-2, 4, 4}, {-1, 4, 4}, {0, 4, 4}, {1, 4, 4}, {2, 4, 4}, // South face + {4, -2, 2}, {4, -1, 2}, {4, 0, 2}, {4, 1, 2}, {4, 2, 2}, // East face + {-4, -2, 8}, {-4, -1, 8}, {-4, 0, 8}, {-4, 1, 8}, {-4, 2, 8}, // West face + } ; + + int Height = 3 + (a_Noise.IntNoise3DInt(a_BlockX + 32 * a_Seq, a_BlockY, a_BlockZ + 32 * a_Seq) / 8) % 3; + + a_LogBlocks.reserve(Height); + a_OtherBlocks.reserve(2 * ARRAYCOUNT(BigO2) + 2 * ARRAYCOUNT(BigO3) + Height * ARRAYCOUNT(Vines) + 20); + + for (int i = 0; i < Height; i++) + { + a_LogBlocks.push_back(sSetBlock(a_BlockX, a_BlockY + i, a_BlockZ, E_BLOCK_LOG, E_META_LOG_APPLE)); + } + int hei = a_BlockY + Height - 2; + + // Put vines around the lowermost leaves layer: + PushSomeColumns(a_BlockX, hei, a_BlockZ, Height, a_Seq, a_Noise, 0x3fffffff, a_OtherBlocks, Vines, ARRAYCOUNT(Vines), E_BLOCK_VINES); + + // The lower two leaves layers are BigO3 with log in the middle and possibly corners: + for (int i = 0; i < 2; i++) + { + PushCoordBlocks(a_BlockX, hei, a_BlockZ, a_OtherBlocks, BigO3, ARRAYCOUNT(BigO3), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + PushCornerBlocks(a_BlockX, hei, a_BlockZ, a_Seq, a_Noise, 0x5fffffff, a_OtherBlocks, 3, E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + hei++; + } // for i - 2* + + // The upper two leaves layers are BigO2 with leaves in the middle and possibly corners: + for (int i = 0; i < 2; i++) + { + PushCoordBlocks(a_BlockX, hei, a_BlockZ, a_OtherBlocks, BigO2, ARRAYCOUNT(BigO2), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + PushCornerBlocks(a_BlockX, hei, a_BlockZ, a_Seq, a_Noise, 0x5fffffff, a_OtherBlocks, 3, E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + a_OtherBlocks.push_back(sSetBlock(a_BlockX, hei, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_APPLE)); + hei++; + } // for i - 2* +} + + + + + +void GetAppleBushImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + a_OtherBlocks.reserve(3 + ARRAYCOUNT(BigO2) + ARRAYCOUNT(BigO1)); + + int hei = a_BlockY; + a_LogBlocks.push_back(sSetBlock(a_BlockX, hei, a_BlockZ, E_BLOCK_LOG, E_META_LOG_JUNGLE)); + PushCoordBlocks(a_BlockX, hei, a_BlockZ, a_OtherBlocks, BigO2, ARRAYCOUNT(BigO2), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + hei++; + + a_OtherBlocks.push_back(sSetBlock(a_BlockX, hei, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_APPLE)); + PushCoordBlocks(a_BlockX, hei, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_APPLE); + hei++; + + a_OtherBlocks.push_back(sSetBlock(a_BlockX, hei, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_APPLE)); +} + + + + + +void GetJungleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + if (a_Noise.IntNoise3DInt(a_BlockX + 32 * a_Seq, a_BlockY + 32 * a_Seq, a_BlockZ) < 0x60000000) + { + GetSmallJungleTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + } + else + { + GetLargeJungleTreeImage(a_BlockX, a_BlockY, a_BlockZ, a_Noise, a_Seq, a_LogBlocks, a_OtherBlocks); + } +} + + + + + +void GetLargeJungleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + // TODO: Generate proper jungle trees with branches + + // Vines are around the BigO4, but not in the corners; need proper meta for direction + static const sMetaCoords Vines[] = + { + {-2, -5, 1}, {-1, -5, 1}, {0, -5, 1}, {1, -5, 1}, {2, -5, 1}, // North face + {-2, 5, 4}, {-1, 5, 4}, {0, 5, 4}, {1, 5, 4}, {2, 5, 4}, // South face + {5, -2, 2}, {5, -1, 2}, {5, 0, 2}, {5, 1, 2}, {5, 2, 2}, // East face + {-5, -2, 8}, {-5, -1, 8}, {-5, 0, 8}, {-5, 1, 8}, {-5, 2, 8}, // West face + // TODO: vines around the trunk, proper metas and height + } ; + + int Height = 24 + (a_Noise.IntNoise3DInt(a_BlockX + 32 * a_Seq, a_BlockY, a_BlockZ + 32 * a_Seq) / 11) % 24; + + a_LogBlocks.reserve(Height * 4); + a_OtherBlocks.reserve(2 * ARRAYCOUNT(BigO4) + ARRAYCOUNT(BigO3) + Height * ARRAYCOUNT(Vines) + 50); + + for (int i = 0; i < Height; i++) + { + a_LogBlocks.push_back(sSetBlock(a_BlockX, a_BlockY + i, a_BlockZ, E_BLOCK_LOG, E_META_LOG_JUNGLE)); + a_LogBlocks.push_back(sSetBlock(a_BlockX + 1, a_BlockY + i, a_BlockZ, E_BLOCK_LOG, E_META_LOG_JUNGLE)); + a_LogBlocks.push_back(sSetBlock(a_BlockX, a_BlockY + i, a_BlockZ + 1, E_BLOCK_LOG, E_META_LOG_JUNGLE)); + a_LogBlocks.push_back(sSetBlock(a_BlockX + 1, a_BlockY + i, a_BlockZ + 1, E_BLOCK_LOG, E_META_LOG_JUNGLE)); + } + int hei = a_BlockY + Height - 2; + + // Put vines around the lowermost leaves layer: + PushSomeColumns(a_BlockX, hei, a_BlockZ, Height, a_Seq, a_Noise, 0x3fffffff, a_OtherBlocks, Vines, ARRAYCOUNT(Vines), E_BLOCK_VINES); + + // The lower two leaves layers are BigO4 with log in the middle and possibly corners: + for (int i = 0; i < 2; i++) + { + PushCoordBlocks(a_BlockX, hei, a_BlockZ, a_OtherBlocks, BigO4, ARRAYCOUNT(BigO4), E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE); + PushCornerBlocks(a_BlockX, hei, a_BlockZ, a_Seq, a_Noise, 0x5fffffff, a_OtherBlocks, 3, E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE); + hei++; + } // for i - 2* + + // The top leaves layer is a BigO3 with leaves in the middle and possibly corners: + PushCoordBlocks(a_BlockX, hei, a_BlockZ, a_OtherBlocks, BigO3, ARRAYCOUNT(BigO3), E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE); + PushCornerBlocks(a_BlockX, hei, a_BlockZ, a_Seq, a_Noise, 0x5fffffff, a_OtherBlocks, 3, E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE); + a_OtherBlocks.push_back(sSetBlock(a_BlockX, hei, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE)); +} + + + + + +void GetSmallJungleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks) +{ + // Vines are around the BigO3, but not in the corners; need proper meta for direction + static const sMetaCoords Vines[] = + { + {-2, -4, 1}, {-1, -4, 1}, {0, -4, 1}, {1, -4, 1}, {2, -4, 1}, // North face + {-2, 4, 4}, {-1, 4, 4}, {0, 4, 4}, {1, 4, 4}, {2, 4, 4}, // South face + {4, -2, 2}, {4, -1, 2}, {4, 0, 2}, {4, 1, 2}, {4, 2, 2}, // East face + {-4, -2, 8}, {-4, -1, 8}, {-4, 0, 8}, {-4, 1, 8}, // West face + // TODO: proper metas and height: {0, 1, 1}, {0, -1, 4}, {-1, 0, 2}, {1, 1, 8}, // Around the tunk + } ; + + int Height = 7 + (a_Noise.IntNoise3DInt(a_BlockX + 5 * a_Seq, a_BlockY, a_BlockZ + 5 * a_Seq) / 5) % 3; + + a_LogBlocks.reserve(Height); + a_OtherBlocks.reserve( + 2 * ARRAYCOUNT(BigO3) + // O3 layer, 2x + 2 * ARRAYCOUNT(BigO2) + // O2 layer, 2x + ARRAYCOUNT(BigO1) + 1 + // Plus on the top + Height * ARRAYCOUNT(Vines) + // Vines + 50 // some safety + ); + + for (int i = 0; i < Height; i++) + { + a_LogBlocks.push_back(sSetBlock(a_BlockX, a_BlockY + i, a_BlockZ, E_BLOCK_LOG, E_META_LOG_JUNGLE)); + } + int hei = a_BlockY + Height - 3; + + // Put vines around the lowermost leaves layer: + PushSomeColumns(a_BlockX, hei, a_BlockZ, Height, a_Seq, a_Noise, 0x3fffffff, a_OtherBlocks, Vines, ARRAYCOUNT(Vines), E_BLOCK_VINES); + + // The lower two leaves layers are BigO3 with log in the middle and possibly corners: + for (int i = 0; i < 2; i++) + { + PushCoordBlocks(a_BlockX, hei, a_BlockZ, a_OtherBlocks, BigO3, ARRAYCOUNT(BigO3), E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE); + PushCornerBlocks(a_BlockX, hei, a_BlockZ, a_Seq, a_Noise, 0x5fffffff, a_OtherBlocks, 3, E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE); + hei++; + } // for i - 2* + + // Two layers of BigO2 leaves, possibly with corners: + for (int i = 0; i < 1; i++) + { + PushCoordBlocks(a_BlockX, hei, a_BlockZ, a_OtherBlocks, BigO2, ARRAYCOUNT(BigO2), E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE); + PushCornerBlocks(a_BlockX, hei, a_BlockZ, a_Seq, a_Noise, 0x5fffffff, a_OtherBlocks, 2, E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE); + hei++; + } // for i - 2* + + // Top plus, all leaves: + PushCoordBlocks(a_BlockX, hei, a_BlockZ, a_OtherBlocks, BigO1, ARRAYCOUNT(BigO1), E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE); + a_OtherBlocks.push_back(sSetBlock(a_BlockX, hei, a_BlockZ, E_BLOCK_LEAVES, E_META_LEAVES_JUNGLE)); +} + + + + diff --git a/src/Generating/Trees.h b/src/Generating/Trees.h new file mode 100644 index 000000000..f5148ad6f --- /dev/null +++ b/src/Generating/Trees.h @@ -0,0 +1,93 @@ + +// Trees.h + +// Interfaces to helper functions used for generating trees + +/* +Note that all of these functions must generate the same tree image for the same input (x, y, z, seq) + - cStructGenTrees depends on this +To generate a random image for the (x, y, z) coords, pass an arbitrary value as (seq). +Each function returns two arrays of blocks, "logs" and "other". The point is that logs are of higher priority, +logs can overwrite others(leaves), but others shouldn't overwrite logs. This is an optimization for the generator. +*/ + + + + + +#pragma once + +#include "../ChunkDef.h" +#include "../Noise.h" + + + + + +// Blocks that don't block tree growth: +#define CASE_TREE_ALLOWED_BLOCKS \ + case E_BLOCK_AIR: \ + case E_BLOCK_LEAVES: \ + case E_BLOCK_SNOW: \ + case E_BLOCK_TALL_GRASS: \ + case E_BLOCK_DEAD_BUSH: \ + case E_BLOCK_SAPLING: \ + case E_BLOCK_VINES + +// Blocks that a tree may overwrite when growing: +#define CASE_TREE_OVERWRITTEN_BLOCKS \ + case E_BLOCK_AIR: \ + /* case E_BLOCK_LEAVES: LEAVES are a special case, they can be overwritten only by log. Handled in cChunkMap::ReplaceTreeBlocks(). */ \ + case E_BLOCK_SNOW: \ + case E_BLOCK_TALL_GRASS: \ + case E_BLOCK_DEAD_BUSH: \ + case E_BLOCK_SAPLING: \ + case E_BLOCK_VINES + + + + + +/// Generates an image of a tree at the specified coords (lowest trunk block) in the specified biome +void GetTreeImageByBiome(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, EMCSBiome a_Biome, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + +/// Generates an image of a random apple tree +void GetAppleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + +/// Generates an image of a small (nonbranching) apple tree +void GetSmallAppleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + +/// Generates an image of a large (branching) apple tree +void GetLargeAppleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + +/// Generates an image of a random birch tree +void GetBirchTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + +/// Generates an image of a random conifer tree +void GetConiferTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + +/// Generates an image of a random spruce (short conifer, two layers of leaves) +void GetSpruceTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + +/// Generates an image of a random pine (tall conifer, little leaves at top) +void GetPineTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + +/// Generates an image of a random swampland tree +void GetSwampTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + +/// Generates an image of a random apple bush (for jungles) +void GetAppleBushImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + +/// Generates an image of a random jungle tree +void GetJungleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + +/// Generates an image of a large jungle tree (2x2 trunk) +void GetLargeJungleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + +/// Generates an image of a small jungle tree (1x1 trunk) +void GetSmallJungleTreeImage(int a_BlockX, int a_BlockY, int a_BlockZ, cNoise & a_Noise, int a_Seq, sSetBlockVector & a_LogBlocks, sSetBlockVector & a_OtherBlocks); + + + + + |