Skip to content

Instantly share code, notes, and snippets.

@Jordach
Created July 20, 2022 23:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Jordach/6d9f08aef70100b49507e05257ddb70e to your computer and use it in GitHub Desktop.
Save Jordach/6d9f08aef70100b49507e05257ddb70e to your computer and use it in GitHub Desktop.
/*
Minetest
Copyright (C) 2022 Jordach <jordach.snelling@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "mapgen.h"
#include <cmath>
#include "util/numeric.h"
#include "voxel.h"
#include "noise.h"
#include "mapblock.h"
#include "mapnode.h"
#include "map.h"
#include "nodedef.h"
#include "voxelalgorithms.h"
//#include "profiler.h" // For TimeTaker
#include "settings.h" // For g_settings
#include "emerge.h"
#include "dungeongen.h"
#include "cavegen.h"
#include "mg_biome.h"
#include "mg_ore.h"
#include "mg_decoration.h"
#include "mapgen_reverb.h"
#include <sstream>
FlagDesc flagdesc_mapgen_reverb[] = {
{"rivers", MGREVERB_RIVERS},
{"test", MGREVERB_TEST},
};
// Init
MapgenReverb::MapgenReverb(MapgenReverbParams *params, EmergeParams *emerge)
: MapgenBasic(MAPGEN_REVERB, params, emerge)
{
// Set data during constructor
spflags = params->spflags;
large_cave_depth = params->large_cave_depth;
small_cave_num_min = params->small_cave_num_min;
small_cave_num_max = params->small_cave_num_max;
large_cave_num_min = params->large_cave_num_min;
large_cave_num_max = params->large_cave_num_max;
large_cave_flooded = params->large_cave_flooded;
cavern_limit = params->cavern_limit;
cavern_taper = params->cavern_taper;
cavern_threshold = params->cavern_threshold;
dungeon_ymin = params->dungeon_ymin;
dungeon_ymax = params->dungeon_ymax;
// Overworld height controls
seabed_limit = params->seabed_limit;
water_level = params->water_level;
hills_max = params->hills_max;
plains_max = params->plains_max;
ridges_max = params->ridges_max;
mountains_max = params->mountains_max;
// 2D noise
noise_world_height = new Noise(&params->np_reverb_world_height, seed, csize.X, csize.Z);
noise_hills = new Noise(&params->np_reverb_hills, seed, csize.X, csize.Z);
noise_plains = new Noise(&params->np_reverb_plains, seed, csize.X, csize.Z);
noise_mountains = new Noise(&params->np_reverb_mountains, seed, csize.X, csize.Z);
noise_ridges = new Noise(&params->np_reverb_ridges, seed, csize.X, csize.Z);
noise_detail = new Noise(&params->np_reverb_detail, seed, csize.X, csize.Y, csize.Z);
noise_cliffs = new Noise(&params->np_reverb_cliffs, seed, csize.X, csize.Z);
// 3D noise
noise_cave_modulator = new Noise(&params->np_reverb_cave_modulator, seed, csize.X, csize.Y + 1, csize.Z);
// River data
ReverbVoronoi voronoi;
// Plenty of points that should find points above 512 nodes
// It seems extreme at startup but this step is only run once.
river_points = params->river_points;
if (river_points > 32)
warningstream << "Using more than 32 river points dramatically increases mapgen startup time.";
world_voronoi = voronoi.generatePoints(1024*river_points, seed, 14, 31007, 31007);
// River controls
river_source_point_limit = params->river_source_point_limit;
river_width_min = params->river_width_min;
river_width_max = params->river_width_max;
river_depth_min = params->river_depth_min;
river_depth_max = params->river_depth_max;
river_source_height = params->river_source_height;
river_blend_width = params->river_blend_width;
// Custom caves and noise
MapgenBasic::np_cave1 = params->np_cave1;
MapgenBasic::np_cave2 = params->np_cave2;
MapgenBasic::np_cavern = params->np_cavern;
noise_cave1 = new Noise(&params->np_cave1, seed, csize.X, csize.Y + 1, csize.Z);
noise_cave2 = new Noise(&params->np_cave2, seed, csize.X, csize.Y + 1, csize.Z);
noise_cavern = new Noise(&params->np_cavern, seed, csize.X, csize.Y + 1, csize.Z);
cave_tunnels_min_width = params->cave_tunnels_min_width;
cave_tunnels_max_width = params->cave_tunnels_max_width;
cave_tunnels_min_height = params->cave_tunnels_min_height;
cave_tunnels_max_height = params->cave_tunnels_max_height;
// Special
noise_filler_depth = new Noise(&params->np_filler_depth, seed, csize.X, csize.Z);
MapgenBasic::np_dungeons = params->np_reverb_dungeon;
}
// Free memory when no longer needed, i.e. exiting Minetest
MapgenReverb::~MapgenReverb()
{
delete noise_world_height;
delete noise_hills;
delete noise_plains;
delete noise_mountains;
delete noise_ridges;
delete noise_detail;
delete noise_cliffs;
delete noise_cave_modulator;
}
// Load default settings in case of missing settings in minetest.conf
MapgenReverbParams::MapgenReverbParams():
// Surface noise
np_reverb_world_height (0.0f, 1.0f, v3f(24000, 24000, 24000), 32, 8, 0.5f, 2.1f, NOISE_FLAG_EASED),
np_reverb_hills (0.0f, 1.0f, v3f(1000, 1000, 1000), 1, 8, 0.4f, 2.3f),
np_reverb_plains (0.0f, 1.0f, v3f(6000, 6000, 6000), 4, 3, 0.4f, 1.4f, NOISE_FLAG_EASED),
np_reverb_mountains (0.0f, 1.0f, v3f(3500, 3500, 3500), 11, 8, 0.5f, 1.8f),
np_reverb_ridges (0.0f, 1.0f, v3f(1000, 1000, 1000), 5, 8, 0.4f, 2.3f),
np_reverb_detail (0.0f, 1.0f, v3f(10, 10, 10), 2, 3, 0.35f, 2.3f, NOISE_FLAG_EASED),
np_reverb_cliffs (0.0f, 1.0f, v3f(1000, 1000, 1000), 10, 6, 0.5f, 2.0f),
// Caves
np_cave1 (0.0f, 12.0f, v3f(61, 15, 61), 6, 3, 0.5f, 2.0f),
np_cave2 (0.0f, 12.0f, v3f(67, 67, 67), 7, 3, 0.5f, 2.0f),
np_cavern (0.0f, 1.0f, v3f(384, 384, 384), 8, 5, 0.63f, 2.0f),
np_reverb_cave_modulator (0.0f, 1.0f, v3f(10000, 10000, 10000), 9, 8, 0.5f, 2.1f),
// Special
np_filler_depth (0.0f, 1.0f, v3f(128, 128, 128), 261, 3, 0.7f, 2.0f),
np_reverb_dungeon (0.9f, 0.5f, v3f(500, 500, 500), 0, 2, 0.8f, 2.0f)
{
}
void MapgenReverbParams::readParams(const Settings *settings)
{
settings->getFlagStrNoEx("mgreverb_spflags", spflags, flagdesc_mapgen_reverb);
// Surface settings
settings->getS16NoEx("mgreverb_seabed_limit", seabed_limit);
settings->getS16NoEx("mgreverb_water_level", water_level);
settings->getS16NoEx("mgreverb_hills_max", hills_max);
settings->getS16NoEx("mgreverb_plains_max", plains_max);
settings->getS16NoEx("mgreverb_ridges_max", ridges_max);
settings->getS16NoEx("mgreverb_mountains_max", mountains_max);
// Surface noise
settings->getNoiseParams("mgreverb_np_world_height", np_reverb_world_height);
settings->getNoiseParams("mgreverb_np_hills", np_reverb_hills);
settings->getNoiseParams("mgreverb_np_plains", np_reverb_plains);
settings->getNoiseParams("mgreverb_np_mountains", np_reverb_mountains);
settings->getNoiseParams("mgreverb_np_ridges", np_reverb_ridges);
settings->getNoiseParams("mgreverb_np_detail", np_reverb_detail);
settings->getNoiseParams("mgreverb_np_cliffs", np_reverb_cliffs);
// Caves
settings->getNoiseParams("mgreverb_np_cave1", np_cave1);
settings->getNoiseParams("mgreverb_np_cave2", np_cave2);
settings->getNoiseParams("mgreverb_np_cavern", np_cavern);
settings->getNoiseParams("mgreverb_np_cave_modulator", np_reverb_cave_modulator);
settings->getS16NoEx("mgreverb_large_cave_depth", large_cave_depth);
settings->getU16NoEx("mgreverb_small_cave_num_min", small_cave_num_min);
settings->getU16NoEx("mgreverb_small_cave_num_max", small_cave_num_max);
settings->getU16NoEx("mgreverb_large_cave_num_min", large_cave_num_min);
settings->getU16NoEx("mgreverb_large_cave_num_max", large_cave_num_max);
settings->getFloatNoEx("mgreverb_large_cave_flooded", large_cave_flooded);
settings->getS16NoEx("mgreverb_cavern_limit", cavern_limit);
settings->getS16NoEx("mgreverb_cavern_taper", cavern_taper);
settings->getFloatNoEx("mgreverb_cavern_threshold", cavern_threshold);
settings->getS16NoEx("mgreverb_dungeon_ymin", dungeon_ymin);
settings->getS16NoEx("mgreverb_dungeon_ymax", dungeon_ymax);
// Rivers
settings->getS16NoEx("mgreverb_num_river_nodes", river_points);
settings->getS16NoEx("mgreverb_num_rivers", river_source_point_limit);
settings->getFloatNoEx("mgreverb_river_width_min", river_width_min);
settings->getFloatNoEx("mgreverb_river_width_max", river_width_max);
settings->getS16NoEx("mgreverb_river_depth_min", river_depth_min);
settings->getS16NoEx("mgreverb_river_depth_max", river_depth_max);
settings->getS16NoEx("mgreverb_river_source_height", river_source_height);
settings->getS16NoEx("mgreverb_river_blend_width", river_blend_width);
}
void MapgenReverbParams::writeParams(Settings *settings) const
{
settings->setFlagStr("mgreverb_spflags", spflags, flagdesc_mapgen_reverb);
// Surface settings
settings->setS16("mgreverb_seabed_limit", seabed_limit);
settings->setS16("mgreverb_water_level", water_level);
settings->setS16("mgreverb_hills_max", hills_max);
settings->setS16("mgreverb_plains_max", plains_max);
settings->setS16("mgreverb_ridges_max", ridges_max);
settings->setS16("mgreverb_mountains_max", mountains_max);
// Surface noise
settings->setNoiseParams("mgreverb_np_world_height", np_reverb_world_height);
settings->setNoiseParams("mgreverb_np_hills", np_reverb_hills);
settings->setNoiseParams("mgreverb_np_plains", np_reverb_plains);
settings->setNoiseParams("mgreverb_np_mountains", np_reverb_mountains);
settings->setNoiseParams("mgreverb_np_ridges", np_reverb_ridges);
settings->setNoiseParams("mgreverb_np_detail", np_reverb_detail);
settings->setNoiseParams("mgreverb_np_cliffs", np_reverb_cliffs);
// Caves
settings->setNoiseParams("mgreverb_np_cave1", np_cave1);
settings->setNoiseParams("mgreverb_np_cave2", np_cave2);
settings->setNoiseParams("mgreverb_np_cavern", np_cavern);
settings->setNoiseParams("mgreverb_np_cave_modulator", np_reverb_cave_modulator);
settings->setS16("mgreverb_large_cave_depth", large_cave_depth);
settings->setU16("mgreverb_small_cave_num_min", small_cave_num_min);
settings->setU16("mgreverb_small_cave_num_max", small_cave_num_max);
settings->setU16("mgreverb_large_cave_num_min", large_cave_num_min);
settings->setU16("mgreverb_large_cave_num_max", large_cave_num_max);
settings->setFloat("mgreverb_large_cave_flooded", large_cave_flooded);
settings->setS16("mgreverb_cavern_limit", cavern_limit);
settings->setS16("mgreverb_cavern_taper", cavern_taper);
settings->setFloat("mgreverb_cavern_threshold", cavern_threshold);
settings->setS16("mgreverb_dungeon_ymin", dungeon_ymin);
settings->setS16("mgreverb_dungeon_ymax", dungeon_ymax);
// Rivers
settings->setS16("mgreverb_num_river_sources", river_points);
settings->setS16("mgreverb_num_rivers", river_source_point_limit);
settings->setFloat("mgreverb_river_width_min", river_width_min);
settings->setFloat("mgreverb_river_width_max", river_width_max);
settings->setS16("mgreverb_river_depth_min", river_depth_min);
settings->setS16("mgreverb_river_depth_max", river_depth_max);
settings->setS16("mgreverb_river_source_height", river_source_height);
settings->setS16("mgreverb_river_blend_width", river_blend_width);
}
void MapgenReverbParams::setDefaultSettings(Settings *settings)
{
settings->setDefault("mgreverb_spflags", flagdesc_mapgen_reverb, MGREVERB_RIVERS);
}
int MapgenReverb::getBaseLevelAtPoint(v2s16 p)
{
// Special case handling for 2d points
if (p.X == -31010 || p.X == 31010)
return 31010;
if (p.Y == -31010 || p.Y == 31010)
return 31010;
// Mix low and high terrain based on total heightmap
f32 world_height_y = remap(NoisePerlin2D(&noise_world_height->np, p.X, p.Y, seed), -2, 2, 0, 1);
// Mix hills and plains together
f32 hill_y = remap(NoisePerlin2D(&noise_hills->np, p.X, p.Y, seed), -2, 2, seabed_limit, hills_max);
f32 plains_y = remap(NoisePerlin2D(&noise_plains->np, p.X, p.Y, seed), -2, 2, seabed_limit, plains_max);
f32 hill_plains_y = lerp(plains_y, hill_y, world_height_y);
// Mix ridges and mountains together
f32 mountain_y = remap(NoisePerlin2D(&noise_mountains->np, p.X, p.Y, seed), -2, 2, seabed_limit, mountains_max);
f32 ridges_y = remap(NoisePerlin2D(&noise_ridges->np, p.X, p.Y, seed), -2, 2, seabed_limit, ridges_max);
f32 mountain_ridge_y = (mountain_y) - ridges_y;
// Use an S shaped curve to smooth out mountains and low lying land transitions
float terrain_mix_factor = quadratic_function(world_height_y, 0.002f, 0.02f, 0.44f);
terrain_mix_factor = clamp(terrain_mix_factor, 0, 1);
f32 mixed_terrain_y = lerp(hill_plains_y - 64, mountain_ridge_y, terrain_mix_factor);
// Underwater trenches
f32 trenches_y = ridges_y * 0.5;
f32 trenches_mix = clamp(
remap(mixed_terrain_y, seabed_limit, seabed_limit * 0.8, 0, 1) * -1, 0, 1);
f32 result_y = lerp(mixed_terrain_y, mixed_terrain_y+trenches_y, trenches_mix);
// Prevent oceans sinking below -256 causing CID_IGNORE generation
if (result_y < seabed_limit)
result_y = seabed_limit;
return int(result_y);
}
void MapgenReverb::calculateRivers()
{
// We do the following at startup, but never beyond that:
// Rivers as a data structure generally tries to look like this:
// river[river_source][river_points] v3s16(x, y, z)
// So each river "source" only flows a single direction towards an ocean and never tries
// to attempt an uphill gradient.
//river[river_source][river_points == 0] Will always be the river "source" position.
// 1) Find Points that are the highest on the map at a specified height
// 2) Find Points that are closest and lower on the map than the current point.
// 3) Mark the lowest point's Y value lower than the last one and add it to a new list.
// 4) Repeat from step 2, and continue until we reach sea level.
// 5) Start over from step 1 until all potential rivers have been exhausted.
actionstream << "mg_reverb river pregeneration started, may take a while." << std::endl;
if (river_source_point_limit > 64)
warningstream << "mg_reverb using more than 64 total rivers may cause long pre-generation times." << std::endl;
// Add all river sources to the base river vector
// c=4 skips the world corners fix for Voronoi.
for (s32 c=4; c<world_voronoi.size(); c++) {
s16 height_map_source = getBaseLevelAtPoint(v2s16(world_voronoi[c].X, world_voronoi[c].Z));
if (height_map_source >= MAX_MAP_GENERATION_LIMIT)
continue;
// Hard limit the number of river sources
if (rivers.size() == river_source_point_limit)
break;
if (height_map_source > river_source_height) {
v3s16 source_pos = v3s16(world_voronoi[c].X, height_map_source, world_voronoi[c].Z);
rivers.push_back(std::vector<v3s16>({source_pos}));
actionstream << "mg_reverb river source n=" << c << " at: x=" << world_voronoi[c].X <<
" y=" << height_map_source <<
" z=" << world_voronoi[c].Z << std::endl;
}
}
// Find all child points that a river source (and future children) may flow to
for (s32 r=0; r<rivers.size(); r++) {
// Keep the current point in mind during our search
s32 river_point = 0;
s16 xmin = rivers[r][river_point].X;
s16 zmin = rivers[r][river_point].Z;
s16 xmax = rivers[r][river_point].X;
s16 zmax = rivers[r][river_point].Z;
if (r == (rivers.size() / 2) - 1)
actionstream << "mg_reverb river generation halfway complete." << std::endl;
if (r == (rivers.size() / 4) - 1)
actionstream << "mg_reverb river generation one quarter complete." << std::endl;
if (r == ((rivers.size() / 4) * 3) - 1)
actionstream << "mg_reverb river generation three quarters complete." << std::endl;
while (true) {
bool break_infinite_loop = false;
v2s16 prev_point;
if (river_point > 0)
prev_point = v2s16(rivers[r][river_point-1].X, rivers[r][river_point-1].Z);
v2s16 current_point = v2s16(rivers[r][river_point].X, rivers[r][river_point].Z);
s16 current_height = rivers[r][river_point].Y;
v2s16 best_point = v2s16(128000, 128000);
s16 best_height = 31010;
bool point_found = false;
// c=4 skips the world corners fix for Voronoi
// Loop through all points to find the following:
// A lower or equal height point, and must be closer
for (s32 c = 4; c < world_voronoi.size(); c++) {
v2s16 next_point = v2s16(world_voronoi[c].X, world_voronoi[c].Z);
s16 next_height = getBaseLevelAtPoint(next_point);
// Make 31007 appear as sea level due to getBaseLevelAtPoint();
if (next_height == MAX_MAP_GENERATION_LIMIT)
next_height = 0;
// Skip this iteration if it's between MAX_MAP_GENERATION_LIMIT
// or the current points height, and the 31010 error height
if (next_height > current_height || next_height == 31010)
continue;
// Do not allow two of the same points to measure distance,
// otherwise it will always prefer itself
if (current_point == next_point || next_point == best_point)
continue;
f32 next_dist = distance(current_point, next_point);
f32 best_dist = distance(current_point, best_point);
// Special hacks to mapgen startup; we never want to use this vertex.
if (river_point == 0 && c == 4)
best_dist = 128000;
// Check if this point is closer and lower than the last point
if (next_height < current_height)
if (next_dist < best_dist) {
best_point = v2s16(next_point.X, next_point.Y);
best_height = (next_height) * 1;
point_found = true;
}
}
// Prevent infinite loops
if (!point_found) {
actionstream << "mg_reverb potential infinite loop on river generation, id=" << r << std::endl;
river_ignore.push_back(r);
break;
}
// Prevent corner voronoi points interfering
if (best_height < MAX_MAP_GENERATION_LIMIT) {
v3s16 point_pos = v3s16(best_point.X, best_height, best_point.Y);
rivers[r].push_back(point_pos);
river_point++;
// Find the bounding box of the entire river
if (best_point.X <= xmin)
xmin = best_point.X;
if (best_point.Y <= zmin)
zmin = best_point.Y;
if (best_point.X > xmax)
xmax = best_point.X;
if (best_point.Y > zmax)
zmax = best_point.Y;
if (best_height <= 0) {
// Add offset for terrain blending rivers.
xmin -= (river_blend_width * 3);
zmin -= (river_blend_width * 3);
xmax += (river_blend_width * 3);
zmin += (river_blend_width * 3);
break;
}
}
}
river_bounds_min.push_back(v2s16(xmin, zmin));
river_bounds_max.push_back(v2s16(xmax, zmax));
}
actionstream << "mg_reverb generated " << rivers.size() << " rivers!" << std::endl;
river_vertexes_generated = true;
}
int MapgenReverb::getSpawnLevelAtPoint(v2s16 p)
{
// Special case handling for 2d points
if (p.X == -31010 || p.X == 31010)
return 31010;
if (p.Y == -31010 || p.Y == 31010)
return 31010;
// Mix low and high terrain based on total heightmap
f32 world_height_y = remap(NoisePerlin2D(&noise_world_height->np, p.X, p.Y, seed), -2, 2, 0, 1);
// Mix hills and plains together
f32 hill_y = remap(NoisePerlin2D(&noise_hills->np, p.X, p.Y, seed), -2, 2, seabed_limit, hills_max);
f32 plains_y = remap(NoisePerlin2D(&noise_plains->np, p.X, p.Y, seed), -2, 2, seabed_limit, plains_max);
f32 hill_plains_y = lerp(plains_y, hill_y, world_height_y);
// Mix ridges and mountains together
f32 mountain_y = remap(NoisePerlin2D(&noise_mountains->np, p.X, p.Y, seed), -2, 2, seabed_limit, mountains_max);
f32 ridges_y = remap(NoisePerlin2D(&noise_ridges->np, p.X, p.Y, seed), -2, 2, seabed_limit, ridges_max);
f32 mountain_ridge_y = (mountain_y) - ridges_y;
// Use an S shaped curve to smooth out mountains and low lying land transitions
float terrain_mix_factor = quadratic_function(world_height_y, 0.002f, 0.02f, 0.44f);
terrain_mix_factor = clamp(terrain_mix_factor, 0, 1);
f32 mixed_terrain_y = lerp(hill_plains_y - 64, mountain_ridge_y, terrain_mix_factor);
// Underwater trenches
f32 trenches_y = ridges_y * 0.5;
f32 trenches_mix = clamp(
remap(mixed_terrain_y, seabed_limit, seabed_limit * 0.8, 0, 1) * -1, 0, 1);
f32 result_y = lerp(mixed_terrain_y, mixed_terrain_y+trenches_y, trenches_mix);
// Prevent oceans sinking below -256 causing CID_IGNORE generation
if (result_y < seabed_limit)
result_y = seabed_limit;
// HEY DINGUS FACTOR RIVER BLENDING
if (result_y < 1)
return MAX_MAP_GENERATION_LIMIT; // Invalid location to spawn i.e. underwater
return int(result_y+2);
}
void MapgenReverb::makeChunk(BlockMakeData *data)
{
// Setup for C++ LVM
assert(data->vmanip);
assert(data->nodedef);
//TimeTaker t("makeChunk");
this->generating = true;
this->vm = data->vmanip;
this->ndef = data->nodedef;
// Figure out how big our generating area is
v3s16 blockpos_min = data->blockpos_min;
v3s16 blockpos_max = data->blockpos_max;
node_min = blockpos_min * MAP_BLOCKSIZE;
node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE;
full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1);
// Seed related things;
blockseed = getBlockSeed2(full_node_min, seed);
// Identify rivers based on point distribution on first run
if (!river_vertexes_generated)
calculateRivers();
// Generate stone
s16 stone_surface_max_y = generateTerrain();
// Create heightmap
updateHeightmap(node_min, node_max);
// Place biomes, build a biomemap and other game/mod related surface features
if (flags & MG_BIOMES) {
biomegen->calcBiomeNoise(node_min);
generateBiomes();
}
if (flags & MG_CAVES) {
// Generate tunnels first as caverns confuse them
//generateCavesReverb(stone_surface_max_y, biomemap);
// Generate caverns
bool near_cavern = generateCavernsNoise(stone_surface_max_y);
// Generate large randomwalk caves
if (!near_cavern)
generateCavesRandomWalk(stone_surface_max_y, large_cave_depth);
else
generateCavesRandomWalk(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT);
}
// Generate the registered ores
if (flags & MG_ORES)
m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max);
// Generate dungeons
if (flags & MG_DUNGEONS)
generateDungeons(stone_surface_max_y);
// Generate the registered decorations
if (flags & MG_DECORATIONS)
m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max);
// Sprinkle some dust on top after everything else was generated
if (flags & MG_BIOMES)
dustTopNodes();
// Update liquids
updateLiquid(&data->transforming_liquid, full_node_min, full_node_max);
// Calculate lighting
if (flags & MG_LIGHT) {
calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0),
full_node_min, full_node_max, true);
}
this->generating = false;
}
s16 MapgenReverb::generateTerrain()
{
// World generation limits
s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT;
// Nodes that will be used to fill the terrain
MapNode n_air(CONTENT_AIR);
MapNode n_stone(c_stone);
MapNode n_water(c_water_source);
MapNode n_river_water(c_river_water_source);
// Get noise info for the currently generating area
noise_world_height->perlinMap2D(node_min.X, node_min.Z);
noise_hills ->perlinMap2D(node_min.X, node_min.Z);
noise_plains ->perlinMap2D(node_min.X, node_min.Z);
noise_mountains ->perlinMap2D(node_min.X, node_min.Z);
noise_ridges ->perlinMap2D(node_min.X, node_min.Z);
noise_detail ->perlinMap3D(node_min.X, node_min.Y-1, node_min.Z);
noise_cliffs ->perlinMap2D(node_min.X, node_min.Z);
// Get total area that we can build in
const v3s16 &em = vm->m_area.getExtent();
// Position in the noise flat 2D or 3D array;
u32 index_zx = 0;
// Create a list of known rivers that have their fullsize bounding
// box within node_min and node_max;
std::vector<s16> valid_rivers;
for (s16 s=0; s<rivers.size(); s++) {
// Filter out failed rivers during pregeneration
bool ignored_river = false;
for (int i=0; i<river_ignore.size(); i++) {
if (s == river_ignore[i]) {
ignored_river = true;
break;
}
}
if (ignored_river)
continue;
// Add a bounding box fudge factor to ensure no corner cases with terrain blending
// adding the river_blend_width four times should ensure it blends across
// mapblocks without obvious visual distortion
v2s16 b_min = v2s16(river_bounds_min[s].X - (river_blend_width + river_width_max),
river_bounds_min[s].Y - (river_blend_width + river_width_max));
v2s16 b_max = v2s16(river_bounds_max[s].X + (river_blend_width + river_width_max),
river_bounds_max[s].Y + (river_blend_width + river_width_max));
bool valid = false;
if (isBetweenBounds(b_min, b_max, v2s16(node_min.X, node_min.Z)))
valid = true;
if (isBetweenBounds(b_min, b_max, v2s16(node_max.X, node_min.Z)))
valid = true;
if (isBetweenBounds(b_min, b_max, v2s16(node_min.X, node_max.Z)))
valid = true;
if (isBetweenBounds(b_min, b_max, v2s16(node_min.X, node_max.Z)))
valid = true;
// Add the river ID to the list of valid rivers;
if (valid)
valid_rivers.push_back(s);
}
// ZX loop, starting from the north and then heading east
for (s16 z = node_min.Z; z <= node_max.Z; z++)
for (s16 x = node_min.X; x <= node_max.X; x++, index_zx++) {
// Biome of river
Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index_zx]);
// Mix low and high terrain based on total heightmap
f32 world_height_y = remap(noise_world_height->result[index_zx], -2, 2, 0, 1);
// Mix hills and plains together
f32 hill_y = remap(noise_hills->result[index_zx], -2, 2, seabed_limit, hills_max);
f32 plains_y = remap(noise_plains->result[index_zx], -2, 2, seabed_limit, plains_max);
f32 hill_plains_y = lerp(plains_y, hill_y, world_height_y);
// Mix ridges and mountains together
f32 mountain_y = remap(noise_mountains->result[index_zx], -2, 2, seabed_limit, mountains_max);
f32 ridges_y = remap(noise_ridges->result[index_zx], -2, 2, seabed_limit, ridges_max);
f32 mountain_ridge_y = mountain_y - ridges_y;
// Use an S shaped curve to smooth out mountains and low lying land transitions
//float terrain_mix_factor = sigmoid_function(world_height_y, 9.5f, 1.0f) - 0.15f;
float terrain_mix_factor = quadratic_function(world_height_y, 0.002f, 0.02f, 0.44f);
terrain_mix_factor = clamp(terrain_mix_factor, 0, 1);
f32 mixed_terrain_y = lerp(hill_plains_y - 64, mountain_ridge_y, terrain_mix_factor);
// Underwater trenches
f32 trenches_y = ridges_y * 0.5;
f32 trenches_mix = clamp(
remap(mixed_terrain_y, seabed_limit, seabed_limit * 0.8, 0, 1) * -1, 0, 1);
f32 result_y = lerp(mixed_terrain_y, mixed_terrain_y+trenches_y, trenches_mix);
// Prevent oceans sinking below -256 causing CID_IGNORE generation
if (result_y < seabed_limit)
result_y = seabed_limit;
f32 river_depth = 0.0f;
// Find river columns for x and z;
// skip entirely if there aren't
// any within the bounds, including when
// there aren't any rivers.
bool river_at_xz = false;
if (valid_rivers.size() > 0) {
// Current column
v2f32 xz = v2f32(x, z);
// Loop through all rivers and check if this column is part of it
for (s16 s=0; s<valid_rivers.size(); s++)
for (s16 p=1; p<rivers[valid_rivers[s]].size(); p++) {
// River start and end points;
v3s16 river_point_start = rivers[valid_rivers[s]][p-1];
v3s16 river_point_end = rivers[valid_rivers[s]][p];
// Deconstruct 3d data to 2d;
v2f32 rs = v2f32(river_point_start.X, river_point_start.Z);
v2f32 re = v2f32(river_point_end.X, river_point_end.Z);
// Blending box bounds;
v2f32 bs;
v2f32 be;
if (rs.X < re.X) {
bs.X = rs.X - (river_blend_width + river_width_max + 8);
be.X = re.X + (river_blend_width + river_width_max + 8);
} else {
bs.X = re.X - (river_blend_width + river_width_max + 8);
be.X = rs.X + (river_blend_width + river_width_max + 8);
}
if (rs.Y < re.Y) { // Start is less than end
bs.Y = rs.Y - (river_blend_width + river_width_max + 8);
be.Y = re.Y + (river_blend_width + river_width_max + 8);
} else { // End is less than start
bs.Y = re.Y - (river_blend_width + river_width_max + 8);
be.Y = rs.Y + (river_blend_width + river_width_max + 8);
}
// Skip this river point if we're not between these points
if (!isBetweenBoundsf32(bs, be, xz))
continue;
// Figure out where the mapgen column is relative to the line
// on a perpendicular axis
v2f32 rl = re - rs;
v2f32 rn = rs + rl * (rl.dotProduct(xz-rs) / rl.dotProduct(rl));
// Fix rn exceeding the bounding box (because vector projection will extend the line infinitely)
if (!isBetweenBoundsf32(rs, re, rn)) {
// Are we closer to the start or end point?
if (distancef32(rs, rn) <= distancef32(re, rn))
rn = rs;
else
rn = re;
}
// Get distance between our xz position and the start of the river points
f32 rc_dist = distancef32(xz, rn);
f32 re_dist = distancef32(rs, rn);
// Get length of the two river points
f32 dist = distancef32(rs, re);
// Get distance to the start point for our river
f32 rsdist = distancef32(xz, rs);
f32 xzdist = distancef32(xz, rn);
// Get height of the river from it's 3d vertices
f32 r_blend = remap(re_dist, 0, dist, 0, 1);
f32 river_height = lerp(river_point_start.Y, river_point_end.Y, r_blend);
// Make river width change smoothly based on height
f32 river_width = remap(river_height, water_level, river_source_height, river_width_max, river_width_min);
river_width = clamp(river_width, river_width_min, river_width_max);
// Make rivers have rounded riverbeds
river_depth = remap(river_height, water_level, river_source_height, river_depth_max, river_depth_min);
river_depth = clamp(river_depth, river_depth_min, river_depth_max);
float river_depth_b = remap(xzdist, 0, river_width, 0, 1);
river_depth_b = sigmoid_function(clamp(river_depth_b, 0, 1), 10, 1);
river_depth = lerp(river_depth, 1, river_depth_b);
// Create rounded circles at river pairs (p-1)
if (rsdist <= river_width && river_point_start.Y >= water_level - river_depth_max) {
river_at_xz = true;
result_y = river_height;
}
if (isVertexEdgef32(rs, re, xz, dist * river_width) && isBetweenBoundsf32(rs, re, xz)) {
// Carve the river
river_at_xz = true;
result_y = river_height;
}
if (xzdist >= river_width && xzdist <= (river_blend_width + river_width) && isBetweenBoundsf32(bs, be, xz)) {
// Blend the sides of the river
float point_blend = remap(xzdist, river_width, river_width+river_blend_width, 0, 1);
point_blend = clamp(point_blend, 0, 1);
point_blend = sigmoid_function(point_blend, 10, 1);
if (river_height >= water_level - river_depth_max)
result_y = lerp(river_height, result_y, point_blend);
}
// Blend terrain around the first of the river pairs (p-1)
if (rsdist >= river_width && rsdist <= (river_blend_width + river_width)) {
float point_blend = remap(rsdist, river_width, river_width+river_blend_width, 0, 1);
point_blend = clamp(point_blend, 0, 1);
point_blend = sigmoid_function(point_blend, 10, 1);
if (river_point_start.Y >= water_level - river_depth_max && isBetweenBoundsf32(rs, re, rn))
result_y = lerp(river_point_start.Y, result_y, point_blend);
}
}
}
// We're heightmap based rather than 3D noise based - so we can get away
// with something like this, not because it's silly, but because we already
// know the world height ahead of time.
if (result_y > stone_surface_max_y)
stone_surface_max_y = result_y;
// Y loop - columns starting from the bottom going upwards;
u32 index_y = (z - node_min.Z) * zstride_1u1d + (x - node_min.X);
u32 vi = vm->m_area.index(x, node_min.Y - 1, z);
for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++,
VoxelArea::add_y(em, vi, 1)) {
if (vm->m_data[vi].getContent() != CONTENT_IGNORE)
continue;
if (y <= result_y) {
vm->m_data[vi] = n_stone;
} else {// Air/sea level
if (y > water_level)
vm->m_data[vi] = n_air;
else if (y > seabed_limit)
vm->m_data[vi] = n_water;
}
if (river_at_xz)
if (y >= result_y-river_depth-1 && y < result_y-1 && y >= water_level)
vm->m_data[vi] = n_river_water;
else if (y >= result_y-1 && y >= water_level)
vm->m_data[vi] = n_air;
}
}
return stone_surface_max_y;
}
// Customised cave generation functions for mg_reverb
void MapgenReverb::generateCavesReverb(s16 stone_surface_max_y, biome_t *biomemap)
{
if (node_min.Y > stone_surface_max_y || node_max.Y > stone_surface_max_y)
return;
assert(vm);
assert(biomemap);
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);
noise_cave_modulator->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z);
const v3s16 &em = vm->m_area.getExtent();
u32 index2d = 0; // Biomemap index
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_under_river = false; // Is column under river water
bool is_under_tunnel = false; // Is tunnel or is under tunnel
bool is_top_filler_above = false; // Is top or filler above node
// Indexes at column top
u32 vi = vm->m_area.index(x, node_min.Y, z);
u32 index3d = (z - node_min.Z) * zstride_1d + csize.Y * ystride +
(x - node_min.X); // 3D noise index
// Biome of column
Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index2d]);
u16 depth_riverbed = biome->depth_riverbed;
u16 depth_top = biome->depth_top;
u16 base_filler = depth_top + biome->depth_filler;
u16 nplaced = 0;
// Don't excavate the overgenerated stone at nmax.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,
VoxelArea::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 || c == c_water_source) {
column_is_open = true;
is_top_filler_above = false;
continue;
}
if (c == biome->c_river_water || c == c_river_water_source) {
column_is_open = true;
is_under_river = true;
is_top_filler_above = false;
continue;
}
// Fix stone being placed in the sky due to it being a part of the tunnel
if (y > stone_surface_max_y && c == CONTENT_AIR) {
column_is_open = false;
is_top_filler_above = false;
continue;
}
// Ground
float d1 = contour(noise_cave1->result[index3d]);
float d2 = contour(noise_cave2->result[index3d]);
float d3 = fabs(noise_cave_modulator->result[index3d]) * 0.001;
// Taper tunnel width based on height
float tunnel_width = remap(y, cave_tunnels_min_height, cave_tunnels_max_height,
cave_tunnels_min_width, cave_tunnels_max_width);
// Prevent a rare case of tunnels completely dissipating, or expanding to +inf
if (tunnel_width > cave_tunnels_max_width)
tunnel_width = cave_tunnels_max_width;
else if (tunnel_width < cave_tunnels_min_width)
tunnel_width = cave_tunnels_min_width;
if ((d1 * d2) - d3 > tunnel_width && ndef->get(c).is_ground_content) {
// In tunnel and ground content, excavate
vm->m_data[vi] = MapNode(CONTENT_AIR);
is_under_tunnel = true;
//If tunnel roof is top or filler, replace with stone
if (is_top_filler_above)
vm->m_data[vi + em.X] = MapNode(biome->c_stone);
is_top_filler_above = false;
} else if (column_is_open && is_under_tunnel &&
(c == biome->c_stone || c == biome->c_filler)) {
// Tunnel entrance floor, place biome surface nodes
if (is_under_river) {
if (nplaced < depth_riverbed) {
vm->m_data[vi] = MapNode(biome->c_riverbed);
is_top_filler_above = true;
nplaced++;
} else {
// Disable top/filler placement
column_is_open = false;
is_under_river = false;
is_under_tunnel = false;
}
} else if (nplaced < depth_top) {
vm->m_data[vi] = MapNode(biome->c_top);
is_top_filler_above = true;
nplaced++;
} else if (nplaced < base_filler) {
vm->m_data[vi] = MapNode(biome->c_filler);
is_top_filler_above = true;
nplaced++;
} else {
// Disable top/filler placement
column_is_open = false;
is_under_tunnel = false;
}
} else {
// Not tunnel or tunnel entrance floor
// Check node for possible replacing with stone for tunnel roof
if (c == biome->c_top || c == biome->c_filler)
is_top_filler_above = true;
column_is_open = false;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment