diff options
author | Alexander Harkness <bearbin@gmail.com> | 2013-11-24 15:19:41 +0100 |
---|---|---|
committer | Alexander Harkness <bearbin@gmail.com> | 2013-11-24 15:19:41 +0100 |
commit | 675b4aa878f16291ce33fced48a2bc7425f635ae (patch) | |
tree | 409914df27a98f65adf866da669429c4de141b6f /src/Generating/Caves.cpp | |
parent | LineBlockTracer: Using the coord-based block faces. (diff) | |
download | cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.tar cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.tar.gz cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.tar.bz2 cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.tar.lz cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.tar.xz cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.tar.zst cuberite-675b4aa878f16291ce33fced48a2bc7425f635ae.zip |
Diffstat (limited to 'src/Generating/Caves.cpp')
-rw-r--r-- | src/Generating/Caves.cpp | 970 |
1 files changed, 970 insertions, 0 deletions
diff --git a/src/Generating/Caves.cpp b/src/Generating/Caves.cpp new file mode 100644 index 000000000..4221ea187 --- /dev/null +++ b/src/Generating/Caves.cpp @@ -0,0 +1,970 @@ + +// Caves.cpp + +// Implements the various cave structure generators: +// - cStructGenWormNestCaves +// - cStructGenDualRidgeCaves +// - cStructGenMarbleCaves +// - cStructGenNetherCaves + +/* +WormNestCave generator: +Caves are generated in "nests" - groups of tunnels generated from a single point. +For each chunk, all the nests that could intersect it are generated. +For each nest, first the schematic structure is generated (tunnel from ... to ..., branch, tunnel2 from ... to ...) +Then each tunnel is randomized by inserting points in between its ends. +Finally each tunnel is smoothed and Bresenham-3D-ed so that it is a collection of spheres with their centers next to each other. +When the tunnels are ready, they are simply carved into the chunk, one by one. +To optimize, each tunnel keeps track of its bounding box, so that it can be skipped for chunks that don't intersect it. + +MarbleCaves generator: +For each voxel a 3D noise function is evaluated, if the value crosses a boundary, the voxel is dug out, otherwise it is kept. +Problem with this is the amount of CPU work needed for each chunk. +Also the overall shape of the generated holes is unsatisfactory - there are whole "sheets" of holes in the ground. + +DualRidgeCaves generator: +Instead of evaluating a single noise function, two different noise functions are multiplied. This produces +regular tunnels instead of sheets. However due to the sheer amount of CPU work needed, the noise functions need to be +reduced in complexity in order for this generator to be useful, so the caves' shapes are "bubbly" at best. +*/ + +#include "Globals.h" +#include "Caves.h" + + + + + +/// How many nests in each direction are generated for a given chunk. Must be an even number +#define NEIGHBORHOOD_SIZE 8 + + + + + +const int MIN_RADIUS = 3; +const int MAX_RADIUS = 8; + + + + + +struct cCaveDefPoint +{ + int m_BlockX; + int m_BlockY; + int m_BlockZ; + int m_Radius; + + cCaveDefPoint(int a_BlockX, int a_BlockY, int a_BlockZ, int a_Radius) : + m_BlockX(a_BlockX), + m_BlockY(a_BlockY), + m_BlockZ(a_BlockZ), + m_Radius(a_Radius) + { + } +} ; + +typedef std::vector<cCaveDefPoint> cCaveDefPoints; + + + + + +/// A single non-branching tunnel of a WormNestCave +class cCaveTunnel +{ + // The bounding box, including the radii around defpoints: + int m_MinBlockX, m_MaxBlockX; + int m_MinBlockY, m_MaxBlockY; + int m_MinBlockZ, m_MaxBlockZ; + + /// Generates the shaping defpoints for the ravine, based on the ravine block coords and noise + void Randomize(cNoise & a_Noise); + + /// Refines (adds and smooths) defpoints from a_Src into a_Dst; returns false if no refinement possible (segments too short) + bool RefineDefPoints(const cCaveDefPoints & a_Src, cCaveDefPoints & a_Dst); + + /// Does rounds of smoothing, two passes of RefineDefPoints(), as long as they return true + void Smooth(void); + + /// Linearly interpolates the points so that the maximum distance between two neighbors is max 1 block + void FinishLinear(void); + + /// Calculates the bounding box of the points present + void CalcBoundingBox(void); + +public: + cCaveDefPoints m_Points; + + cCaveTunnel( + int a_BlockStartX, int a_BlockStartY, int a_BlockStartZ, int a_StartRadius, + int a_BlockEndX, int a_BlockEndY, int a_BlockEndZ, int a_EndRadius, + cNoise & a_Noise + ); + + /// Carves the tunnel into the chunk specified + void ProcessChunk( + int a_ChunkX, int a_ChunkZ, + cChunkDef::BlockTypes & a_BlockTypes, + cChunkDef::HeightMap & a_HeightMap + ); + + #ifdef _DEBUG + AString ExportAsSVG(int a_Color, int a_OffsetX, int a_OffsetZ) const; + #endif // _DEBUG +} ; + +typedef std::vector<cCaveTunnel *> cCaveTunnels; + + + + + +/// A collection of connected tunnels, possibly branching. +class cStructGenWormNestCaves::cCaveSystem +{ +public: + // The generating block position; is read directly in cStructGenWormNestCaves::GetCavesForChunk() + int m_BlockX; + int m_BlockZ; + + cCaveSystem(int a_BlockX, int a_BlockZ, int a_MaxOffset, int a_Size, cNoise & a_Noise); + ~cCaveSystem(); + + /// Carves the cave system into the chunk specified + void ProcessChunk( + int a_ChunkX, int a_ChunkZ, + cChunkDef::BlockTypes & a_BlockTypes, + cChunkDef::HeightMap & a_HeightMap + ); + + #ifdef _DEBUG + AString ExportAsSVG(int a_Color, int a_OffsetX, int a_OffsetZ) const; + #endif // _DEBUG + +protected: + int m_Size; + cCaveTunnels m_Tunnels; + + void Clear(void); + + /// Generates a_Segment successive tunnels, with possible branches. Generates the same output for the same [x, y, z, a_Segments] + void GenerateTunnelsFromPoint( + int a_OriginX, int a_OriginY, int a_OriginZ, + cNoise & a_Noise, int a_Segments + ); + + /// Returns a radius based on the location provided. + int GetRadius(cNoise & a_Noise, int a_OriginX, int a_OriginY, int a_OriginZ); +} ; + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cCaveTunnel: + +cCaveTunnel::cCaveTunnel( + int a_BlockStartX, int a_BlockStartY, int a_BlockStartZ, int a_StartRadius, + int a_BlockEndX, int a_BlockEndY, int a_BlockEndZ, int a_EndRadius, + cNoise & a_Noise +) +{ + m_Points.push_back(cCaveDefPoint(a_BlockStartX, a_BlockStartY, a_BlockStartZ, a_StartRadius)); + m_Points.push_back(cCaveDefPoint(a_BlockEndX, a_BlockEndY, a_BlockEndZ, a_EndRadius)); + + if ((a_BlockStartY <= 0) && (a_BlockEndY <= 0)) + { + // Don't bother detailing this cave, it's under the world anyway + return; + } + + Randomize(a_Noise); + Smooth(); + + // We know that the linear finishing won't affect the bounding box, so let's calculate it now, as we have less data: + CalcBoundingBox(); + + FinishLinear(); +} + + + + + +void cCaveTunnel::Randomize(cNoise & a_Noise) +{ + // Repeat 4 times: + for (int i = 0; i < 4; i++) + { + // For each already present point, insert a point in between it and its predecessor, shifted randomly. + int PrevX = m_Points.front().m_BlockX; + int PrevY = m_Points.front().m_BlockY; + int PrevZ = m_Points.front().m_BlockZ; + int PrevR = m_Points.front().m_Radius; + cCaveDefPoints Pts; + Pts.reserve(m_Points.size() * 2 + 1); + Pts.push_back(m_Points.front()); + for (cCaveDefPoints::const_iterator itr = m_Points.begin() + 1, end = m_Points.end(); itr != end; ++itr) + { + int Random = a_Noise.IntNoise3DInt(PrevX, PrevY, PrevZ + i) / 11; + int len = (PrevX - itr->m_BlockX) * (PrevX - itr->m_BlockX); + len += (PrevY - itr->m_BlockY) * (PrevY - itr->m_BlockY); + len += (PrevZ - itr->m_BlockZ) * (PrevZ - itr->m_BlockZ); + len = 3 * (int)sqrt((double)len) / 4; + int Rad = std::min(MAX_RADIUS, std::max(MIN_RADIUS, (PrevR + itr->m_Radius) / 2 + (Random % 3) - 1)); + Random /= 4; + int x = (itr->m_BlockX + PrevX) / 2 + (Random % (len + 1) - len / 2); + Random /= 256; + int y = (itr->m_BlockY + PrevY) / 2 + (Random % (len / 2 + 1) - len / 4); + Random /= 256; + int z = (itr->m_BlockZ + PrevZ) / 2 + (Random % (len + 1) - len / 2); + Pts.push_back(cCaveDefPoint(x, y, z, Rad)); + Pts.push_back(*itr); + PrevX = itr->m_BlockX; + PrevY = itr->m_BlockY; + PrevZ = itr->m_BlockZ; + PrevR = itr->m_Radius; + } + std::swap(Pts, m_Points); + } +} + + + + + +bool cCaveTunnel::RefineDefPoints(const cCaveDefPoints & a_Src, cCaveDefPoints & a_Dst) +{ + // Smoothing: for each line segment, add points on its 1/4 lengths + bool res = false; + int Num = a_Src.size() - 2; // this many intermediary points + a_Dst.clear(); + a_Dst.reserve(Num * 2 + 2); + cCaveDefPoints::const_iterator itr = a_Src.begin() + 1; + a_Dst.push_back(a_Src.front()); + int PrevX = a_Src.front().m_BlockX; + int PrevY = a_Src.front().m_BlockY; + int PrevZ = a_Src.front().m_BlockZ; + int PrevR = a_Src.front().m_Radius; + for (int i = 0; i <= Num; ++i, ++itr) + { + int dx = itr->m_BlockX - PrevX; + int dy = itr->m_BlockY - PrevY; + int dz = itr->m_BlockZ - PrevZ; + if (abs(dx) + abs(dz) + abs(dy) < 6) + { + // Too short a segment to smooth-subdivide into quarters + PrevX = itr->m_BlockX; + PrevY = itr->m_BlockY; + PrevZ = itr->m_BlockZ; + PrevR = itr->m_Radius; + continue; + } + int dr = itr->m_Radius - PrevR; + int Rad1 = std::max(PrevR + 1 * dr / 4, 1); + int Rad2 = std::max(PrevR + 3 * dr / 4, 1); + a_Dst.push_back(cCaveDefPoint(PrevX + 1 * dx / 4, PrevY + 1 * dy / 4, PrevZ + 1 * dz / 4, Rad1)); + a_Dst.push_back(cCaveDefPoint(PrevX + 3 * dx / 4, PrevY + 3 * dy / 4, PrevZ + 3 * dz / 4, Rad2)); + PrevX = itr->m_BlockX; + PrevY = itr->m_BlockY; + PrevZ = itr->m_BlockZ; + PrevR = itr->m_Radius; + res = true; + } + a_Dst.push_back(a_Src.back()); + return res && (a_Src.size() < a_Dst.size()); +} + + + + + +void cCaveTunnel::Smooth(void) +{ + cCaveDefPoints Pts; + while (true) + { + if (!RefineDefPoints(m_Points, Pts)) + { + std::swap(Pts, m_Points); + return; + } + if (!RefineDefPoints(Pts, m_Points)) + { + return; + } + } +} + + + + + +void cCaveTunnel::FinishLinear(void) +{ + // For each segment, use Bresenham's 3D line algorithm to draw a "line" of defpoints + cCaveDefPoints Pts; + std::swap(Pts, m_Points); + + m_Points.reserve(Pts.size() * 3); + int PrevX = Pts.front().m_BlockX; + int PrevY = Pts.front().m_BlockY; + int PrevZ = Pts.front().m_BlockZ; + for (cCaveDefPoints::const_iterator itr = Pts.begin() + 1, end = Pts.end(); itr != end; ++itr) + { + int x1 = itr->m_BlockX; + int y1 = itr->m_BlockY; + int z1 = itr->m_BlockZ; + int dx = abs(x1 - PrevX); + int dy = abs(y1 - PrevY); + int dz = abs(z1 - PrevZ); + int sx = (PrevX < x1) ? 1 : -1; + int sy = (PrevY < y1) ? 1 : -1; + int sz = (PrevZ < z1) ? 1 : -1; + int 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; skip 3/7 off the top and bottom: + int DifX = itr->m_BlockX - BlockStartX; // substitution for faster calc + int DifY = itr->m_BlockY; + int DifZ = itr->m_BlockZ - BlockStartZ; // substitution for faster calc + int Bottom = std::max(itr->m_BlockY - 3 * itr->m_Radius / 7, 1); + int Top = std::min(itr->m_BlockY + 3 * itr->m_Radius / 7, (int)(cChunkDef::Height)); + int SqRad = itr->m_Radius * itr->m_Radius; + for (int z = 0; z < cChunkDef::Width; z++) for (int x = 0; x < cChunkDef::Width; x++) + { + for (int y = Bottom; y <= Top; y++) + { + int SqDist = (DifX - x) * (DifX - x) + (DifY - y) * (DifY - y) + (DifZ - z) * (DifZ - z); + if (4 * SqDist <= SqRad) + { + switch (cChunkDef::GetBlock(a_BlockTypes, x, y, z)) + { + // Only carve out these specific block types + case E_BLOCK_DIRT: + case E_BLOCK_GRASS: + case E_BLOCK_STONE: + case E_BLOCK_COBBLESTONE: + case E_BLOCK_GRAVEL: + case E_BLOCK_SAND: + case E_BLOCK_SANDSTONE: + case E_BLOCK_NETHERRACK: + case E_BLOCK_COAL_ORE: + case E_BLOCK_IRON_ORE: + case E_BLOCK_GOLD_ORE: + case E_BLOCK_DIAMOND_ORE: + case E_BLOCK_REDSTONE_ORE: + case E_BLOCK_REDSTONE_ORE_GLOWING: + { + cChunkDef::SetBlock(a_BlockTypes, x, y, z, E_BLOCK_AIR); + break; + } + default: break; + } + } + } // for y + } // for x, z + } // for itr - m_Points[] + + /* + #ifdef _DEBUG + // For debugging purposes, outline the shape of the cave using glowstone, *after* carving the entire cave: + for (cCaveDefPoints::const_iterator itr = m_Points.begin(), end = m_Points.end(); itr != end; ++itr) + { + int DifX = itr->m_BlockX - BlockStartX; // substitution for faster calc + int DifZ = itr->m_BlockZ - BlockStartZ; // substitution for faster calc + if ( + (DifX >= 0) && (DifX < cChunkDef::Width) && + (itr->m_BlockY > 0) && (itr->m_BlockY < cChunkDef::Height) && + (DifZ >= 0) && (DifZ < cChunkDef::Width) + ) + { + cChunkDef::SetBlock(a_BlockTypes, DifX, itr->m_BlockY, DifZ, E_BLOCK_GLOWSTONE); + } + } // for itr - m_Points[] + #endif // _DEBUG + //*/ +} + + + + + +#ifdef _DEBUG +AString cCaveTunnel::ExportAsSVG(int a_Color, int a_OffsetX, int a_OffsetZ) const +{ + AString SVG; + SVG.reserve(m_Points.size() * 20 + 200); + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#%06x;stroke-width:1px;\"\nd=\"", a_Color); + char Prefix = 'M'; // The first point needs "M" prefix, all the others need "L" + for (cCaveDefPoints::const_iterator itr = m_Points.begin(); itr != m_Points.end(); ++itr) + { + AppendPrintf(SVG, "%c %d,%d ", Prefix, a_OffsetX + itr->m_BlockX, a_OffsetZ + itr->m_BlockZ); + Prefix = 'L'; + } + SVG.append("\"/>\n"); + return SVG; +} +#endif // _DEBUG + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenWormNestCaves::cCaveSystem: + +cStructGenWormNestCaves::cCaveSystem::cCaveSystem(int a_BlockX, int a_BlockZ, int a_MaxOffset, int a_Size, cNoise & a_Noise) : + m_BlockX(a_BlockX), + m_BlockZ(a_BlockZ), + m_Size(a_Size) +{ + int Num = 1 + a_Noise.IntNoise2DInt(a_BlockX, a_BlockZ) % 3; + for (int i = 0; i < Num; i++) + { + int OriginX = a_BlockX + (a_Noise.IntNoise3DInt(13 * a_BlockX, 17 * a_BlockZ, 11 * i) / 19) % a_MaxOffset; + int OriginZ = a_BlockZ + (a_Noise.IntNoise3DInt(17 * a_BlockX, 13 * a_BlockZ, 11 * i) / 23) % a_MaxOffset; + int OriginY = 20 + (a_Noise.IntNoise3DInt(19 * a_BlockX, 13 * a_BlockZ, 11 * i) / 17) % 20; + + // Generate three branches from the origin point: + // The tunnels generated depend on X, Y, Z and Branches, + // for the same set of numbers it generates the same offsets! + // That's why we add a +1 to X in the third line + GenerateTunnelsFromPoint(OriginX, OriginY, OriginZ, a_Noise, 3); + GenerateTunnelsFromPoint(OriginX, OriginY, OriginZ, a_Noise, 2); + GenerateTunnelsFromPoint(OriginX + 1, OriginY, OriginZ, a_Noise, 3); + } +} + + + + + +cStructGenWormNestCaves::cCaveSystem::~cCaveSystem() +{ + Clear(); +} + + + + + + +void cStructGenWormNestCaves::cCaveSystem::ProcessChunk( + int a_ChunkX, int a_ChunkZ, + cChunkDef::BlockTypes & a_BlockTypes, + cChunkDef::HeightMap & a_HeightMap +) +{ + for (cCaveTunnels::const_iterator itr = m_Tunnels.begin(), end = m_Tunnels.end(); itr != end; ++itr) + { + (*itr)->ProcessChunk(a_ChunkX, a_ChunkZ, a_BlockTypes, a_HeightMap); + } // for itr - m_Tunnels[] +} + + + + + +#ifdef _DEBUG +AString cStructGenWormNestCaves::cCaveSystem::ExportAsSVG(int a_Color, int a_OffsetX, int a_OffsetZ) const +{ + AString SVG; + SVG.reserve(512 * 1024); + for (cCaveTunnels::const_iterator itr = m_Tunnels.begin(), end = m_Tunnels.end(); itr != end; ++itr) + { + SVG.append((*itr)->ExportAsSVG(a_Color, a_OffsetX, a_OffsetZ)); + } // for itr - m_Tunnels[] + + // Base point highlight: + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#ff0000;stroke-width:1px;\"\nd=\"M %d,%d L %d,%d\"/>\n", + a_OffsetX + m_BlockX - 5, a_OffsetZ + m_BlockZ, a_OffsetX + m_BlockX + 5, a_OffsetZ + m_BlockZ + ); + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#ff0000;stroke-width:1px;\"\nd=\"M %d,%d L %d,%d\"/>\n", + a_OffsetX + m_BlockX, a_OffsetZ + m_BlockZ - 5, a_OffsetX + m_BlockX, a_OffsetZ + m_BlockZ + 5 + ); + + // A gray line from the base point to the first point of the ravine, for identification: + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#cfcfcf;stroke-width:1px;\"\nd=\"M %d,%d L %d,%d\"/>\n", + a_OffsetX + m_BlockX, a_OffsetZ + m_BlockZ, + a_OffsetX + m_Tunnels.front()->m_Points.front().m_BlockX, + a_OffsetZ + m_Tunnels.front()->m_Points.front().m_BlockZ + ); + + // Offset guides: + if (a_OffsetX > 0) + { + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#0000ff;stroke-width:1px;\"\nd=\"M %d,0 L %d,1024\"/>\n", + a_OffsetX, a_OffsetX + ); + } + if (a_OffsetZ > 0) + { + AppendPrintf(SVG, "<path style=\"fill:none;stroke:#0000ff;stroke-width:1px;\"\nd=\"M 0,%d L 1024,%d\"/>\n", + a_OffsetZ, a_OffsetZ + ); + } + + return SVG; +} +#endif // _DEBUG + + + + + +void cStructGenWormNestCaves::cCaveSystem::Clear(void) +{ + for (cCaveTunnels::const_iterator itr = m_Tunnels.begin(), end = m_Tunnels.end(); itr != end; ++itr) + { + delete *itr; + } + m_Tunnels.clear(); +} + + + + + +void cStructGenWormNestCaves::cCaveSystem::GenerateTunnelsFromPoint( + int a_OriginX, int a_OriginY, int a_OriginZ, + cNoise & a_Noise, int a_NumSegments +) +{ + int DoubleSize = m_Size * 2; + int Radius = GetRadius(a_Noise, a_OriginX + a_OriginY, a_OriginY + a_OriginZ, a_OriginZ + a_OriginX); + for (int i = a_NumSegments - 1; i >= 0; --i) + { + int EndX = a_OriginX + (((a_Noise.IntNoise3DInt(a_OriginX, a_OriginY, a_OriginZ + 11 * a_NumSegments) / 7) % DoubleSize) - m_Size) / 2; + int EndY = a_OriginY + (((a_Noise.IntNoise3DInt(a_OriginY, 13 * a_NumSegments, a_OriginZ + a_OriginX) / 7) % DoubleSize) - m_Size) / 4; + int EndZ = a_OriginZ + (((a_Noise.IntNoise3DInt(a_OriginZ + 17 * a_NumSegments, a_OriginX, a_OriginY) / 7) % DoubleSize) - m_Size) / 2; + int EndR = GetRadius(a_Noise, a_OriginX + 7 * i, a_OriginY + 11 * i, a_OriginZ + a_OriginX); + m_Tunnels.push_back(new cCaveTunnel(a_OriginX, a_OriginY, a_OriginZ, Radius, EndX, EndY, EndZ, EndR, a_Noise)); + GenerateTunnelsFromPoint(EndX, EndY, EndZ, a_Noise, i); + a_OriginX = EndX; + a_OriginY = EndY; + a_OriginZ = EndZ; + Radius = EndR; + } // for i - a_NumSegments +} + + + + + +int cStructGenWormNestCaves::cCaveSystem::GetRadius(cNoise & a_Noise, int a_OriginX, int a_OriginY, int a_OriginZ) +{ + // Instead of a flat distribution noise function, we need to shape it, so that most caves are smallish and only a few select are large + int rnd = a_Noise.IntNoise3DInt(a_OriginX, a_OriginY, a_OriginZ) / 11; + /* + // Not good enough: + // The algorithm of choice: emulate gauss-distribution noise by adding 3 flat noises, then fold it in half using absolute value. + // To save on processing, use one random value and extract 3 bytes to be separately added as the gaussian noise + int sum = (rnd & 0xff) + ((rnd >> 8) & 0xff) + ((rnd >> 16) & 0xff); + // sum is now a gaussian-distribution noise within [0 .. 767], with center at 384. + // We want mapping 384 -> 3, 0 -> 19, 768 -> 19, so divide by 24 to get [0 .. 31] with center at 16, then use abs() to fold around the center + int res = 3 + abs((sum / 24) - 16); + */ + + // Algorithm of choice: random value in the range of zero to random value - heavily towards zero + int res = MIN_RADIUS + (rnd >> 8) % ((rnd % (MAX_RADIUS - MIN_RADIUS)) + 1); + return res; +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenWormNestCaves: + +cStructGenWormNestCaves::~cStructGenWormNestCaves() +{ + ClearCache(); +} + + + + + +void cStructGenWormNestCaves::ClearCache(void) +{ + for (cCaveSystems::const_iterator itr = m_Cache.begin(), end = m_Cache.end(); itr != end; ++itr) + { + delete *itr; + } // for itr - m_Cache[] + m_Cache.clear(); +} + + + + + +void cStructGenWormNestCaves::GenStructures(cChunkDesc & a_ChunkDesc) +{ + int ChunkX = a_ChunkDesc.GetChunkX(); + int ChunkZ = a_ChunkDesc.GetChunkZ(); + cCaveSystems Caves; + GetCavesForChunk(ChunkX, ChunkZ, Caves); + for (cCaveSystems::const_iterator itr = Caves.begin(); itr != Caves.end(); ++itr) + { + (*itr)->ProcessChunk(ChunkX, ChunkZ, a_ChunkDesc.GetBlockTypes(), a_ChunkDesc.GetHeightMap()); + } // for itr - Caves[] +} + + + + + +void cStructGenWormNestCaves::GetCavesForChunk(int a_ChunkX, int a_ChunkZ, cStructGenWormNestCaves::cCaveSystems & a_Caves) +{ + int BaseX = a_ChunkX * cChunkDef::Width / m_Grid; + int BaseZ = a_ChunkZ * cChunkDef::Width / m_Grid; + if (BaseX < 0) + { + --BaseX; + } + if (BaseZ < 0) + { + --BaseZ; + } + BaseX -= NEIGHBORHOOD_SIZE / 2; + BaseZ -= NEIGHBORHOOD_SIZE / 2; + + // Walk the cache, move each cave system that we want into a_Caves: + int StartX = BaseX * m_Grid; + int EndX = (BaseX + NEIGHBORHOOD_SIZE + 1) * m_Grid; + int StartZ = BaseZ * m_Grid; + int EndZ = (BaseZ + NEIGHBORHOOD_SIZE + 1) * m_Grid; + for (cCaveSystems::iterator itr = m_Cache.begin(), end = m_Cache.end(); itr != end;) + { + if ( + ((*itr)->m_BlockX >= StartX) && ((*itr)->m_BlockX < EndX) && + ((*itr)->m_BlockZ >= StartZ) && ((*itr)->m_BlockZ < EndZ) + ) + { + // want + a_Caves.push_back(*itr); + itr = m_Cache.erase(itr); + } + else + { + // don't want + ++itr; + } + } // for itr - m_Cache[] + + for (int x = 0; x < NEIGHBORHOOD_SIZE; x++) + { + int RealX = (BaseX + x) * m_Grid; + for (int z = 0; z < NEIGHBORHOOD_SIZE; z++) + { + int RealZ = (BaseZ + z) * m_Grid; + bool Found = false; + for (cCaveSystems::const_iterator itr = a_Caves.begin(), end = a_Caves.end(); itr != end; ++itr) + { + if (((*itr)->m_BlockX == RealX) && ((*itr)->m_BlockZ == RealZ)) + { + Found = true; + break; + } + } + if (!Found) + { + a_Caves.push_back(new cCaveSystem(RealX, RealZ, m_MaxOffset, m_Size, m_Noise)); + } + } + } + + // Copy a_Caves into m_Cache to the beginning: + cCaveSystems CavesCopy(a_Caves); + m_Cache.splice(m_Cache.begin(), CavesCopy, CavesCopy.begin(), CavesCopy.end()); + + // Trim the cache if it's too long: + if (m_Cache.size() > 100) + { + cCaveSystems::iterator itr = m_Cache.begin(); + std::advance(itr, 100); + for (cCaveSystems::iterator end = m_Cache.end(); itr != end; ++itr) + { + delete *itr; + } + itr = m_Cache.begin(); + std::advance(itr, 100); + m_Cache.erase(itr, m_Cache.end()); + } + + /* + // Uncomment this block for debugging the caves' shapes in 2D using an SVG export + #ifdef _DEBUG + AString SVG; + SVG.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"1024\" height = \"1024\">\n"); + SVG.reserve(2 * 1024 * 1024); + for (cCaveSystems::const_iterator itr = a_Caves.begin(), end = a_Caves.end(); itr != end; ++itr) + { + int Color = 0x10 * abs((*itr)->m_BlockX / m_Grid); + Color |= 0x1000 * abs((*itr)->m_BlockZ / m_Grid); + SVG.append((*itr)->ExportAsSVG(Color, 512, 512)); + } + SVG.append("</svg>\n"); + + AString fnam; + Printf(fnam, "wnc\\%03d_%03d.svg", a_ChunkX, a_ChunkZ); + cFile File(fnam, cFile::fmWrite); + File.Write(SVG.c_str(), SVG.size()); + #endif // _DEBUG + //*/ +} + + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenMarbleCaves: + +static float GetMarbleNoise( float x, float y, float z, cNoise & a_Noise ) +{ + static const float PI_2 = 1.57079633f; + float oct1 = (a_Noise.CubicNoise3D(x * 0.1f, y * 0.1f, z * 0.1f )) * 4; + + oct1 = oct1 * oct1 * oct1; + if (oct1 < 0.f) oct1 = PI_2; + if (oct1 > PI_2) oct1 = PI_2; + + return oct1; +} + + + + + +void cStructGenMarbleCaves::GenStructures(cChunkDesc & a_ChunkDesc) +{ + cNoise Noise(m_Seed); + for (int z = 0; z < cChunkDef::Width; z++) + { + const float zz = (float)(a_ChunkDesc.GetChunkZ() * cChunkDef::Width + z); + for (int x = 0; x < cChunkDef::Width; x++) + { + const float xx = (float)(a_ChunkDesc.GetChunkX() * cChunkDef::Width + x); + + int Top = a_ChunkDesc.GetHeight(x, z); + for (int y = 1; y < Top; ++y ) + { + if (a_ChunkDesc.GetBlockType(x, y, z) != E_BLOCK_STONE) + { + continue; + } + + const float yy = (float)y; + const float WaveNoise = 1; + if (cosf(GetMarbleNoise(xx, yy * 0.5f, zz, Noise)) * fabs(cosf(yy * 0.2f + WaveNoise * 2) * 0.75f + WaveNoise) > 0.0005f) + { + a_ChunkDesc.SetBlockType(x, y, z, E_BLOCK_AIR); + } + } // for y + } // for x + } // for z +} + + + + + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// cStructGenDualRidgeCaves: + +void cStructGenDualRidgeCaves::GenStructures(cChunkDesc & a_ChunkDesc) +{ + for (int z = 0; z < cChunkDef::Width; z++) + { + const float zz = (float)(a_ChunkDesc.GetChunkZ() * cChunkDef::Width + z) / 10; + for (int x = 0; x < cChunkDef::Width; x++) + { + const float xx = (float)(a_ChunkDesc.GetChunkX() * cChunkDef::Width + x) / 10; + + int Top = a_ChunkDesc.GetHeight(x, z); + for (int y = 1; y <= Top; ++y) + { + const float yy = (float)y / 10; + const float WaveNoise = 1; + float n1 = m_Noise1.CubicNoise3D(xx, yy, zz); + float n2 = m_Noise2.CubicNoise3D(xx, yy, zz); + float n3 = m_Noise1.CubicNoise3D(xx * 4, yy * 4, zz * 4) / 4; + float n4 = m_Noise2.CubicNoise3D(xx * 4, yy * 4, zz * 4) / 4; + if ((abs(n1 + n3) * abs(n2 + n4)) > m_Threshold) + { + a_ChunkDesc.SetBlockType(x, y, z, E_BLOCK_AIR); + } + } // for y + } // for x + } // for z +} + + + + |