-
-
Save Jordach/6d9f08aef70100b49507e05257ddb70e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
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(¶ms->np_reverb_world_height, seed, csize.X, csize.Z); | |
noise_hills = new Noise(¶ms->np_reverb_hills, seed, csize.X, csize.Z); | |
noise_plains = new Noise(¶ms->np_reverb_plains, seed, csize.X, csize.Z); | |
noise_mountains = new Noise(¶ms->np_reverb_mountains, seed, csize.X, csize.Z); | |
noise_ridges = new Noise(¶ms->np_reverb_ridges, seed, csize.X, csize.Z); | |
noise_detail = new Noise(¶ms->np_reverb_detail, seed, csize.X, csize.Y, csize.Z); | |
noise_cliffs = new Noise(¶ms->np_reverb_cliffs, seed, csize.X, csize.Z); | |
// 3D noise | |
noise_cave_modulator = new Noise(¶ms->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(¶ms->np_cave1, seed, csize.X, csize.Y + 1, csize.Z); | |
noise_cave2 = new Noise(¶ms->np_cave2, seed, csize.X, csize.Y + 1, csize.Z); | |
noise_cavern = new Noise(¶ms->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(¶ms->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