Skip to content

Commit

Permalink
Mapgen: Combine generateBiomes, dustTopNodes, and generateCaves
Browse files Browse the repository at this point in the history
This commit condenses the above methods into a single implementation used by
V7, V5, Flat, Fractal, and Valleys mapgens and introduces MapgenBasic.
  • Loading branch information
kwolekr committed May 28, 2016
1 parent 76f4856 commit 87bc39d
Show file tree
Hide file tree
Showing 12 changed files with 316 additions and 1,161 deletions.
236 changes: 236 additions & 0 deletions src/mapgen.cpp
Expand Up @@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "util/numeric.h"
#include "filesys.h"
#include "log.h"
#include "cavegen.h"

FlagDesc flagdesc_mapgen[] = {
{"trees", MG_TREES},
Expand Down Expand Up @@ -369,6 +370,241 @@ void Mapgen::spreadLight(v3s16 nmin, v3s16 nmax)
}


////
//// MapgenBasic
////

MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge)
: Mapgen(mapgenid, params, emerge)
{

}

MgStoneType MapgenBasic::generateBiomes()
{
v3s16 em = vm->m_area.getExtent();
u32 index = 0;
MgStoneType stone_type = STONE;

for (s16 z = node_min.Z; z <= node_max.Z; z++)
for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
Biome *biome = NULL;
u16 depth_top = 0;
u16 base_filler = 0;
u16 depth_water_top = 0;
u32 vi = vm->m_area.index(x, node_max.Y, z);

// Check node at base of mapchunk above, either a node of a previously
// generated mapchunk or if not, a node of overgenerated base terrain.
content_t c_above = vm->m_data[vi + em.X].getContent();
bool air_above = c_above == CONTENT_AIR;
bool water_above = (c_above == c_water_source || c_above == c_river_water_source);

// If there is air or water above enable top/filler placement, otherwise force
// nplaced to stone level by setting a number exceeding any possible filler depth.
u16 nplaced = (air_above || water_above) ? 0 : U16_MAX;

for (s16 y = node_max.Y; y >= node_min.Y; y--) {
content_t c = vm->m_data[vi].getContent();

// Biome is recalculated each time an upper surface is detected while
// working down a column. The selected biome then remains in effect for
// all nodes below until the next surface and biome recalculation.
// Biome is recalculated:
// 1. At the surface of stone below air or water.
// 2. At the surface of water below air.
// 3. When stone or water is detected but biome has not yet been calculated.
if ((c == c_stone && (air_above || water_above || !biome))
|| ((c == c_water_source || c == c_river_water_source)
&& (air_above || !biome))) {
biome = biomegen->getBiomeAtIndex(index, y);

depth_top = biome->depth_top;
base_filler = MYMAX(depth_top
+ biome->depth_filler
+ noise_filler_depth->result[index], 0.f);
depth_water_top = biome->depth_water_top;

// Detect stone type for dungeons during every biome calculation.
// This is more efficient than detecting per-node and will not
// miss any desert stone or sandstone biomes.
if (biome->c_stone == c_desert_stone)
stone_type = DESERT_STONE;
else if (biome->c_stone == c_sandstone)
stone_type = SANDSTONE;
}

if (c == c_stone) {
content_t c_below = vm->m_data[vi - em.X].getContent();

// If the node below isn't solid, make this node stone, so that
// any top/filler nodes above are structurally supported.
// This is done by aborting the cycle of top/filler placement
// immediately by forcing nplaced to stone level.
if (c_below == CONTENT_AIR
|| c_below == c_water_source
|| c_below == c_river_water_source)
nplaced = U16_MAX;

if (nplaced < depth_top) {
vm->m_data[vi] = MapNode(biome->c_top);
nplaced++;
} else if (nplaced < base_filler) {
vm->m_data[vi] = MapNode(biome->c_filler);
nplaced++;
} else {
vm->m_data[vi] = MapNode(biome->c_stone);
}

air_above = false;
water_above = false;
} else if (c == c_water_source) {
vm->m_data[vi] = MapNode((y > (s32)(water_level - depth_water_top))
? biome->c_water_top : biome->c_water);
nplaced = 0; // Enable top/filler placement for next surface
air_above = false;
water_above = true;
} else if (c == c_river_water_source) {
vm->m_data[vi] = MapNode(biome->c_river_water);
nplaced = depth_top; // Enable filler placement for next surface
air_above = false;
water_above = true;
} else if (c == CONTENT_AIR) {
nplaced = 0; // Enable top/filler placement for next surface
air_above = true;
water_above = false;
} else { // Possible various nodes overgenerated from neighbouring mapchunks
nplaced = U16_MAX; // Disable top/filler placement
air_above = false;
water_above = false;
}

vm->m_area.add_y(em, vi, -1);
}
}

return stone_type;
}


void MapgenBasic::dustTopNodes()
{
if (node_max.Y < water_level)
return;

v3s16 em = vm->m_area.getExtent();
u32 index = 0;

for (s16 z = node_min.Z; z <= node_max.Z; z++)
for (s16 x = node_min.X; x <= node_max.X; x++, index++) {
Biome *biome = (Biome *)bmgr->getRaw(biomemap[index]);

if (biome->c_dust == CONTENT_IGNORE)
continue;

u32 vi = vm->m_area.index(x, full_node_max.Y, z);
content_t c_full_max = vm->m_data[vi].getContent();
s16 y_start;

if (c_full_max == CONTENT_AIR) {
y_start = full_node_max.Y - 1;
} else if (c_full_max == CONTENT_IGNORE) {
vi = vm->m_area.index(x, node_max.Y + 1, z);
content_t c_max = vm->m_data[vi].getContent();

if (c_max == CONTENT_AIR)
y_start = node_max.Y;
else
continue;
} else {
continue;
}

vi = vm->m_area.index(x, y_start, z);
for (s16 y = y_start; y >= node_min.Y - 1; y--) {
if (vm->m_data[vi].getContent() != CONTENT_AIR)
break;

vm->m_area.add_y(em, vi, -1);
}

content_t c = vm->m_data[vi].getContent();
if (!ndef->get(c).buildable_to && c != CONTENT_IGNORE && c != biome->c_dust) {
vm->m_area.add_y(em, vi, 1);
vm->m_data[vi] = MapNode(biome->c_dust);
}
}
}


void MapgenBasic::generateCaves(s16 max_stone_y, s16 large_cave_depth)
{
if (max_stone_y < node_min.Y)
return;

noise_cave1->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
noise_cave2->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);

v3s16 em = vm->m_area.getExtent();
u32 index2d = 0;

for (s16 z = node_min.Z; z <= node_max.Z; z++)
for (s16 x = node_min.X; x <= node_max.X; x++, index2d++) {
bool column_is_open = false; // Is column open to overground
bool is_tunnel = false; // Is tunnel or tunnel floor
u32 vi = vm->m_area.index(x, node_max.Y, z);
u32 index3d = (z - node_min.Z) * zstride_1d + csize.Y * ystride +
(x - node_min.X);
// Biome of column
Biome *biome = (Biome *)bmgr->getRaw(biomemap[index2d]);

// Don't excavate the overgenerated stone at node_max.Y + 1,
// this creates a 'roof' over the tunnel, preventing light in
// tunnels at mapchunk borders when generating mapchunks upwards.
// This 'roof' is removed when the mapchunk above is generated.
for (s16 y = node_max.Y; y >= node_min.Y - 1; y--,
index3d -= ystride,
vm->m_area.add_y(em, vi, -1)) {

content_t c = vm->m_data[vi].getContent();
if (c == CONTENT_AIR || c == biome->c_water_top ||
c == biome->c_water) {
column_is_open = true;
continue;
}
// Ground
float d1 = contour(noise_cave1->result[index3d]);
float d2 = contour(noise_cave2->result[index3d]);

if (d1 * d2 > cave_width && ndef->get(c).is_ground_content) {
// In tunnel and ground content, excavate
vm->m_data[vi] = MapNode(CONTENT_AIR);
is_tunnel = true;
} else {
// Not in tunnel or not ground content
if (is_tunnel && column_is_open &&
(c == biome->c_filler || c == biome->c_stone))
// Tunnel entrance floor
vm->m_data[vi] = MapNode(biome->c_top);

column_is_open = false;
is_tunnel = false;
}
}
}

if (node_max.Y > large_cave_depth)
return;

PseudoRandom ps(blockseed + 21343);
u32 bruises_count = ps.range(0, 2);
for (u32 i = 0; i < bruises_count; i++) {
CaveV5 cave(this, &ps); ////caves version varies ---- todo- fix this!
cave.makeCave(node_min, node_max, max_stone_y);
}
}


////
//// GenerateNotifier
////
Expand Down
56 changes: 56 additions & 0 deletions src/mapgen.h
Expand Up @@ -46,6 +46,7 @@ extern FlagDesc flagdesc_gennotify[];
class Biome;
class BiomeGen;
struct BiomeParams;
class BiomeManager;
class EmergeManager;
class MapBlock;
class VoxelManipulator;
Expand Down Expand Up @@ -137,6 +138,16 @@ struct MapgenParams {
void save(Settings &settings) const;
};


/*
Generic interface for map generators. All mapgens must inherit this class.
If a feature exposed by a public member pointer is not supported by a
certain mapgen, it must be set to NULL.
Apart from makeChunk, getGroundLevelAtPoint, and getSpawnLevelAtPoint, all
methods can be used by constructing a Mapgen base class and setting the
appropriate public members (e.g. vm, ndef, and so on).
*/
class Mapgen {
public:
int seed;
Expand Down Expand Up @@ -189,6 +200,51 @@ class Mapgen {
DISABLE_CLASS_COPY(Mapgen);
};

/*
MapgenBasic is a Mapgen implementation that handles basic functionality
the majority of conventional mapgens will probably want to use, but isn't
generic enough to be included as part of the base Mapgen class (such as
generating biome terrain over terrain node skeletons, generating caves,
dungeons, etc.)
Inherit MapgenBasic instead of Mapgen to add this basic functionality to
your mapgen without having to reimplement it. Feel free to override any of
these methods if you desire different or more advanced behavior.
Note that you must still create your own generateTerrain implementation when
inheriting MapgenBasic.
*/
class MapgenBasic : public Mapgen {
public:
EmergeManager *m_emerge;
BiomeManager *bmgr;

Noise *noise_filler_depth;
Noise *noise_cave1;
Noise *noise_cave2;

v3s16 node_min;
v3s16 node_max;
v3s16 full_node_min;
v3s16 full_node_max;

content_t c_stone;
content_t c_water_source;
content_t c_river_water_source;
content_t c_desert_stone;
content_t c_sandstone;

int ystride;
int zstride_1d;
float cave_width;

MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge);

virtual MgStoneType generateBiomes();
virtual void dustTopNodes();
virtual void generateCaves(s16 max_stone_y, s16 large_cave_depth);
};

struct MapgenFactory {
virtual Mapgen *createMapgen(int mgid, MapgenParams *params,
EmergeManager *emerge) = 0;
Expand Down

0 comments on commit 87bc39d

Please sign in to comment.