summaryrefslogtreecommitdiffstats
path: root/source/Generating
diff options
context:
space:
mode:
Diffstat (limited to 'source/Generating')
-rw-r--r--source/Generating/BioGen.cpp304
-rw-r--r--source/Generating/BioGen.h171
-rw-r--r--source/Generating/Caves.cpp943
-rw-r--r--source/Generating/Caves.h120
-rw-r--r--source/Generating/ChunkGenerator.cpp596
-rw-r--r--source/Generating/ChunkGenerator.h226
-rw-r--r--source/Generating/CompoGen.cpp427
-rw-r--r--source/Generating/CompoGen.h154
-rw-r--r--source/Generating/FinishGen.cpp387
-rw-r--r--source/Generating/FinishGen.h152
-rw-r--r--source/Generating/HeiGen.cpp302
-rw-r--r--source/Generating/HeiGen.h137
-rw-r--r--source/Generating/Ravines.cpp514
-rw-r--r--source/Generating/Ravines.h53
-rw-r--r--source/Generating/StructGen.cpp415
-rw-r--r--source/Generating/StructGen.h112
-rw-r--r--source/Generating/Trees.cpp684
-rw-r--r--source/Generating/Trees.h93
18 files changed, 5790 insertions, 0 deletions
diff --git a/source/Generating/BioGen.cpp b/source/Generating/BioGen.cpp
new file mode 100644
index 000000000..a43d995df
--- /dev/null
+++ b/source/Generating/BioGen.cpp
@@ -0,0 +1,304 @@
+
+// BioGen.cpp
+
+// Implements the various biome generators
+
+#include "Globals.h"
+#include "BioGen.h"
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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;
+ }
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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;
+ delete m_BioGenToCache;
+}
+
+
+
+
+
+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;
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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)
+ {
+ EMCSBiome Biome = StringToBiome(*itr);
+ if (Biome != -1)
+ {
+ m_Biomes.push_back(Biome);
+ }
+ } // 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;
+ for (int x = 0; x < cChunkDef::Width; x++)
+ {
+ int Add = cChunkDef::Width * a_ChunkX + x;
+ a_BiomeMap[x + cChunkDef::Width * z] = m_Biomes[(Base / m_BiomeSize + Add / m_BiomeSize) % m_BiomesCount];
+ }
+ }
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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++)
+ {
+ cChunkDef::SetBiome(a_BiomeMap, x, z, VoronoiBiome(BaseX + x, AbsoluteZ));
+ } // for x
+ } // for z
+}
+
+
+
+
+
+EMCSBiome cBioGenVoronoi::VoronoiBiome(int a_BlockX, int a_BlockZ)
+{
+ int CellX = a_BlockX / m_CellSize;
+ int CellZ = a_BlockZ / m_CellSize;
+
+ // Note that Noise values need to be divided by 8 to gain a uniform modulo-2^n distribution
+
+ // Get 5x5 neighboring cell seeds, compare distance to each. Return the biome in the minumim-distance cell
+ int MinDist = m_CellSize * m_CellSize * 16; // There has to be a cell closer than this
+ EMCSBiome res = biPlains; // Will be overriden
+ for (int x = CellX - 2; x <= CellX + 2; x++)
+ {
+ int BaseX = x * m_CellSize;
+ for (int z = CellZ - 2; z < CellZ + 2; z++)
+ {
+ int OffsetX = (m_Noise.IntNoise3DInt(x, 16 * x + 32 * z, z) / 8) % m_CellSize;
+ int OffsetZ = (m_Noise.IntNoise3DInt(x, 32 * x - 16 * z, z) / 8) % m_CellSize;
+ int SeedX = BaseX + OffsetX;
+ int SeedZ = z * m_CellSize + OffsetZ;
+
+ int Dist = (SeedX - a_BlockX) * (SeedX - a_BlockX) + (SeedZ - a_BlockZ) * (SeedZ - a_BlockZ);
+ if (Dist < MinDist)
+ {
+ MinDist = Dist;
+ res = m_Biomes[(m_Noise.IntNoise3DInt(x, x - z + 1000, z) / 8) % m_BiomesCount];
+ }
+ } // for z
+ } // for x
+
+ return res;
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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]);
+ }
+
+ IntArrayLinearInterpolate2D(&DistortX[0][0], cChunkDef::Width + 1, cChunkDef::Width + 1, 4, 4);
+ IntArrayLinearInterpolate2D(&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++)
+ {
+ // Distort(BaseX + x, AbsoluteZ, DistX, DistZ);
+ cChunkDef::SetBiome(a_BiomeMap, x, z, VoronoiBiome(DistortX[x][z], DistortZ[x][z]));
+ } // for x
+ } // for z
+}
+
+
+
+
+
+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);
+}
+
+
+
+
+
diff --git a/source/Generating/BioGen.h b/source/Generating/BioGen.h
new file mode 100644
index 000000000..6cd78b7e0
--- /dev/null
+++ b/source/Generating/BioGen.h
@@ -0,0 +1,171 @@
+
+// BioGen.h
+
+/*
+Interfaces to the various biome generators:
+ - cBioGenConstant
+ - cBioGenCheckerboard
+ - cBioGenDistortedVoronoi
+*/
+
+
+
+
+
+#pragma once
+
+#include "ChunkGenerator.h"
+#include "../cNoise.h"
+
+
+
+
+
+class cBioGenConstant :
+ public cBiomeGen
+{
+public:
+ cBioGenConstant(EMCSBiome a_Biome) : m_Biome(a_Biome) {}
+
+protected:
+
+ EMCSBiome m_Biome;
+
+ // cBiomeGen override:
+ virtual void GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override;
+} ;
+
+
+
+
+
+/// A simple cache that stores N most recently generated chunks' biomes; N being settable upon creation
+class cBioGenCache :
+ public cBiomeGen
+{
+public:
+ cBioGenCache(cBiomeGen * a_BioGenToCache, int a_CacheSize); // Takes 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;
+} ;
+
+
+
+
+
+/// Base class for generators that use a list of available biomes. This class takes care of the list.
+class cBiomeGenList :
+ public cBiomeGen
+{
+protected:
+ cBiomeGenList(const AString & a_Biomes)
+ {
+ InitializeBiomes(a_Biomes);
+ }
+
+ // 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
+
+ void InitializeBiomes(const AString & a_Biomes);
+
+} ;
+
+
+
+
+
+class cBioGenCheckerboard :
+ public cBiomeGenList
+{
+public:
+ cBioGenCheckerboard(int a_BiomeSize, const AString & a_Biomes) :
+ cBiomeGenList(a_Biomes),
+ m_BiomeSize((a_BiomeSize < 8) ? 8 : a_BiomeSize)
+ {
+ }
+
+protected:
+
+ int m_BiomeSize;
+
+ // cBiomeGen override:
+ virtual void GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override;
+} ;
+
+
+
+
+
+class cBioGenVoronoi :
+ public cBiomeGenList
+{
+public:
+ cBioGenVoronoi(int a_Seed, int a_CellSize, const AString & a_Biomes) :
+ cBiomeGenList(a_Biomes),
+ m_CellSize((a_CellSize > 4) ? a_CellSize : 4),
+ m_Noise(a_Seed)
+ {
+ }
+
+protected:
+
+ int m_CellSize;
+
+ cNoise m_Noise;
+
+ // cBiomeGen override:
+ virtual void GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override;
+
+ EMCSBiome VoronoiBiome(int a_BlockX, int a_BlockZ);
+} ;
+
+
+
+
+
+class cBioGenDistortedVoronoi :
+ public cBioGenVoronoi
+{
+public:
+ cBioGenDistortedVoronoi(int a_Seed, int a_CellSize, const AString & a_Biomes) :
+ cBioGenVoronoi(a_Seed, a_CellSize, a_Biomes)
+ {
+ }
+
+protected:
+
+ // cBiomeGen override:
+ virtual void GenBiomes(int a_ChunkX, int a_ChunkZ, cChunkDef::BiomeMap & a_BiomeMap) override;
+
+ /// Distorts the coords using a Perlin-like noise
+ void Distort(int a_BlockX, int a_BlockZ, int & a_DistortedX, int & a_DistortedZ);
+} ;
+
+
+
+
+
diff --git a/source/Generating/Caves.cpp b/source/Generating/Caves.cpp
new file mode 100644
index 000000000..f334023ce
--- /dev/null
+++ b/source/Generating/Caves.cpp
@@ -0,0 +1,943 @@
+
+// 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 nosie 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
+
+
+
+
+
+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
+ );
+} ;
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 = (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 err = dx - dz;
+ 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) ||
+ (BaseX > m_MaxBlockX) || (BaseX + cChunkDef::Width < m_MinBlockX)
+ )
+ {
+ // 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:
+ 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 - itr->m_Radius, 1);
+ int Top = std::min(itr->m_BlockY + itr->m_Radius, (int)(cChunkDef::Height)); // Stupid gcc needs int cast
+ 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 (6 * SqDist <= SqRad)
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, y, z, E_BLOCK_AIR);
+ }
+ } // 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 = 2 + (a_Noise.IntNoise3DInt(a_OriginX + a_OriginY, a_OriginY + a_OriginZ, a_OriginZ + a_OriginX) / 11) % 10;
+ 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 = 2 + (a_Noise.IntNoise3DInt(a_OriginX + 7 * i, a_OriginY + 11 * i, a_OriginZ + a_OriginX) / 11) % 10;
+ 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
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+)
+{
+ cCaveSystems Caves;
+ GetCavesForChunk(a_ChunkX, a_ChunkZ, Caves);
+ for (cCaveSystems::const_iterator itr = Caves.begin(); itr != Caves.end(); ++itr)
+ {
+ (*itr)->ProcessChunk(a_ChunkX, a_ChunkZ, a_BlockTypes, a_HeightMap);
+ } // 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(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+)
+{
+ cNoise Noise(m_Seed);
+ 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 Top = cChunkDef::GetHeight(a_HeightMap, x, z);
+ for (int y = 1; y < Top; ++y )
+ {
+ if (cChunkDef::GetBlock(a_BlockTypes, 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)
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, y, z, E_BLOCK_AIR);
+ }
+ } // for y
+ } // for x
+ } // for z
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cStructGenDualRidgeCaves:
+
+void cStructGenDualRidgeCaves::GenStructures(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+)
+{
+ cNoise Noise1(m_Seed);
+ cNoise Noise2(2 * m_Seed + 19999);
+ for (int z = 0; z < cChunkDef::Width; z++)
+ {
+ const float zz = (float)(a_ChunkZ * cChunkDef::Width + z) / 10;
+ for (int x = 0; x < cChunkDef::Width; x++)
+ {
+ const float xx = (float)(a_ChunkX * cChunkDef::Width + x) / 10;
+
+ int Top = cChunkDef::GetHeight(a_HeightMap, x, z);
+ for (int y = 1; y <= Top; ++y)
+ {
+ /*
+ if (cChunkDef::GetBlock(a_BlockTypes, x, y, z) != E_BLOCK_STONE)
+ {
+ continue;
+ }
+ */
+
+ const float yy = (float)y / 10;
+ const float WaveNoise = 1;
+ float n1 = Noise1.CubicNoise3D(xx, yy, zz);
+ float n2 = Noise2.CubicNoise3D(xx, yy, zz);
+ float n3 = Noise1.CubicNoise3D(xx * 4, yy * 4, zz * 4) / 4;
+ float n4 = Noise2.CubicNoise3D(xx * 4, yy * 4, zz * 4) / 4;
+ if ((abs(n1 + n3) * abs(n2 + n4)) > m_Threshold)
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, y, z, E_BLOCK_AIR);
+ }
+ } // for y
+ } // for x
+ } // for z
+}
+
+
+
+
diff --git a/source/Generating/Caves.h b/source/Generating/Caves.h
new file mode 100644
index 000000000..cc3f00a52
--- /dev/null
+++ b/source/Generating/Caves.h
@@ -0,0 +1,120 @@
+
+// Caves.h
+
+// Interfaces to the various cave structure generators:
+// - cStructGenWormNestCaves
+// - cStructGenMarbleCaves
+// - cStructGenNetherCaves
+
+
+
+
+
+#pragma once
+
+#include "ChunkGenerator.h"
+#include "../cNoise.h"
+
+
+
+
+
+class cStructGenMarbleCaves :
+ public cStructureGen
+{
+public:
+ cStructGenMarbleCaves(int a_Seed) : m_Seed(a_Seed) {}
+
+protected:
+
+ int m_Seed;
+
+ // cStructureGen override:
+ virtual void GenStructures(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) override;
+} ;
+
+
+
+
+
+class cStructGenDualRidgeCaves :
+ public cStructureGen
+{
+public:
+ cStructGenDualRidgeCaves(int a_Seed, float a_Threshold) :
+ m_Seed(a_Seed),
+ m_Threshold(a_Threshold)
+ {
+ }
+
+protected:
+
+ int m_Seed;
+ float m_Threshold;
+
+ // cStructureGen override:
+ virtual void GenStructures(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) 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(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) override;
+} ;
+
+
+
+
diff --git a/source/Generating/ChunkGenerator.cpp b/source/Generating/ChunkGenerator.cpp
new file mode 100644
index 000000000..5862c0f5f
--- /dev/null
+++ b/source/Generating/ChunkGenerator.cpp
@@ -0,0 +1,596 @@
+
+#include "Globals.h"
+
+#include "ChunkGenerator.h"
+#include "../cWorld.h"
+#include "../../iniFile/iniFile.h"
+#include "BioGen.h"
+#include "HeiGen.h"
+#include "CompoGen.h"
+#include "StructGen.h"
+#include "FinishGen.h"
+#include "../cRoot.h"
+#include "../cPluginManager.h"
+#include "../cLuaChunk.h"
+#include "Ravines.h"
+#include "Caves.h"
+
+
+
+
+
+/// If the generation queue size exceeds this number, a warning will be output
+const int QUEUE_WARNING_LIMIT = 1000;
+
+/// If the generation queue size exceeds this number, chunks with no clients will be skipped
+const int QUEUE_SKIP_LIMIT = 500;
+
+
+
+
+
+static BLOCKTYPE 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;
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cChunkGenerator:
+
+cChunkGenerator::cChunkGenerator(void)
+ : super("cChunkGenerator")
+ , m_World(NULL)
+ , m_BiomeGen(NULL)
+ , m_HeightGen(NULL)
+ , m_CompositionGen(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());
+
+ InitBiomeGen(a_IniFile);
+ InitHeightGen(a_IniFile);
+ InitCompositionGen(a_IniFile);
+ InitStructureGens(a_IniFile);
+ InitFinishGens(a_IniFile);
+
+ a_IniFile.WriteFile();
+
+ 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 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;
+}
+
+
+
+
+
+void cChunkGenerator::InitBiomeGen(cIniFile & a_IniFile)
+{
+ AString BiomeGenName = a_IniFile.GetValueSet("Generator", "BiomeGen", "");
+ if (BiomeGenName.empty())
+ {
+ LOGWARN("[Generator]::BiomeGen value not found in world.ini, using \"DistortedVoronoi\".");
+ BiomeGenName = "DistortedVoronoi";
+ }
+
+ bool CacheOffByDefault = false;
+ if (NoCaseCompare(BiomeGenName, "constant") == 0)
+ {
+ AString Biome = a_IniFile.GetValueSet("Generator", "ConstantBiome", "Plains");
+ EMCSBiome b = StringToBiome(Biome);
+ if (b == -1)
+ {
+ LOGWARN("[Generator]::ConstantBiome value \"%s\" not recognized, using \"Plains\".", Biome.c_str());
+ b = biPlains;
+ }
+ m_BiomeGen = new cBioGenConstant(b);
+ CacheOffByDefault = true; // we're generating faster than a cache would retrieve data :)
+ }
+ else if (NoCaseCompare(BiomeGenName, "checkerboard") == 0)
+ {
+ int BiomeSize = a_IniFile.GetValueSetI("Generator", "CheckerboardBiomeSize", 64);
+ AString Biomes = a_IniFile.GetValueSet ("Generator", "CheckerBoardBiomes", "");
+ m_BiomeGen = new cBioGenCheckerboard(BiomeSize, Biomes);
+ CacheOffByDefault = true; // we're (probably) generating faster than a cache would retrieve data
+ }
+ else if (NoCaseCompare(BiomeGenName, "voronoi") == 0)
+ {
+ int CellSize = a_IniFile.GetValueSetI("Generator", "VoronoiCellSize", 64);
+ AString Biomes = a_IniFile.GetValueSet ("Generator", "VoronoiBiomes", "");
+ m_BiomeGen = new cBioGenVoronoi(m_Seed, CellSize, Biomes);
+ }
+ else
+ {
+ if (NoCaseCompare(BiomeGenName, "distortedvoronoi") != 0)
+ {
+ LOGWARNING("Unknown BiomeGen \"%s\", using \"DistortedVoronoi\" instead.", BiomeGenName.c_str());
+ }
+ int CellSize = a_IniFile.GetValueSetI("Generator", "DistortedVoronoiCellSize", 96);
+ AString Biomes = a_IniFile.GetValueSet ("Generator", "DistortedVoronoiBiomes", "");
+ m_BiomeGen = new cBioGenDistortedVoronoi(m_Seed, CellSize, Biomes);
+ }
+
+ // 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;
+ }
+ LOGINFO("Using a cache for biomegen of size %d.", CacheSize);
+ m_BiomeGen = new cBioGenCache(m_BiomeGen, CacheSize);
+ }
+}
+
+
+
+
+
+void cChunkGenerator::InitHeightGen(cIniFile & a_IniFile)
+{
+ AString HeightGenName = a_IniFile.GetValueSet("Generator", "HeightGen", "");
+ if (HeightGenName.empty())
+ {
+ LOGWARN("[Generator]::HeightGen value not found in world.ini, using \"Biomal\".");
+ HeightGenName = "Biomal";
+ }
+
+ bool CacheOffByDefault = false;
+ if (NoCaseCompare(HeightGenName, "flat") == 0)
+ {
+ int Height = a_IniFile.GetValueSetI("Generator", "FlatHeight", 5);
+ m_HeightGen = new cHeiGenFlat(Height);
+ CacheOffByDefault = true; // We're generating faster than a cache would retrieve data
+ }
+ else if (NoCaseCompare(HeightGenName, "classic") == 0)
+ {
+ // These used to be in terrain.ini, but now they are in world.ini (so that multiple worlds can have different values):
+ float HeightFreq1 = (float)a_IniFile.GetValueSetF("Generator", "ClassicHeightFreq1", 0.1);
+ float HeightFreq2 = (float)a_IniFile.GetValueSetF("Generator", "ClassicHeightFreq2", 1.0);
+ float HeightFreq3 = (float)a_IniFile.GetValueSetF("Generator", "ClassicHeightFreq3", 2.0);
+ float HeightAmp1 = (float)a_IniFile.GetValueSetF("Generator", "ClassicHeightAmp1", 1.0);
+ float HeightAmp2 = (float)a_IniFile.GetValueSetF("Generator", "ClassicHeightAmp2", 0.5);
+ float HeightAmp3 = (float)a_IniFile.GetValueSetF("Generator", "ClassicHeightAmp3", 0.5);
+ m_HeightGen = new cHeiGenClassic(m_Seed, HeightFreq1, HeightAmp1, HeightFreq2, HeightAmp2, HeightFreq3, HeightAmp3);
+ }
+ else // "biomal" or <not found>
+ {
+ if (NoCaseCompare(HeightGenName, "biomal") != 0)
+ {
+ LOGWARN("Unknown HeightGen \"%s\", using \"Biomal\" instead.", HeightGenName.c_str());
+ }
+ m_HeightGen = new cHeiGenBiomal(m_Seed, *m_BiomeGen);
+ }
+
+ // 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;
+ }
+ LOGINFO("Using a cache for Heightgen of size %d.", CacheSize);
+ m_HeightGen = new cHeiGenCache(m_HeightGen, CacheSize);
+ }
+}
+
+
+
+
+
+void cChunkGenerator::InitCompositionGen(cIniFile & a_IniFile)
+{
+ AString CompoGenName = a_IniFile.GetValueSet("Generator", "CompositionGen", "");
+ if (CompoGenName.empty())
+ {
+ LOGWARN("[Generator]::CompositionGen value not found in world.ini, using \"Biomal\".");
+ CompoGenName = "Biomal";
+ }
+ if (NoCaseCompare(CompoGenName, "sameblock") == 0)
+ {
+ int Block = GetIniBlock(a_IniFile, "Generator", "SameBlockType", "stone");
+ bool Bedrocked = (a_IniFile.GetValueSetI("Generator", "SameBlockBedrocked", 1) != 0);
+ m_CompositionGen = new cCompoGenSameBlock((BLOCKTYPE)Block, Bedrocked);
+ }
+ else if (NoCaseCompare(CompoGenName, "debugbiomes") == 0)
+ {
+ m_CompositionGen = new cCompoGenDebugBiomes;
+ }
+ else if (NoCaseCompare(CompoGenName, "classic") == 0)
+ {
+ int SeaLevel = a_IniFile.GetValueSetI("Generator", "ClassicSeaLevel", 60);
+ int BeachHeight = a_IniFile.GetValueSetI("Generator", "ClassicBeachHeight", 2);
+ int BeachDepth = a_IniFile.GetValueSetI("Generator", "ClassicBeachDepth", 4);
+ BLOCKTYPE BlockTop = GetIniBlock(a_IniFile, "Generator", "ClassicBlockTop", "grass");
+ BLOCKTYPE BlockMiddle = GetIniBlock(a_IniFile, "Generator", "ClassicBlockMiddle", "dirt");
+ BLOCKTYPE BlockBottom = GetIniBlock(a_IniFile, "Generator", "ClassicBlockBottom", "stone");
+ BLOCKTYPE BlockBeach = GetIniBlock(a_IniFile, "Generator", "ClassicBlockBeach", "sand");
+ BLOCKTYPE BlockBeachBottom = GetIniBlock(a_IniFile, "Generator", "ClassicBlockBeachBottom", "sandstone");
+ BLOCKTYPE BlockSea = GetIniBlock(a_IniFile, "Generator", "ClassicBlockSea", "9");
+ m_CompositionGen = new cCompoGenClassic(
+ SeaLevel, BeachHeight, BeachDepth, BlockTop, BlockMiddle, BlockBottom, BlockBeach,
+ BlockBeachBottom, BlockSea
+ );
+ }
+ else
+ {
+ if (NoCaseCompare(CompoGenName, "biomal") != 0)
+ {
+ LOGWARN("Unknown CompositionGen \"%s\", using \"biomal\" instead.", CompoGenName.c_str());
+ }
+ int SeaLevel = a_IniFile.GetValueSetI("Generator", "BiomalSeaLevel", 62);
+ m_CompositionGen = new cCompoGenBiomal(m_Seed, SeaLevel);
+ }
+}
+
+
+
+
+
+void cChunkGenerator::InitStructureGens(cIniFile & a_IniFile)
+{
+ AString Structures = a_IniFile.GetValueSet("Generator", "Structures", "Trees,MarbleCaves,OreNests");
+
+ AStringVector Str = StringSplit(Structures, ",");
+ for (AStringVector::const_iterator itr = Str.begin(); itr != Str.end(); ++itr)
+ {
+ if (NoCaseCompare(*itr, "trees") == 0)
+ {
+ m_StructureGens.push_back(new cStructGenTrees(m_Seed, m_BiomeGen, m_HeightGen, m_CompositionGen));
+ }
+ else if (NoCaseCompare(*itr, "marblecaves") == 0)
+ {
+ m_StructureGens.push_back(new cStructGenMarbleCaves(m_Seed));
+ }
+ else if (NoCaseCompare(*itr, "dualridgecaves") == 0)
+ {
+ float Threshold = (float)a_IniFile.GetValueSetF("Generator", "DualRidgeCavesThreshold", 0.3);
+ m_StructureGens.push_back(new cStructGenDualRidgeCaves(m_Seed, Threshold));
+ }
+ else if (NoCaseCompare(*itr, "orenests") == 0)
+ {
+ m_StructureGens.push_back(new cStructGenOreNests(m_Seed));
+ }
+ else if (NoCaseCompare(*itr, "ravines") == 0)
+ {
+ m_StructureGens.push_back(new cStructGenRavines(m_Seed, 128));
+ }
+ //*
+ // TODO: Not implemented yet; need a name
+ else if (NoCaseCompare(*itr, "wormnestcaves") == 0)
+ {
+ m_StructureGens.push_back(new cStructGenWormNestCaves(m_Seed));
+ }
+ //*/
+ else
+ {
+ LOGWARNING("Unknown structure generator: \"%s\". Ignoring.", itr->c_str());
+ }
+ } // for itr - Str[]
+}
+
+
+
+
+
+void cChunkGenerator::InitFinishGens(cIniFile & a_IniFile)
+{
+ AString Structures = a_IniFile.GetValueSet("Generator", "Finishers", "SprinkleFoliage,Ice,Snow,Lilypads,BottomLava");
+
+ AStringVector Str = StringSplit(Structures, ",");
+ for (AStringVector::const_iterator itr = Str.begin(); itr != Str.end(); ++itr)
+ {
+ if (NoCaseCompare(*itr, "SprinkleFoliage") == 0)
+ {
+ m_FinishGens.push_back(new cFinishGenSprinkleFoliage(m_Seed));
+ }
+ else if (NoCaseCompare(*itr, "Snow") == 0)
+ {
+ m_FinishGens.push_back(new cFinishGenSnow);
+ }
+ else if (NoCaseCompare(*itr, "Ice") == 0)
+ {
+ m_FinishGens.push_back(new cFinishGenIce);
+ }
+ else if (NoCaseCompare(*itr, "Lilypads") == 0)
+ {
+ m_FinishGens.push_back(new cFinishGenLilypads(m_Seed));
+ }
+ else if (NoCaseCompare(*itr, "BottomLava") == 0)
+ {
+ int BottomLavaLevel = a_IniFile.GetValueSetI("Generator", "BottomLavaLevel", 10);
+ m_FinishGens.push_back(new cFinishGenBottomLava(BottomLavaLevel));
+ }
+ } // for itr - Str[]
+}
+
+
+
+
+
+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_BiomeGen != NULL) // Quick fix for generator deinitializing before the world storage finishes loading
+ {
+ m_BiomeGen->GenBiomes(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)
+{
+ cChunkDef::BiomeMap Biomes;
+ int Y = 0;
+ int ChunkX, ChunkZ;
+ cWorld::AbsoluteToRelative(a_BlockX, Y, a_BlockZ, ChunkX, Y, ChunkZ);
+ m_BiomeGen->GenBiomes(ChunkX, ChunkZ, Biomes);
+ return cChunkDef::GetBiome(Biomes, a_BlockX, a_BlockZ);
+}
+
+
+
+
+
+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_ChunkY, 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_ChunkY, coords.m_ChunkZ))
+ {
+ LOGWARNING("Chunk generator overloaded, skipping chunk [%d, %d, %d]", coords.m_ChunkX, coords.m_ChunkY, 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)
+{
+ cChunkDef::BiomeMap BiomeMap;
+ cChunkDef::BlockTypes BlockTypes;
+ cChunkDef::BlockNibbles BlockMeta;
+ cChunkDef::HeightMap HeightMap;
+ cEntityList Entities;
+ cBlockEntityList BlockEntities;
+
+ cLuaChunk LuaChunk( BlockTypes, BlockMeta, HeightMap, BiomeMap );
+ if( cRoot::Get()->GetPluginManager()->CallHookChunkGenerating(m_World, a_ChunkX, a_ChunkZ, &LuaChunk ) )
+ {
+ // A plugin interrupted generation, handle something plugin specific
+ if( LuaChunk.IsUsingDefaultBiomes() )
+ {
+ m_BiomeGen->GenBiomes(a_ChunkX, a_ChunkZ, BiomeMap);
+ }
+ if( LuaChunk.IsUsingDefaultComposition() )
+ {
+ m_CompositionGen->ComposeTerrain(a_ChunkX, a_ChunkZ, BlockTypes, BlockMeta, HeightMap, BiomeMap, Entities, BlockEntities);
+ }
+ if( LuaChunk.IsUsingDefaultStructures() )
+ {
+ for (cStructureGenList::iterator itr = m_StructureGens.begin(); itr != m_StructureGens.end(); ++itr)
+ {
+ (*itr)->GenStructures(a_ChunkX, a_ChunkZ, BlockTypes, BlockMeta, HeightMap, Entities, BlockEntities);
+ } // for itr - m_StructureGens[]
+ }
+ if( LuaChunk.IsUsingDefaultFinish() )
+ {
+ for (cFinishGenList::iterator itr = m_FinishGens.begin(); itr != m_FinishGens.end(); ++itr)
+ {
+ (*itr)->GenFinish(a_ChunkX, a_ChunkZ, BlockTypes, BlockMeta, HeightMap, BiomeMap, Entities, BlockEntities);
+ } // for itr - m_FinishGens[]
+ }
+ }
+ else
+ {
+ // Use the composed generator:
+ m_BiomeGen->GenBiomes(a_ChunkX, a_ChunkZ, BiomeMap);
+ m_HeightGen->GenHeightMap(a_ChunkX, a_ChunkZ, HeightMap);
+ m_CompositionGen->ComposeTerrain(a_ChunkX, a_ChunkZ, BlockTypes, BlockMeta, HeightMap, BiomeMap, Entities, BlockEntities);
+ for (cStructureGenList::iterator itr = m_StructureGens.begin(); itr != m_StructureGens.end(); ++itr)
+ {
+ (*itr)->GenStructures(a_ChunkX, a_ChunkZ, BlockTypes, BlockMeta, HeightMap, Entities, BlockEntities);
+ } // for itr - m_StructureGens[]
+ for (cFinishGenList::iterator itr = m_FinishGens.begin(); itr != m_FinishGens.end(); ++itr)
+ {
+ (*itr)->GenFinish(a_ChunkX, a_ChunkZ, BlockTypes, BlockMeta, HeightMap, BiomeMap, Entities, BlockEntities);
+ } // for itr - m_FinishGens[]
+ }
+
+ m_World->SetChunkData(
+ a_ChunkX, a_ChunkY, a_ChunkZ,
+ BlockTypes, BlockMeta,
+ NULL, NULL, // We don't have lighting, chunk will be lighted when needed
+ &HeightMap, &BiomeMap,
+ Entities, BlockEntities,
+ true
+ );
+
+ cRoot::Get()->GetPluginManager()->CallHook(cPluginManager::E_PLUGIN_CHUNK_GENERATED, 3, m_World, a_ChunkX, a_ChunkZ);
+}
+
+
+
+
diff --git a/source/Generating/ChunkGenerator.h b/source/Generating/ChunkGenerator.h
new file mode 100644
index 000000000..23cf76751
--- /dev/null
+++ b/source/Generating/ChunkGenerator.h
@@ -0,0 +1,226 @@
+
+// cChunkGenerator.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
+
+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 "../cIsThread.h"
+#include "../ChunkDef.h"
+
+
+
+
+
+// fwd:
+class cWorld;
+class cIniFile;
+
+// TODO: remove this:
+class cWorldGenerator;
+
+
+
+
+
+/** 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;
+} ;
+
+
+
+
+
+/** 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;
+} ;
+
+
+
+
+
+/** 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(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // BlockTypes to be generated (the whole array gets initialized, even air)
+ cChunkDef::BlockNibbles & a_BlockMeta, // BlockMetas to be generated (the whole array gets initialized)
+ const cChunkDef::HeightMap & a_HeightMap, // The height map to fit
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entitites may be generated along with the terrain
+ cBlockEntityList & a_BlockEntities // Block entitites may be generated (chests / furnaces / ...)
+ ) = 0;
+} ;
+
+
+
+
+
+/** 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(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) = 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(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) = 0;
+} ;
+
+typedef std::list<cFinishGen *> cFinishGenList;
+
+
+
+
+
+/// The chunk generator itself
+class cChunkGenerator :
+ cIsThread
+{
+ typedef cIsThread super;
+
+public:
+
+ cChunkGenerator (void);
+ ~cChunkGenerator();
+
+ bool Start(cWorld * a_World, cIniFile & a_IniFile);
+ void Stop(void);
+
+ void QueueGenerateChunk(int a_ChunkX, int a_ChunkY, int a_ChunkZ); // Queues the chunk for generation; removes duplicate requests
+
+ /// Generates the biomes for the specified chunk (directly, not in a separate thread)
+ 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; }
+
+ EMCSBiome GetBiomeAt(int a_BlockX, int a_BlockZ);
+
+private:
+
+ cWorld * m_World;
+
+ // The generation composition:
+ cBiomeGen * m_BiomeGen;
+ cTerrainHeightGen * m_HeightGen;
+ cTerrainCompositionGen * m_CompositionGen;
+ cStructureGenList m_StructureGens;
+ cFinishGenList m_FinishGens;
+
+ 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
+
+ /// 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);
+
+ // cIsThread override:
+ virtual void Execute(void) override;
+
+ void DoGenerate(int a_ChunkX, int a_ChunkY, int a_ChunkZ);
+};
+
+
+
+
diff --git a/source/Generating/CompoGen.cpp b/source/Generating/CompoGen.cpp
new file mode 100644
index 000000000..046310d91
--- /dev/null
+++ b/source/Generating/CompoGen.cpp
@@ -0,0 +1,427 @@
+
+// CompoGen.cpp
+
+/* Implements the various terrain composition generators:
+ - cCompoGenSameBlock
+ - cCompoGenDebugBiomes
+ - cCompoGenClassic
+*/
+
+#include "Globals.h"
+#include "CompoGen.h"
+#include "../BlockID.h"
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cCompoGenSameBlock:
+
+void cCompoGenSameBlock::ComposeTerrain(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // BlockTypes to be generated
+ cChunkDef::BlockNibbles & a_BlockMeta, // BlockMetas to be generated
+ const cChunkDef::HeightMap & a_HeightMap, // The height map to fit
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entitites may be generated along with the terrain
+ cBlockEntityList & a_BlockEntities // Block entitites may be generated (chests / furnaces / ...)
+)
+{
+ memset(a_BlockTypes, E_BLOCK_AIR, sizeof(a_BlockTypes));
+ memset(a_BlockMeta, 0, sizeof(a_BlockMeta));
+ for (int z = 0; z < cChunkDef::Width; z++)
+ {
+ for (int x = 0; x < cChunkDef::Width; x++)
+ {
+ int Start;
+ if (m_IsBedrocked)
+ {
+ a_BlockTypes[cChunkDef::MakeIndex(x, 0, z)] = E_BLOCK_BEDROCK;
+ Start = 1;
+ }
+ else
+ {
+ Start = 0;
+ }
+ for (int y = a_HeightMap[x + cChunkDef::Width * z]; y >= Start; y--)
+ {
+ a_BlockTypes[cChunkDef::MakeIndex(x, y, z)] = m_BlockType;
+ } // for y
+ } // for z
+ } // for x
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cCompoGenDebugBiomes:
+
+void cCompoGenDebugBiomes::ComposeTerrain(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // BlockTypes to be generated
+ cChunkDef::BlockNibbles & a_BlockMeta, // BlockMetas to be generated
+ const cChunkDef::HeightMap & a_HeightMap, // The height map to fit
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entitites may be generated along with the terrain
+ cBlockEntityList & a_BlockEntities // Block entitites may be generated (chests / furnaces / ...)
+)
+{
+ static BLOCKTYPE Blocks[] =
+ {
+ E_BLOCK_STONE,
+ E_BLOCK_COBBLESTONE,
+ E_BLOCK_LOG,
+ E_BLOCK_PLANKS,
+ E_BLOCK_SANDSTONE,
+ E_BLOCK_WHITE_CLOTH,
+ 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,
+ } ;
+
+ memset(a_BlockTypes, E_BLOCK_AIR, sizeof(a_BlockTypes));
+ memset(a_BlockMeta, 0, sizeof(a_BlockMeta));
+
+ for (int z = 0; z < cChunkDef::Width; z++)
+ {
+ for (int x = 0; x < cChunkDef::Width; x++)
+ {
+ BLOCKTYPE BlockType = Blocks[cChunkDef::GetBiome(a_BiomeMap, x, z) % ARRAYCOUNT(Blocks)];
+ for (int y = a_HeightMap[x + cChunkDef::Width * z]; y >= 0; y--)
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, y, z, BlockType);
+ } // for y
+ } // for z
+ } // for x
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cCompoGenClassic:
+
+cCompoGenClassic::cCompoGenClassic(
+ int a_SeaLevel, int a_BeachHeight, int a_BeachDepth,
+ BLOCKTYPE a_BlockTop, BLOCKTYPE a_BlockMiddle, BLOCKTYPE a_BlockBottom,
+ BLOCKTYPE a_BlockBeach, BLOCKTYPE a_BlockBeachBottom, BLOCKTYPE a_BlockSea
+) :
+ m_SeaLevel(a_SeaLevel),
+ m_BeachHeight(a_BeachHeight),
+ m_BeachDepth(a_BeachDepth),
+ m_BlockTop(a_BlockTop),
+ m_BlockMiddle(a_BlockMiddle),
+ m_BlockBottom(a_BlockBottom),
+ m_BlockBeach(a_BlockBeach),
+ m_BlockBeachBottom(a_BlockBeachBottom),
+ m_BlockSea(a_BlockSea)
+{
+}
+
+
+
+
+
+void cCompoGenClassic::ComposeTerrain(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // BlockTypes to be generated
+ cChunkDef::BlockNibbles & a_BlockMeta, // BlockMetas to be generated
+ const cChunkDef::HeightMap & a_HeightMap, // The height map to fit
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entitites may be generated along with the terrain
+ cBlockEntityList & a_BlockEntities // Block entitites may be generated (chests / furnaces / ...)
+)
+{
+ /* 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
+ */
+
+ memset(a_BlockTypes, E_BLOCK_AIR, sizeof(a_BlockTypes));
+ memset(a_BlockMeta, 0, sizeof(a_BlockMeta));
+
+ // The patterns to use for different situations, must be same length!
+ static const BLOCKTYPE PatternGround[] = {m_BlockTop, m_BlockMiddle, m_BlockMiddle, m_BlockMiddle} ;
+ static const BLOCKTYPE PatternBeach[] = {m_BlockBeach, m_BlockBeach, m_BlockBeach, m_BlockBeachBottom} ;
+ static 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 = cChunkDef::GetHeight(a_HeightMap, 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)
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, y, z, m_BlockSea);
+ }
+
+ // Fill from height till the bottom:
+ for (int y = Height; y >= 1; y--)
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, y, z, (Height - y < PatternLength) ? Pattern[Height - y] : m_BlockBottom);
+ }
+
+ // The last layer is always bedrock:
+ cChunkDef::SetBlock(a_BlockTypes, x, 0, z, E_BLOCK_BEDROCK);
+ } // for x
+ } // for z
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cCompoGenBiomal:
+
+void cCompoGenBiomal::ComposeTerrain(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // BlockTypes to be generated
+ cChunkDef::BlockNibbles & a_BlockMeta, // BlockMetas to be generated
+ const cChunkDef::HeightMap & a_HeightMap, // The height map to fit
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entitites may be generated along with the terrain
+ cBlockEntityList & a_BlockEntities // Block entitites may be generated (chests / furnaces / ...)
+)
+{
+ memset(a_BlockTypes, 0, sizeof(a_BlockTypes));
+ memset(a_BlockMeta, 0, sizeof(a_BlockMeta));
+ for (int z = 0; z < cChunkDef::Width; z++)
+ {
+ for (int x = 0; x < cChunkDef::Width; x++)
+ {
+ int Height = cChunkDef::GetHeight(a_HeightMap, x, z);
+ if (Height > m_SeaLevel)
+ {
+ switch (cChunkDef::GetBiome(a_BiomeMap, 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:
+ {
+ FillColumnGrass(x, z, Height, a_BlockTypes);
+ break;
+ }
+ case biDesertHills:
+ case biDesert:
+ case biBeach:
+ {
+ FillColumnSand(x, z, Height, a_BlockTypes);
+ break;
+ }
+ case biMushroomIsland:
+ case biMushroomShore:
+ {
+ FillColumnMycelium(x, z, Height, a_BlockTypes);
+ break;
+ }
+ default:
+ {
+ // TODO
+ ASSERT(!"CompoGenBiomal: Biome not implemented yet!");
+ break;
+ }
+ }
+ }
+ else
+ {
+ switch (cChunkDef::GetBiome(a_BiomeMap, x, z))
+ {
+ case biDesert:
+ case biBeach:
+ {
+ // Fill with water, sand, sandstone and stone
+ FillColumnWaterSand(x, z, Height, a_BlockTypes);
+ break;
+ }
+ default:
+ {
+ // Fill with water, sand/dirt/clay mix and stone
+ FillColumnWaterMix(a_ChunkX, a_ChunkZ, x, z, Height, a_BlockTypes);
+ break;
+ }
+ } // switch (biome)
+ } // else (under water)
+ cChunkDef::SetBlock(a_BlockTypes, x, 0, z, E_BLOCK_BEDROCK);
+ } // for x
+ } // for z
+}
+
+
+
+
+
+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::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_WATER);
+ }
+}
+
+
+
+
+
+void cCompoGenBiomal::FillColumnWaterMix(int a_ChunkX, int a_ChunkZ, int a_RelX, int a_RelZ, int a_Height, cChunkDef::BlockTypes & a_BlockTypes)
+{
+ if (m_Noise.CubicNoise2D(0.5f * (cChunkDef::Width * a_ChunkX + a_RelX), 0.5f * (cChunkDef::Width * a_ChunkZ + a_RelZ)) < 0)
+ {
+ FillColumnWaterSand(a_RelX, a_RelZ, a_Height, a_BlockTypes);
+ }
+ else
+ {
+ // 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_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]);
+ }
+}
+
+
+
+
diff --git a/source/Generating/CompoGen.h b/source/Generating/CompoGen.h
new file mode 100644
index 000000000..235b9dafd
--- /dev/null
+++ b/source/Generating/CompoGen.h
@@ -0,0 +1,154 @@
+
+// CompoGen.h
+
+/* Interfaces to the various terrain composition generators:
+ - cCompoGenSameBlock
+ - cCompoGenDebugBiomes
+ - cCompoGenClassic
+ - cCompoGenBiomal
+*/
+
+
+
+
+
+#pragma once
+
+#include "ChunkGenerator.h"
+#include "../cNoise.h"
+
+
+
+
+
+class cCompoGenSameBlock :
+ public cTerrainCompositionGen
+{
+public:
+ cCompoGenSameBlock(BLOCKTYPE a_BlockType, bool a_IsBedrocked) :
+ m_BlockType(a_BlockType),
+ m_IsBedrocked(a_IsBedrocked)
+ {}
+
+protected:
+
+ BLOCKTYPE m_BlockType;
+ bool m_IsBedrocked;
+
+ // cTerrainCompositionGen overrides:
+ virtual void ComposeTerrain(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // BlockTypes to be generated
+ cChunkDef::BlockNibbles & a_BlockMeta, // BlockMetas to be generated
+ const cChunkDef::HeightMap & a_HeightMap, // The height map to fit
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entitites may be generated along with the terrain
+ cBlockEntityList & a_BlockEntities // Block entitites may be generated (chests / furnaces / ...)
+ ) override;
+} ;
+
+
+
+
+
+class cCompoGenDebugBiomes :
+ public cTerrainCompositionGen
+{
+public:
+ cCompoGenDebugBiomes(void) {}
+
+protected:
+
+ // cTerrainCompositionGen overrides:
+ virtual void ComposeTerrain(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // BlockTypes to be generated
+ cChunkDef::BlockNibbles & a_BlockMeta, // BlockMetas to be generated
+ const cChunkDef::HeightMap & a_HeightMap, // The height map to fit
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entitites may be generated along with the terrain
+ cBlockEntityList & a_BlockEntities // Block entitites may be generated (chests / furnaces / ...)
+ ) override;
+} ;
+
+
+
+
+
+class cCompoGenClassic :
+ public cTerrainCompositionGen
+{
+public:
+ cCompoGenClassic(
+ int a_SeaLevel, int a_BeachHeight, int a_BeachDepth,
+ BLOCKTYPE a_BlockTop, BLOCKTYPE a_BlockMiddle, BLOCKTYPE a_BlockBottom,
+ BLOCKTYPE a_BlockBeach, BLOCKTYPE a_BlockBeachBottom, BLOCKTYPE a_BlockSea
+ );
+
+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(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // BlockTypes to be generated
+ cChunkDef::BlockNibbles & a_BlockMeta, // BlockMetas to be generated
+ const cChunkDef::HeightMap & a_HeightMap, // The height map to fit
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entitites may be generated along with the terrain
+ cBlockEntityList & a_BlockEntities // Block entitites may be generated (chests / furnaces / ...)
+ ) override;
+} ;
+
+
+
+
+
+class cCompoGenBiomal :
+ public cTerrainCompositionGen
+{
+public:
+ cCompoGenBiomal(int a_Seed, int a_SeaLevel) :
+ m_Noise(a_Seed + 1000),
+ m_SeaLevel(a_SeaLevel - 1) // we do an adjustment later in filling the terrain with water
+ {
+ }
+
+protected:
+
+ cNoise m_Noise;
+ int m_SeaLevel;
+
+ // cTerrainCompositionGen overrides:
+ virtual void ComposeTerrain(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // BlockTypes to be generated
+ cChunkDef::BlockNibbles & a_BlockMeta, // BlockMetas to be generated
+ const cChunkDef::HeightMap & a_HeightMap, // The height map to fit
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entitites may be generated along with the terrain
+ cBlockEntityList & a_BlockEntities // Block entitites may be generated (chests / furnaces / ...)
+ ) override;
+
+ void FillColumnGrass (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 FillColumnWaterMix (int a_ChunkX, int a_ChunkZ, 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);
+} ;
+
+
+
+
diff --git a/source/Generating/FinishGen.cpp b/source/Generating/FinishGen.cpp
new file mode 100644
index 000000000..c3aedd1a0
--- /dev/null
+++ b/source/Generating/FinishGen.cpp
@@ -0,0 +1,387 @@
+
+// FinishGen.cpp
+
+/* Implements the various finishing generators:
+ - cFinishGenSnow
+ - cFinishGenIce
+ - cFinishGenSprinkleFoliage
+*/
+
+#include "Globals.h"
+
+#include "FinishGen.h"
+#include "../cNoise.h"
+#include "../BlockID.h"
+
+
+
+
+
+static inline bool IsWater(BLOCKTYPE a_BlockType)
+{
+ return (a_BlockType == E_BLOCK_STATIONARY_WATER) || (a_BlockType == E_BLOCK_WATER);
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cFinishGenSprinkleFoliage:
+
+bool cFinishGenSprinkleFoliage::TryAddSugarcane(
+ int a_ChunkX, int a_ChunkZ,
+ int a_RelX, int a_RelY, int a_RelZ,
+ cChunkDef::BlockTypes & a_BlockTypes,
+ cChunkDef::BlockNibbles & a_BlockMeta
+)
+{
+ // 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 (cChunkDef::GetBlock(a_BlockTypes, 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(cChunkDef::GetBlock(a_BlockTypes, a_RelX - 1, a_RelY, a_RelZ)) &&
+ !IsWater(cChunkDef::GetBlock(a_BlockTypes, a_RelX + 1, a_RelY, a_RelZ)) &&
+ !IsWater(cChunkDef::GetBlock(a_BlockTypes, a_RelX , a_RelY, a_RelZ - 1)) &&
+ !IsWater(cChunkDef::GetBlock(a_BlockTypes, a_RelX , a_RelY, a_RelZ + 1))
+ )
+ {
+ return false;
+ }
+
+ // All conditions met, place a sugarcane here:
+ cChunkDef::SetBlock(a_BlockTypes, a_RelX, a_RelY + 1, a_RelZ, E_BLOCK_SUGARCANE);
+ return true;
+}
+
+
+
+
+
+void cFinishGenSprinkleFoliage::GenFinish(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ )
+{
+ // Generate small foliage (1-block):
+
+ // TODO: Update heightmap with 1-block-tall foliage
+ cNoise Noise(m_Seed);
+ for (int z = 0; z < cChunkDef::Width; z++)
+ {
+ int BlockZ = a_ChunkZ * cChunkDef::Width + z;
+ const float zz = (float)BlockZ;
+ for (int x = 0; x < cChunkDef::Width; x++)
+ {
+ int BlockX = a_ChunkX * cChunkDef::Width + x;
+ if (((Noise.IntNoise2DInt(BlockX, BlockZ) / 8) % 128) < 124)
+ {
+ continue;
+ }
+ int Top = cChunkDef::GetHeight(a_HeightMap, x, z);
+ if (Top > 250)
+ {
+ // Nothing grows above Y=250
+ continue;
+ }
+ if (cChunkDef::GetBlock(a_BlockTypes, 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 = Noise.CubicNoise2D(xx * 0.1f, zz * 0.1f );
+ float val2 = Noise.CubicNoise2D(xx * 0.01f, zz * 0.01f );
+ switch (cChunkDef::GetBlock(a_BlockTypes, x, Top, z))
+ {
+ case E_BLOCK_GRASS:
+ {
+ float val3 = Noise.CubicNoise2D(xx * 0.01f + 10, zz * 0.01f + 10 );
+ float val4 = Noise.CubicNoise2D(xx * 0.05f + 20, zz * 0.05f + 20 );
+ if (val1 + val2 > 0.2f)
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, ++Top, z, E_BLOCK_YELLOW_FLOWER);
+ }
+ else if (val2 + val3 > 0.2f)
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, ++Top, z, E_BLOCK_RED_ROSE);
+ }
+ else if (val3 + val4 > 0.2f)
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, ++Top, z, E_BLOCK_RED_MUSHROOM);
+ }
+ else if (val1 + val4 > 0.2f)
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, ++Top, z, E_BLOCK_BROWN_MUSHROOM);
+ }
+ else if (val1 + val2 + val3 + val4 < -0.1)
+ {
+ cChunkDef::SetBlock (a_BlockTypes, x, ++Top, z, E_BLOCK_TALL_GRASS);
+ cChunkDef::SetNibble(a_BlockMeta, x, Top, z, E_META_TALL_GRASS_GRASS);
+ }
+ else if (TryAddSugarcane(a_ChunkX, a_ChunkZ, x, Top, z, a_BlockTypes, a_BlockMeta))
+ {
+ ++Top;
+ }
+ else if ((val1 > 0.5) && (val2 < -0.5))
+ {
+ cChunkDef::SetBlock (a_BlockTypes, x, ++Top, z, E_BLOCK_PUMPKIN);
+ cChunkDef::SetNibble(a_BlockMeta, x, Top, z, (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) &&
+ (cChunkDef::GetBlock(a_BlockTypes, x + 1, y, z) == E_BLOCK_AIR) &&
+ (cChunkDef::GetBlock(a_BlockTypes, x - 1, y, z) == E_BLOCK_AIR) &&
+ (cChunkDef::GetBlock(a_BlockTypes, x, y, z + 1) == E_BLOCK_AIR) &&
+ (cChunkDef::GetBlock(a_BlockTypes, x, y, z - 1) == E_BLOCK_AIR)
+ )
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, ++Top, z, E_BLOCK_CACTUS);
+ }
+ else if (TryAddSugarcane(a_ChunkX, a_ChunkZ, x, Top, z, a_BlockTypes, a_BlockMeta))
+ {
+ ++Top;
+ }
+ break;
+ }
+ } // switch (TopBlock)
+ cChunkDef::SetHeight(a_HeightMap, x, z, Top);
+ } // for y
+ } // for z
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cFinishGenSnow:
+
+void cFinishGenSnow::GenFinish(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ )
+{
+ // 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 (cChunkDef::GetBiome(a_BiomeMap, x, z))
+ {
+ case biIcePlains:
+ case biIceMountains:
+ case biTaiga:
+ case biTaigaHills:
+ case biFrozenRiver:
+ case biFrozenOcean:
+ {
+ int Height = cChunkDef::GetHeight(a_HeightMap, x, z);
+ if (g_BlockIsSnowable[cChunkDef::GetBlock(a_BlockTypes, x, Height, z)])
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, Height + 1, z, E_BLOCK_SNOW);
+ cChunkDef::SetHeight(a_HeightMap, x, z, Height + 1);
+ }
+ break;
+ }
+ }
+ }
+ } // for z
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cFinishGenIce:
+
+void cFinishGenIce::GenFinish(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ )
+{
+ // 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 (cChunkDef::GetBiome(a_BiomeMap, x, z))
+ {
+ case biIcePlains:
+ case biIceMountains:
+ case biTaiga:
+ case biTaigaHills:
+ case biFrozenRiver:
+ case biFrozenOcean:
+ {
+ int Height = cChunkDef::GetHeight(a_HeightMap, x, z);
+ switch (cChunkDef::GetBlock(a_BlockTypes, x, Height, z))
+ {
+ case E_BLOCK_WATER:
+ case E_BLOCK_STATIONARY_WATER:
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, Height, z, E_BLOCK_ICE);
+ break;
+ }
+ }
+ break;
+ }
+ }
+ }
+ } // for z
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cFinishGenLilypads:
+
+int cFinishGenLilypads::GetNumLilypads(const cChunkDef::BiomeMap & a_BiomeMap)
+{
+ int res = 0;
+ for (int i = 0; i < ARRAYCOUNT(a_BiomeMap); i++)
+ {
+ if (a_BiomeMap[i] == biSwampland)
+ {
+ res++;
+ }
+ } // for i - a_BiomeMap[]
+ return res / 64;
+}
+
+
+
+
+
+void cFinishGenLilypads::GenFinish(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+)
+{
+ // Add Lilypads on top of water surface in Swampland
+
+ int NumLilypads = GetNumLilypads(a_BiomeMap);
+ for (int i = 0; i < NumLilypads; i++)
+ {
+ int x = m_Noise.IntNoise3DInt(a_ChunkX + a_ChunkZ, a_ChunkZ, i) % cChunkDef::Width;
+ int z = m_Noise.IntNoise3DInt(a_ChunkX - a_ChunkZ, i, a_ChunkZ) % cChunkDef::Width;
+
+ // Place a lily pad at {x, z} if possible (swampland, empty block, water below):
+ if (cChunkDef::GetBiome(a_BiomeMap, x, z) != biSwampland)
+ {
+ // not swampland
+ continue;
+ }
+ int Height = cChunkDef::GetHeight(a_HeightMap, x, z);
+ if (Height >= cChunkDef::Height)
+ {
+ // Too high up
+ continue;
+ }
+ if (cChunkDef::GetBlock(a_BlockTypes, x, Height + 1, z) != E_BLOCK_AIR)
+ {
+ // not empty block
+ continue;
+ }
+ switch (cChunkDef::GetBlock(a_BlockTypes, x, Height, z))
+ {
+ case E_BLOCK_WATER:
+ case E_BLOCK_STATIONARY_WATER:
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, Height + 1, z, E_BLOCK_LILY_PAD);
+ cChunkDef::SetHeight(a_HeightMap, x, z, Height + 1);
+ break;
+ }
+ } // switch (GetBlock)
+ } // for i
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cFinishGenBottomLava:
+
+void cFinishGenBottomLava::GenFinish(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+)
+{
+ 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 (a_BlockTypes[Index] == E_BLOCK_AIR)
+ {
+ a_BlockTypes[Index] = E_BLOCK_STATIONARY_LAVA;
+ }
+ } // for x, for z
+ } // for y
+}
+
+
+
+
diff --git a/source/Generating/FinishGen.h b/source/Generating/FinishGen.h
new file mode 100644
index 000000000..f9a539572
--- /dev/null
+++ b/source/Generating/FinishGen.h
@@ -0,0 +1,152 @@
+
+// FinishGen.h
+
+/* Interfaces to the various finishing generators:
+ - cFinishGenSnow
+ - cFinishGenIce
+ - cFinishGenSprinkleFoliage
+ - cFinishGenLilypads
+ - cFinishGenBottomLava
+*/
+
+
+
+
+
+#include "ChunkGenerator.h"
+#include "../cNoise.h"
+
+
+
+
+
+class cFinishGenSnow :
+ public cFinishGen
+{
+protected:
+ // cFinishGen override:
+ virtual void GenFinish(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) override;
+} ;
+
+
+
+
+
+class cFinishGenIce :
+ public cFinishGen
+{
+protected:
+ // cFinishGen override:
+ virtual void GenFinish(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) override;
+} ;
+
+
+
+
+class cFinishGenSprinkleFoliage :
+ public cFinishGen
+{
+public:
+ cFinishGenSprinkleFoliage(int a_Seed) : m_Seed(a_Seed) {}
+
+protected:
+ int m_Seed;
+
+ /// Tries to place sugarcane at the coords specified, returns true if successful
+ bool TryAddSugarcane(
+ int a_ChunkX, int a_ChunkZ,
+ int a_RelX, int a_RelY, int a_RelZ, // relative block coords of the sugarcane's base
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta // Block meta to read and change
+ );
+
+ // cFinishGen override:
+ virtual void GenFinish(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) override;
+} ;
+
+
+
+
+
+class cFinishGenLilypads :
+ public cFinishGen
+{
+public:
+ cFinishGenLilypads(int a_Seed) :
+ m_Noise(a_Seed)
+ {
+ }
+
+protected:
+ cNoise m_Noise;
+
+ int GetNumLilypads(const cChunkDef::BiomeMap & a_BiomeMap);
+
+ // cFinishGen override:
+ virtual void GenFinish(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) override;
+} ;
+
+
+
+
+
+class cFinishGenBottomLava :
+ public cFinishGen
+{
+public:
+ cFinishGenBottomLava(int a_Level) :
+ m_Level(a_Level)
+ {
+ }
+
+protected:
+ int m_Level;
+
+ // cFinishGen override:
+ virtual void GenFinish(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ const cChunkDef::BiomeMap & a_BiomeMap, // Biomes to adhere to
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) override;
+} ;
+
+
+
+
+
diff --git a/source/Generating/HeiGen.cpp b/source/Generating/HeiGen.cpp
new file mode 100644
index 000000000..ed353751c
--- /dev/null
+++ b/source/Generating/HeiGen.cpp
@@ -0,0 +1,302 @@
+
+// HeiGen.cpp
+
+// Implements the various terrain height generators
+
+#include "Globals.h"
+#include "HeiGen.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;
+ }
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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;
+ delete m_HeiGenToCache;
+}
+
+
+
+
+
+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;
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cHeiGenClassic:
+
+cHeiGenClassic::cHeiGenClassic(int a_Seed, float a_HeightFreq1, float a_HeightAmp1, float a_HeightFreq2, float a_HeightAmp2, float a_HeightFreq3, float a_HeightAmp3) :
+ m_Seed(a_Seed),
+ m_Noise(a_Seed),
+ m_HeightFreq1(a_HeightFreq1),
+ m_HeightAmp1 (a_HeightAmp1),
+ m_HeightFreq2(a_HeightFreq2),
+ m_HeightAmp2 (a_HeightAmp2),
+ m_HeightFreq3(a_HeightFreq3),
+ m_HeightAmp3 (a_HeightAmp3)
+{
+ // Nothing needed yet
+}
+
+
+
+
+
+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
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cHeiGenBiomal:
+
+const cHeiGenBiomal::sGenParam cHeiGenBiomal::m_GenParam[biNumBiomes] =
+{
+ /* Fast-changing | Middle-changing | Slow-changing |*/
+ /* Biome | Freq1 | Amp1 | Freq2 | Amp2 | Freq3 | Amp3 | BaseHeight */
+ /* biOcean */ { 0.1f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 40}, // done
+ /* biPlains */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 68}, // done
+ /* biDesert */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 68}, // done
+ /* biExtremeHills */ { 0.2f, 4.0f, 0.05f, 20.0f, 0.01f, 16.0f, 100}, // done
+ /* biForest */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 70}, // done
+ /* biTaiga */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 70}, // done
+ /* biSwampland */ { 0.1f, 1.1f, 0.05f, 1.5f, 0.02f, 2.5f, 61.5}, // done
+ /* biRiver */ { 0.2f, 3.0f, 0.05f, 1.0f, 0.01f, 1.0f, 56}, // done
+ /* 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}, // done
+ /* biFrozenRiver */ { 0.2f, 3.0f, 0.05f, 1.0f, 0.01f, 1.0f, 56}, // done
+ /* biIcePlains */ { 0.1f, 1.0f, 0.05f, 1.5f, 0.01f, 4.0f, 68}, // done
+ /* biIceMountains */ { 0.2f, 2.0f, 0.05f, 10.0f, 0.01f, 8.0f, 80}, // done
+ /* biMushroomIsland */ { 0.1f, 2.0f, 0.05f, 8.0f, 0.01f, 6.0f, 80}, // done
+ /* biMushroomShore */ { 0.1f, 1.0f, 0.05f, 2.0f, 0.01f, 4.0f, 64}, // done
+ /* biBeach */ { 0.1f, 0.5f, 0.05f, 1.0f, 0.01f, 1.0f, 64}, // done
+ /* biDesertHills */ { 0.2f, 2.0f, 0.05f, 5.0f, 0.01f, 4.0f, 75}, // done
+ /* biForestHills */ { 0.2f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 80}, // done
+ /* biTaigaHills */ { 0.2f, 2.0f, 0.05f, 12.0f, 0.01f, 10.0f, 80}, // done
+ /* biExtremeHillsEdge */ { 0.2f, 3.0f, 0.05f, 16.0f, 0.01f, 12.0f, 80}, // done
+ /* biJungle */ { 0.1f, 3.0f, 0.05f, 6.0f, 0.01f, 6.0f, 70}, // done
+ /* biJungleHills */ { 0.2f, 3.0f, 0.05f, 12.0f, 0.01f, 10.0f, 80}, // done
+} ;
+
+
+
+
+
+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
+
+ // 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
+ }
+}
+
+
+
+
+
+HEIGHTTYPE 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[biNumBiomes];
+ 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);
+ if ((Biome < 0) || (Biome >= ARRAYCOUNT(BiomeCounts)))
+ {
+ continue;
+ }
+ 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)
+ {
+ int 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;
+ }
+ float oct1 = m_Noise.CubicNoise2D(BlockX * m_GenParam[i].m_HeightFreq1, BlockZ * m_GenParam[i].m_HeightFreq1) * m_GenParam[i].m_HeightAmp1;
+ float oct2 = m_Noise.CubicNoise2D(BlockX * m_GenParam[i].m_HeightFreq2, BlockZ * m_GenParam[i].m_HeightFreq2) * m_GenParam[i].m_HeightAmp2;
+ float oct3 = m_Noise.CubicNoise2D(BlockX * m_GenParam[i].m_HeightFreq3, BlockZ * m_GenParam[i].m_HeightFreq3) * m_GenParam[i].m_HeightAmp3;
+ Height += BiomeCounts[i] * (int)(m_GenParam[i].m_BaseHeight + oct1 + oct2 + oct3);
+ }
+ int res = (HEIGHTTYPE)(Height / Sum);
+ return std::min(250, std::max(res, 5));
+ }
+
+ // No known biome around? Weird. Return a bogus value:
+ ASSERT(!"cHeiGenBiomal: Biome sum failed, no known biome around");
+ return 5;
+}
+
+
+
+
+
diff --git a/source/Generating/HeiGen.h b/source/Generating/HeiGen.h
new file mode 100644
index 000000000..42e70f2a5
--- /dev/null
+++ b/source/Generating/HeiGen.h
@@ -0,0 +1,137 @@
+
+// HeiGen.h
+
+/*
+Interfaces to the various height generators:
+ - cHeiGenFlat
+ - cHeiGenClassic
+ - cHeiGenBiomal
+*/
+
+
+
+
+
+#pragma once
+
+#include "ChunkGenerator.h"
+#include "../cNoise.h"
+
+
+
+
+
+class cHeiGenFlat :
+ public cTerrainHeightGen
+{
+public:
+ cHeiGenFlat(int a_Height) : m_Height(a_Height) {}
+
+protected:
+
+ int m_Height;
+
+ // cTerrainHeightGen override:
+ virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) 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); // Takes ownership of a_HeiGenToCache
+ ~cHeiGenCache();
+
+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)
+
+ virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override;
+} ;
+
+
+
+
+
+class cHeiGenClassic :
+ public cTerrainHeightGen
+{
+public:
+ cHeiGenClassic(int a_Seed, float a_HeightFreq1, float a_HeightAmp1, float a_HeightFreq2, float a_HeightAmp2, float a_HeightFreq3, float a_HeightAmp3);
+
+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 override:
+ virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) 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[biNumBiomes];
+
+ // cTerrainHeightGen override:
+ virtual void GenHeightMap(int a_ChunkX, int a_ChunkZ, cChunkDef::HeightMap & a_HeightMap) override;
+
+ HEIGHTTYPE GetHeightAt(int a_RelX, int a_RelZ, int a_ChunkX, int a_ChunkZ, const BiomeNeighbors & a_BiomeNeighbors);
+} ;
+
+
+
+
diff --git a/source/Generating/Ravines.cpp b/source/Generating/Ravines.cpp
new file mode 100644
index 000000000..c298033f9
--- /dev/null
+++ b/source/Generating/Ravines.cpp
@@ -0,0 +1,514 @@
+
+// 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(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+)
+{
+ cRavines Ravines;
+ GetRavinesForChunk(a_ChunkX, a_ChunkZ, Ravines);
+ for (cRavines::const_iterator itr = Ravines.begin(); itr != Ravines.end(); ++itr)
+ {
+ (*itr)->ProcessChunk(a_ChunkX, a_ChunkZ, a_BlockTypes, a_HeightMap);
+ } // 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++)
+ {
+ cChunkDef::SetBlock(a_BlockTypes, x, y, z, E_BLOCK_AIR);
+ }
+ }
+ } // for x, z - a_BlockTypes
+ } // for itr - m_Points[]
+}
+
+
+
+
diff --git a/source/Generating/Ravines.h b/source/Generating/Ravines.h
new file mode 100644
index 000000000..623986f29
--- /dev/null
+++ b/source/Generating/Ravines.h
@@ -0,0 +1,53 @@
+
+// Ravines.h
+
+// Interfaces to the cStructGenRavines class representing the ravine structure generator
+
+
+
+
+
+#pragma once
+
+#include "ChunkGenerator.h"
+#include "../cNoise.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(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) override;
+} ;
+
+
+
+
diff --git a/source/Generating/StructGen.cpp b/source/Generating/StructGen.cpp
new file mode 100644
index 000000000..037aa46f8
--- /dev/null
+++ b/source/Generating/StructGen.cpp
@@ -0,0 +1,415 @@
+
+// StructGen.h
+
+#include "Globals.h"
+#include "StructGen.h"
+#include "../BlockID.h"
+#include "Trees.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 = 70;
+const int NUM_NESTS_IRON = 20;
+const int NEST_SIZE_IRON = 6;
+
+const int MAX_HEIGHT_REDSTONE = 40;
+const int NUM_NESTS_REDSTONE = 7;
+const int NEST_SIZE_REDSTONE = 6;
+
+const int MAX_HEIGHT_GOLD = 35;
+const int NUM_NESTS_GOLD = 6;
+const int NEST_SIZE_GOLD = 6;
+
+const int MAX_HEIGHT_DIAMOND = 16;
+const int NUM_NESTS_DIAMOND = 3;
+const int NEST_SIZE_DIAMOND = 5;
+
+const int MAX_HEIGHT_LAPIS = 30;
+const int NUM_NESTS_LAPIS = 3;
+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(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMetas, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+)
+{
+ cChunkDef::BlockTypes WorkerBlockTypes;
+ cChunkDef::BlockNibbles WorkerBlockMeta;
+ cChunkDef::HeightMap WorkerHeight;
+
+ cEntityList Entities;
+ cBlockEntityList BlockEntities;
+
+ // Generate trees:
+ for (int x = 0; x <= 2; x++)
+ {
+ int BaseX = a_ChunkX + x - 1;
+ for (int z = 0; z <= 2; z++)
+ {
+ int BaseZ = a_ChunkZ + z - 1;
+
+ cChunkDef::BlockTypes * BlT;
+ cChunkDef::BlockNibbles * BlM;
+ cChunkDef::HeightMap * Hei;
+
+ cChunkDef::BiomeMap Biomes;
+ m_BiomeGen->GenBiomes(BaseX, BaseZ, Biomes);
+
+ if ((x != 1) || (z != 1))
+ {
+ BlT = &WorkerBlockTypes;
+ BlM = &WorkerBlockMeta;
+ Hei = &WorkerHeight;
+
+ m_HeightGen->GenHeightMap (BaseX, BaseZ, *Hei);
+ m_CompositionGen->ComposeTerrain(BaseX, BaseZ, *BlT, *BlM, *Hei, Biomes, Entities, BlockEntities);
+ // TODO: Free the entity lists
+ }
+ else
+ {
+ BlT = &a_BlockTypes;
+ BlM = &a_BlockMetas;
+ Hei = &a_HeightMap;
+ }
+
+ int NumTrees = GetNumTrees(BaseX, BaseZ, Biomes);
+
+ sSetBlockVector OutsideLogs, OutsideOther;
+ for (int i = 0; i < NumTrees; i++)
+ {
+ GenerateSingleTree(BaseX, BaseZ, i, *BlT, *BlM, *Hei, Biomes, OutsideLogs, OutsideOther);
+ }
+
+ sSetBlockVector IgnoredOverflow;
+ IgnoredOverflow.reserve(OutsideOther.size());
+ ApplyTreeImage(a_ChunkX, a_ChunkZ, a_BlockTypes, a_BlockMetas, OutsideOther, IgnoredOverflow);
+ IgnoredOverflow.clear();
+ IgnoredOverflow.reserve(OutsideLogs.size());
+ ApplyTreeImage(a_ChunkX, a_ChunkZ, a_BlockTypes, a_BlockMetas, 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 (cChunkDef::GetBlock(a_BlockTypes, x, y, z) != E_BLOCK_AIR)
+ {
+ cChunkDef::SetHeight(a_HeightMap, x, z, y);
+ break;
+ }
+ } // for y
+ } // for z
+ } // for x
+}
+
+
+
+
+
+void cStructGenTrees::GenerateSingleTree(
+ int a_ChunkX, int a_ChunkZ, int a_Seq,
+ cChunkDef::BlockTypes & a_BlockTypes,
+ cChunkDef::BlockNibbles & a_BlockMetas,
+ const cChunkDef::HeightMap & a_Height,
+ const cChunkDef::BiomeMap & a_Biomes,
+ 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 = cChunkDef::GetHeight(a_Height, x, z);
+
+ if ((Height <= 0) || (Height > 240))
+ {
+ return;
+ }
+
+ // Check the block underneath the tree:
+ BLOCKTYPE TopBlock = cChunkDef::GetBlock(a_BlockTypes, x, Height, z);
+ if ((TopBlock != E_BLOCK_DIRT) && (TopBlock != E_BLOCK_GRASS) && (TopBlock != E_BLOCK_SOIL))
+ {
+ return;
+ }
+
+ sSetBlockVector TreeLogs, TreeOther;
+ GetTreeImageByBiome(
+ a_ChunkX * cChunkDef::Width + x, Height + 1, a_ChunkZ * cChunkDef::Width + z,
+ m_Noise, a_Seq,
+ cChunkDef::GetBiome(a_Biomes, 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 = cChunkDef::GetBlock(a_BlockTypes, 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_BlockTypes, a_BlockMetas, TreeOther, a_OutsideOther);
+ ApplyTreeImage(a_ChunkX, a_ChunkZ, a_BlockTypes, a_BlockMetas, TreeLogs, a_OutsideLogs);
+}
+
+
+
+
+
+void cStructGenTrees::ApplyTreeImage(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes,
+ cChunkDef::BlockNibbles & a_BlockMetas,
+ 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(); itr != a_Image.end(); ++itr)
+ {
+ if ((itr->ChunkX == a_ChunkX) && (itr->ChunkZ == a_ChunkZ))
+ {
+ // Inside this chunk, integrate into a_BlockTypes:
+ switch (cChunkDef::GetBlock(a_BlockTypes, itr->x, itr->y, itr->z))
+ {
+ case E_BLOCK_LEAVES:
+ {
+ if (itr->BlockType != E_BLOCK_LOG)
+ {
+ break;
+ }
+ // fallthrough:
+ }
+ CASE_TREE_OVERWRITTEN_BLOCKS:
+ {
+ cChunkDef::SetBlock (a_BlockTypes, itr->x, itr->y, itr->z, itr->BlockType);
+ cChunkDef::SetNibble(a_BlockMetas, itr->x, itr->y, itr->z, 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 (a_Biomes[x + cChunkDef::Width * z])
+ {
+ 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;
+ }
+ NumTrees += Add;
+ }
+ return NumTrees / 1024;
+}
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cStructGenOreNests:
+
+void cStructGenOreNests::GenStructures(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+)
+{
+ GenerateOre(a_ChunkX, a_ChunkZ, E_BLOCK_COAL_ORE, MAX_HEIGHT_COAL, NUM_NESTS_COAL, NEST_SIZE_COAL, a_BlockTypes, 1);
+ GenerateOre(a_ChunkX, a_ChunkZ, E_BLOCK_IRON_ORE, MAX_HEIGHT_IRON, NUM_NESTS_IRON, NEST_SIZE_IRON, a_BlockTypes, 2);
+ GenerateOre(a_ChunkX, a_ChunkZ, E_BLOCK_REDSTONE_ORE, MAX_HEIGHT_REDSTONE, NUM_NESTS_REDSTONE, NEST_SIZE_REDSTONE, a_BlockTypes, 3);
+ GenerateOre(a_ChunkX, a_ChunkZ, E_BLOCK_GOLD_ORE, MAX_HEIGHT_GOLD, NUM_NESTS_GOLD, NEST_SIZE_GOLD, a_BlockTypes, 4);
+ GenerateOre(a_ChunkX, a_ChunkZ, E_BLOCK_DIAMOND_ORE, MAX_HEIGHT_DIAMOND, NUM_NESTS_DIAMOND, NEST_SIZE_DIAMOND, a_BlockTypes, 5);
+ GenerateOre(a_ChunkX, a_ChunkZ, E_BLOCK_LAPIS_ORE, MAX_HEIGHT_LAPIS, NUM_NESTS_LAPIS, NEST_SIZE_LAPIS, a_BlockTypes, 6);
+ GenerateOre(a_ChunkX, a_ChunkZ, E_BLOCK_DIRT, MAX_HEIGHT_DIRT, NUM_NESTS_DIRT, NEST_SIZE_DIRT, a_BlockTypes, 10);
+ GenerateOre(a_ChunkX, a_ChunkZ, E_BLOCK_GRAVEL, MAX_HEIGHT_GRAVEL, NUM_NESTS_GRAVEL, NEST_SIZE_GRAVEL, a_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
+}
+
+
+
+
+
diff --git a/source/Generating/StructGen.h b/source/Generating/StructGen.h
new file mode 100644
index 000000000..8ddfdadba
--- /dev/null
+++ b/source/Generating/StructGen.h
@@ -0,0 +1,112 @@
+
+// StructGen.h
+
+/* Interfaces to the various structure generators:
+ - cStructGenTrees
+ - cStructGenMarbleCaves
+ - cStructGenOres
+*/
+
+
+
+
+
+#pragma once
+
+#include "ChunkGenerator.h"
+#include "../cNoise.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,
+ cChunkDef::BlockTypes & a_BlockTypes,
+ cChunkDef::BlockNibbles & a_BlockMetas,
+ const cChunkDef::HeightMap & a_Height,
+ const cChunkDef::BiomeMap & a_Biomes,
+ 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,
+ cChunkDef::BlockTypes & a_BlockTypes,
+ cChunkDef::BlockNibbles & a_BlockMetas,
+ 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(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMetas, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) 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(
+ int a_ChunkX, int a_ChunkZ,
+ cChunkDef::BlockTypes & a_BlockTypes, // Block types to read and change
+ cChunkDef::BlockNibbles & a_BlockMeta, // Block meta to read and change
+ cChunkDef::HeightMap & a_HeightMap, // Height map to read and change by the current data
+ cEntityList & a_Entities, // Entities may be added or deleted
+ cBlockEntityList & a_BlockEntities // Block entities may be added or deleted
+ ) 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);
+} ;
+
+
+
+
+
diff --git a/source/Generating/Trees.cpp b/source/Generating/Trees.cpp
new file mode 100644
index 000000000..487d3e065
--- /dev/null
+++ b/source/Generating/Trees.cpp
@@ -0,0 +1,684 @@
+
+// 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, -2}, {-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:
+ {
+ // 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:
+ {
+ // 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);
+ }
+ }
+ }
+}
+
+
+
+
+
+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/source/Generating/Trees.h b/source/Generating/Trees.h
new file mode 100644
index 000000000..d3e76a78c
--- /dev/null
+++ b/source/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 "../cNoise.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);
+
+
+
+
+