diff options
Diffstat (limited to 'src/mapgen')
33 files changed, 11648 insertions, 0 deletions
diff --git a/src/mapgen/CMakeLists.txt b/src/mapgen/CMakeLists.txt new file mode 100644 index 000000000..e74bd85db --- /dev/null +++ b/src/mapgen/CMakeLists.txt @@ -0,0 +1,19 @@ +set(mapgen_SRCS + ${CMAKE_CURRENT_SOURCE_DIR}/cavegen.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dungeongen.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_carpathian.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mapgen.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_flat.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_fractal.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_singlenode.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v5.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v6.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_v7.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mapgen_valleys.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mg_biome.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mg_decoration.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mg_ore.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/mg_schematic.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/treegen.cpp + PARENT_SCOPE +) diff --git a/src/mapgen/cavegen.cpp b/src/mapgen/cavegen.cpp new file mode 100644 index 000000000..e66634517 --- /dev/null +++ b/src/mapgen/cavegen.cpp @@ -0,0 +1,882 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@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 "util/numeric.h" +#include "map.h" +#include "mapgen.h" +#include "mapgen_v5.h" +#include "mapgen_v6.h" +#include "mapgen_v7.h" +#include "mg_biome.h" +#include "cavegen.h" + +static NoiseParams nparams_caveliquids(0, 1, v3f(150.0, 150.0, 150.0), 776, 3, 0.6, 2.0); + + +//// +//// CavesNoiseIntersection +//// + +CavesNoiseIntersection::CavesNoiseIntersection( + INodeDefManager *nodedef, BiomeManager *biomemgr, v3s16 chunksize, + NoiseParams *np_cave1, NoiseParams *np_cave2, s32 seed, float cave_width) +{ + assert(nodedef); + assert(biomemgr); + + m_ndef = nodedef; + m_bmgr = biomemgr; + + m_csize = chunksize; + m_cave_width = cave_width; + + m_ystride = m_csize.X; + m_zstride_1d = m_csize.X * (m_csize.Y + 1); + + // Noises are created using 1-down overgeneration + // A Nx-by-1-by-Nz-sized plane is at the bottom of the desired for + // re-carving the solid overtop placed for blocking sunlight + noise_cave1 = new Noise(np_cave1, seed, m_csize.X, m_csize.Y + 1, m_csize.Z); + noise_cave2 = new Noise(np_cave2, seed, m_csize.X, m_csize.Y + 1, m_csize.Z); +} + + +CavesNoiseIntersection::~CavesNoiseIntersection() +{ + delete noise_cave1; + delete noise_cave2; +} + + +void CavesNoiseIntersection::generateCaves(MMVManip *vm, + v3s16 nmin, v3s16 nmax, u8 *biomemap) +{ + assert(vm); + assert(biomemap); + + noise_cave1->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z); + noise_cave2->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z); + + const v3s16 &em = vm->m_area.getExtent(); + u32 index2d = 0; // Biomemap index + + for (s16 z = nmin.Z; z <= nmax.Z; z++) + for (s16 x = nmin.X; x <= nmax.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, nmax.Y, z); + u32 index3d = (z - nmin.Z) * m_zstride_1d + m_csize.Y * m_ystride + + (x - nmin.X); // 3D noise index + // Biome of column + Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index2d]); + u16 depth_top = biome->depth_top; + u16 base_filler = depth_top + biome->depth_filler; + u16 depth_riverbed = biome->depth_riverbed; + 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 = nmax.Y; y >= nmin.Y - 1; y--, + index3d -= m_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; + is_top_filler_above = false; + continue; + } + + if (c == biome->c_river_water) { + column_is_open = true; + is_under_river = true; + is_top_filler_above = false; + continue; + } + + // Ground + float d1 = contour(noise_cave1->result[index3d]); + float d2 = contour(noise_cave2->result[index3d]); + + if (d1 * d2 > m_cave_width && m_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; + } + } + } +} + + +//// +//// CavernsNoise +//// + +CavernsNoise::CavernsNoise( + INodeDefManager *nodedef, v3s16 chunksize, NoiseParams *np_cavern, + s32 seed, float cavern_limit, float cavern_taper, float cavern_threshold) +{ + assert(nodedef); + + m_ndef = nodedef; + + m_csize = chunksize; + m_cavern_limit = cavern_limit; + m_cavern_taper = cavern_taper; + m_cavern_threshold = cavern_threshold; + + m_ystride = m_csize.X; + m_zstride_1d = m_csize.X * (m_csize.Y + 1); + + // Noise is created using 1-down overgeneration + // A Nx-by-1-by-Nz-sized plane is at the bottom of the desired for + // re-carving the solid overtop placed for blocking sunlight + noise_cavern = new Noise(np_cavern, seed, m_csize.X, m_csize.Y + 1, m_csize.Z); + + c_water_source = m_ndef->getId("mapgen_water_source"); + if (c_water_source == CONTENT_IGNORE) + c_water_source = CONTENT_AIR; + + c_lava_source = m_ndef->getId("mapgen_lava_source"); + if (c_lava_source == CONTENT_IGNORE) + c_lava_source = CONTENT_AIR; +} + + +CavernsNoise::~CavernsNoise() +{ + delete noise_cavern; +} + + +bool CavernsNoise::generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax) +{ + assert(vm); + + // Calculate noise + noise_cavern->perlinMap3D(nmin.X, nmin.Y - 1, nmin.Z); + + // Cache cavern_amp values + float *cavern_amp = new float[m_csize.Y + 1]; + u8 cavern_amp_index = 0; // Index zero at column top + for (s16 y = nmax.Y; y >= nmin.Y - 1; y--, cavern_amp_index++) { + cavern_amp[cavern_amp_index] = + MYMIN((m_cavern_limit - y) / (float)m_cavern_taper, 1.0f); + } + + //// Place nodes + bool near_cavern = false; + const v3s16 &em = vm->m_area.getExtent(); + u32 index2d = 0; + + for (s16 z = nmin.Z; z <= nmax.Z; z++) + for (s16 x = nmin.X; x <= nmax.X; x++, index2d++) { + // Reset cave_amp index to column top + cavern_amp_index = 0; + // Initial voxelmanip index at column top + u32 vi = vm->m_area.index(x, nmax.Y, z); + // Initial 3D noise index at column top + u32 index3d = (z - nmin.Z) * m_zstride_1d + m_csize.Y * m_ystride + + (x - nmin.X); + // Don't excavate the overgenerated stone at node_max.Y + 1, + // this creates a 'roof' over the cavern, preventing light in + // caverns at mapchunk borders when generating mapchunks upwards. + // This 'roof' is excavated when the mapchunk above is generated. + for (s16 y = nmax.Y; y >= nmin.Y - 1; y--, + index3d -= m_ystride, + vm->m_area.add_y(em, vi, -1), + cavern_amp_index++) { + content_t c = vm->m_data[vi].getContent(); + float n_absamp_cavern = fabs(noise_cavern->result[index3d]) * + cavern_amp[cavern_amp_index]; + // Disable CavesRandomWalk at a safe distance from caverns + // to avoid excessively spreading liquids in caverns. + if (n_absamp_cavern > m_cavern_threshold - 0.1f) { + near_cavern = true; + if (n_absamp_cavern > m_cavern_threshold && + m_ndef->get(c).is_ground_content) + vm->m_data[vi] = MapNode(CONTENT_AIR); + } + } + } + + delete[] cavern_amp; + + return near_cavern; +} + + +//// +//// CavesRandomWalk +//// + +CavesRandomWalk::CavesRandomWalk( + INodeDefManager *ndef, + GenerateNotifier *gennotify, + s32 seed, + int water_level, + content_t water_source, + content_t lava_source, + int lava_depth) +{ + assert(ndef); + + this->ndef = ndef; + this->gennotify = gennotify; + this->seed = seed; + this->water_level = water_level; + this->np_caveliquids = &nparams_caveliquids; + this->lava_depth = lava_depth; + + c_water_source = water_source; + if (c_water_source == CONTENT_IGNORE) + c_water_source = ndef->getId("mapgen_water_source"); + if (c_water_source == CONTENT_IGNORE) + c_water_source = CONTENT_AIR; + + c_lava_source = lava_source; + if (c_lava_source == CONTENT_IGNORE) + c_lava_source = ndef->getId("mapgen_lava_source"); + if (c_lava_source == CONTENT_IGNORE) + c_lava_source = CONTENT_AIR; +} + + +void CavesRandomWalk::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, + PseudoRandom *ps, bool is_large_cave, int max_stone_height, s16 *heightmap) +{ + assert(vm); + assert(ps); + + this->vm = vm; + this->ps = ps; + this->node_min = nmin; + this->node_max = nmax; + this->heightmap = heightmap; + this->large_cave = is_large_cave; + + this->ystride = nmax.X - nmin.X + 1; + + // Set initial parameters from randomness + int dswitchint = ps->range(1, 14); + flooded = ps->range(1, 2) == 2; + + if (large_cave) { + part_max_length_rs = ps->range(2, 4); + tunnel_routepoints = ps->range(5, ps->range(15, 30)); + min_tunnel_diameter = 5; + max_tunnel_diameter = ps->range(7, ps->range(8, 24)); + } else { + part_max_length_rs = ps->range(2, 9); + tunnel_routepoints = ps->range(10, ps->range(15, 30)); + min_tunnel_diameter = 2; + max_tunnel_diameter = ps->range(2, 6); + } + + large_cave_is_flat = (ps->range(0, 1) == 0); + + main_direction = v3f(0, 0, 0); + + // Allowed route area size in nodes + ar = node_max - node_min + v3s16(1, 1, 1); + // Area starting point in nodes + of = node_min; + + // Allow a bit more + //(this should be more than the maximum radius of the tunnel) + const s16 insure = 10; + s16 more = MYMAX(MAP_BLOCKSIZE - max_tunnel_diameter / 2 - insure, 1); + ar += v3s16(1, 0, 1) * more * 2; + of -= v3s16(1, 0, 1) * more; + + route_y_min = 0; + // Allow half a diameter + 7 over stone surface + route_y_max = -of.Y + max_stone_y + max_tunnel_diameter / 2 + 7; + + // Limit maximum to area + route_y_max = rangelim(route_y_max, 0, ar.Y - 1); + + if (large_cave) { + s16 minpos = 0; + if (node_min.Y < water_level && node_max.Y > water_level) { + minpos = water_level - max_tunnel_diameter / 3 - of.Y; + route_y_max = water_level + max_tunnel_diameter / 3 - of.Y; + } + route_y_min = ps->range(minpos, minpos + max_tunnel_diameter); + route_y_min = rangelim(route_y_min, 0, route_y_max); + } + + s16 route_start_y_min = route_y_min; + s16 route_start_y_max = route_y_max; + + route_start_y_min = rangelim(route_start_y_min, 0, ar.Y - 1); + route_start_y_max = rangelim(route_start_y_max, route_start_y_min, ar.Y - 1); + + // Randomize starting position + orp.Z = (float)(ps->next() % ar.Z) + 0.5f; + orp.Y = (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5f; + orp.X = (float)(ps->next() % ar.X) + 0.5f; + + // Add generation notify begin event + if (gennotify) { + v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + GenNotifyType notifytype = large_cave ? + GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN; + gennotify->addEvent(notifytype, abs_pos); + } + + // Generate some tunnel starting from orp + for (u16 j = 0; j < tunnel_routepoints; j++) + makeTunnel(j % dswitchint == 0); + + // Add generation notify end event + if (gennotify) { + v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + GenNotifyType notifytype = large_cave ? + GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END; + gennotify->addEvent(notifytype, abs_pos); + } +} + + +void CavesRandomWalk::makeTunnel(bool dirswitch) +{ + if (dirswitch && !large_cave) { + main_direction.Z = ((float)(ps->next() % 20) - (float)10) / 10; + main_direction.Y = ((float)(ps->next() % 20) - (float)10) / 30; + main_direction.X = ((float)(ps->next() % 20) - (float)10) / 10; + + main_direction *= (float)ps->range(0, 10) / 10; + } + + // Randomize size + s16 min_d = min_tunnel_diameter; + s16 max_d = max_tunnel_diameter; + rs = ps->range(min_d, max_d); + s16 rs_part_max_length_rs = rs * part_max_length_rs; + + v3s16 maxlen; + if (large_cave) { + maxlen = v3s16( + rs_part_max_length_rs, + rs_part_max_length_rs / 2, + rs_part_max_length_rs + ); + } else { + maxlen = v3s16( + rs_part_max_length_rs, + ps->range(1, rs_part_max_length_rs), + rs_part_max_length_rs + ); + } + + v3f vec; + // Jump downward sometimes + if (!large_cave && ps->range(0, 12) == 0) { + vec.Z = (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2; + vec.Y = (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y; + vec.X = (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2; + } else { + vec.Z = (float)(ps->next() % (maxlen.Z * 1)) - (float)maxlen.Z / 2; + vec.Y = (float)(ps->next() % (maxlen.Y * 1)) - (float)maxlen.Y / 2; + vec.X = (float)(ps->next() % (maxlen.X * 1)) - (float)maxlen.X / 2; + } + + // Do not make caves that are above ground. + // It is only necessary to check the startpoint and endpoint. + v3s16 p1 = v3s16(orp.X, orp.Y, orp.Z) + of + rs / 2; + v3s16 p2 = v3s16(vec.X, vec.Y, vec.Z) + p1; + if (isPosAboveSurface(p1) || isPosAboveSurface(p2)) + return; + + vec += main_direction; + + v3f rp = orp + vec; + if (rp.X < 0) + rp.X = 0; + else if (rp.X >= ar.X) + rp.X = ar.X - 1; + + if (rp.Y < route_y_min) + rp.Y = route_y_min; + else if (rp.Y >= route_y_max) + rp.Y = route_y_max - 1; + + if (rp.Z < 0) + rp.Z = 0; + else if (rp.Z >= ar.Z) + rp.Z = ar.Z - 1; + + vec = rp - orp; + + float veclen = vec.getLength(); + if (veclen < 0.05f) + veclen = 1.0f; + + // Every second section is rough + bool randomize_xz = (ps->range(1, 2) == 1); + + // Carve routes + for (float f = 0.f; f < 1.0f; f += 1.0f / veclen) + carveRoute(vec, f, randomize_xz); + + orp = rp; +} + + +void CavesRandomWalk::carveRoute(v3f vec, float f, bool randomize_xz) +{ + MapNode airnode(CONTENT_AIR); + MapNode waternode(c_water_source); + MapNode lavanode(c_lava_source); + + v3s16 startp(orp.X, orp.Y, orp.Z); + startp += of; + + float nval = NoisePerlin3D(np_caveliquids, startp.X, + startp.Y, startp.Z, seed); + MapNode liquidnode = (nval < 0.40f && node_max.Y < lava_depth) ? + lavanode : waternode; + + v3f fp = orp + vec * f; + fp.X += 0.1f * ps->range(-10, 10); + fp.Z += 0.1f * ps->range(-10, 10); + v3s16 cp(fp.X, fp.Y, fp.Z); + + s16 d0 = -rs / 2; + s16 d1 = d0 + rs; + if (randomize_xz) { + d0 += ps->range(-1, 1); + d1 += ps->range(-1, 1); + } + + bool flat_cave_floor = !large_cave && ps->range(0, 2) == 2; + + for (s16 z0 = d0; z0 <= d1; z0++) { + s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1); + for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) { + s16 maxabsxz = MYMAX(abs(x0), abs(z0)); + + s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1); + + for (s16 y0 = -si2; y0 <= si2; y0++) { + // Make better floors in small caves + if (flat_cave_floor && y0 <= -rs / 2 && rs <= 7) + continue; + + if (large_cave_is_flat) { + // Make large caves not so tall + if (rs > 7 && abs(y0) >= rs / 3) + continue; + } + + v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0); + p += of; + + if (!vm->m_area.contains(p)) + continue; + + u32 i = vm->m_area.index(p); + content_t c = vm->m_data[i].getContent(); + if (!ndef->get(c).is_ground_content) + continue; + + if (large_cave) { + int full_ymin = node_min.Y - MAP_BLOCKSIZE; + int full_ymax = node_max.Y + MAP_BLOCKSIZE; + + if (flooded && full_ymin < water_level && full_ymax > water_level) + vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode; + else if (flooded && full_ymax < water_level) + vm->m_data[i] = (p.Y < startp.Y - 4) ? liquidnode : airnode; + else + vm->m_data[i] = airnode; + } else { + if (c == CONTENT_IGNORE) + continue; + + vm->m_data[i] = airnode; + vm->m_flags[i] |= VMANIP_FLAG_CAVE; + } + } + } + } +} + + +inline bool CavesRandomWalk::isPosAboveSurface(v3s16 p) +{ + if (heightmap != NULL && + p.Z >= node_min.Z && p.Z <= node_max.Z && + p.X >= node_min.X && p.X <= node_max.X) { + u32 index = (p.Z - node_min.Z) * ystride + (p.X - node_min.X); + if (heightmap[index] < p.Y) + return true; + } else if (p.Y > water_level) { + return true; + } + + return false; +} + + +//// +//// CavesV6 +//// + +CavesV6::CavesV6(INodeDefManager *ndef, GenerateNotifier *gennotify, + int water_level, content_t water_source, content_t lava_source) +{ + assert(ndef); + + this->ndef = ndef; + this->gennotify = gennotify; + this->water_level = water_level; + + c_water_source = water_source; + if (c_water_source == CONTENT_IGNORE) + c_water_source = ndef->getId("mapgen_water_source"); + if (c_water_source == CONTENT_IGNORE) + c_water_source = CONTENT_AIR; + + c_lava_source = lava_source; + if (c_lava_source == CONTENT_IGNORE) + c_lava_source = ndef->getId("mapgen_lava_source"); + if (c_lava_source == CONTENT_IGNORE) + c_lava_source = CONTENT_AIR; +} + + +void CavesV6::makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, + PseudoRandom *ps, PseudoRandom *ps2, + bool is_large_cave, int max_stone_height, s16 *heightmap) +{ + assert(vm); + assert(ps); + assert(ps2); + + this->vm = vm; + this->ps = ps; + this->ps2 = ps2; + this->node_min = nmin; + this->node_max = nmax; + this->heightmap = heightmap; + this->large_cave = is_large_cave; + + this->ystride = nmax.X - nmin.X + 1; + + // Set initial parameters from randomness + min_tunnel_diameter = 2; + max_tunnel_diameter = ps->range(2, 6); + int dswitchint = ps->range(1, 14); + if (large_cave) { + part_max_length_rs = ps->range(2, 4); + tunnel_routepoints = ps->range(5, ps->range(15, 30)); + min_tunnel_diameter = 5; + max_tunnel_diameter = ps->range(7, ps->range(8, 24)); + } else { + part_max_length_rs = ps->range(2, 9); + tunnel_routepoints = ps->range(10, ps->range(15, 30)); + } + large_cave_is_flat = (ps->range(0, 1) == 0); + + main_direction = v3f(0, 0, 0); + + // Allowed route area size in nodes + ar = node_max - node_min + v3s16(1, 1, 1); + // Area starting point in nodes + of = node_min; + + // Allow a bit more + //(this should be more than the maximum radius of the tunnel) + const s16 max_spread_amount = MAP_BLOCKSIZE; + const s16 insure = 10; + s16 more = MYMAX(max_spread_amount - max_tunnel_diameter / 2 - insure, 1); + ar += v3s16(1, 0, 1) * more * 2; + of -= v3s16(1, 0, 1) * more; + + route_y_min = 0; + // Allow half a diameter + 7 over stone surface + route_y_max = -of.Y + max_stone_height + max_tunnel_diameter / 2 + 7; + + // Limit maximum to area + route_y_max = rangelim(route_y_max, 0, ar.Y - 1); + + if (large_cave) { + s16 minpos = 0; + if (node_min.Y < water_level && node_max.Y > water_level) { + minpos = water_level - max_tunnel_diameter / 3 - of.Y; + route_y_max = water_level + max_tunnel_diameter / 3 - of.Y; + } + route_y_min = ps->range(minpos, minpos + max_tunnel_diameter); + route_y_min = rangelim(route_y_min, 0, route_y_max); + } + + s16 route_start_y_min = route_y_min; + s16 route_start_y_max = route_y_max; + + route_start_y_min = rangelim(route_start_y_min, 0, ar.Y - 1); + route_start_y_max = rangelim(route_start_y_max, route_start_y_min, ar.Y - 1); + + // Randomize starting position + orp.Z = (float)(ps->next() % ar.Z) + 0.5f; + orp.Y = (float)(ps->range(route_start_y_min, route_start_y_max)) + 0.5f; + orp.X = (float)(ps->next() % ar.X) + 0.5f; + + // Add generation notify begin event + if (gennotify != NULL) { + v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + GenNotifyType notifytype = large_cave ? + GENNOTIFY_LARGECAVE_BEGIN : GENNOTIFY_CAVE_BEGIN; + gennotify->addEvent(notifytype, abs_pos); + } + + // Generate some tunnel starting from orp + for (u16 j = 0; j < tunnel_routepoints; j++) + makeTunnel(j % dswitchint == 0); + + // Add generation notify end event + if (gennotify != NULL) { + v3s16 abs_pos(of.X + orp.X, of.Y + orp.Y, of.Z + orp.Z); + GenNotifyType notifytype = large_cave ? + GENNOTIFY_LARGECAVE_END : GENNOTIFY_CAVE_END; + gennotify->addEvent(notifytype, abs_pos); + } +} + + +void CavesV6::makeTunnel(bool dirswitch) +{ + if (dirswitch && !large_cave) { + main_direction.Z = ((float)(ps->next() % 20) - (float)10) / 10; + main_direction.Y = ((float)(ps->next() % 20) - (float)10) / 30; + main_direction.X = ((float)(ps->next() % 20) - (float)10) / 10; + + main_direction *= (float)ps->range(0, 10) / 10; + } + + // Randomize size + s16 min_d = min_tunnel_diameter; + s16 max_d = max_tunnel_diameter; + rs = ps->range(min_d, max_d); + s16 rs_part_max_length_rs = rs * part_max_length_rs; + + v3s16 maxlen; + if (large_cave) { + maxlen = v3s16( + rs_part_max_length_rs, + rs_part_max_length_rs / 2, + rs_part_max_length_rs + ); + } else { + maxlen = v3s16( + rs_part_max_length_rs, + ps->range(1, rs_part_max_length_rs), + rs_part_max_length_rs + ); + } + + v3f vec; + vec.Z = (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2; + vec.Y = (float)(ps->next() % maxlen.Y) - (float)maxlen.Y / 2; + vec.X = (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2; + + // Jump downward sometimes + if (!large_cave && ps->range(0, 12) == 0) { + vec.Z = (float)(ps->next() % maxlen.Z) - (float)maxlen.Z / 2; + vec.Y = (float)(ps->next() % (maxlen.Y * 2)) - (float)maxlen.Y; + vec.X = (float)(ps->next() % maxlen.X) - (float)maxlen.X / 2; + } + + // Do not make caves that are entirely above ground, to fix shadow bugs + // caused by overgenerated large caves. + // It is only necessary to check the startpoint and endpoint. + v3s16 p1 = v3s16(orp.X, orp.Y, orp.Z) + of + rs / 2; + v3s16 p2 = v3s16(vec.X, vec.Y, vec.Z) + p1; + + // If startpoint and endpoint are above ground, disable placement of nodes + // in carveRoute while still running all PseudoRandom calls to ensure caves + // are consistent with existing worlds. + bool tunnel_above_ground = + p1.Y > getSurfaceFromHeightmap(p1) && + p2.Y > getSurfaceFromHeightmap(p2); + + vec += main_direction; + + v3f rp = orp + vec; + if (rp.X < 0) + rp.X = 0; + else if (rp.X >= ar.X) + rp.X = ar.X - 1; + + if (rp.Y < route_y_min) + rp.Y = route_y_min; + else if (rp.Y >= route_y_max) + rp.Y = route_y_max - 1; + + if (rp.Z < 0) + rp.Z = 0; + else if (rp.Z >= ar.Z) + rp.Z = ar.Z - 1; + + vec = rp - orp; + + float veclen = vec.getLength(); + // As odd as it sounds, veclen is *exactly* 0.0 sometimes, causing a FPE + if (veclen < 0.05f) + veclen = 1.0f; + + // Every second section is rough + bool randomize_xz = (ps2->range(1, 2) == 1); + + // Carve routes + for (float f = 0.f; f < 1.0f; f += 1.0f / veclen) + carveRoute(vec, f, randomize_xz, tunnel_above_ground); + + orp = rp; +} + + +void CavesV6::carveRoute(v3f vec, float f, bool randomize_xz, + bool tunnel_above_ground) +{ + MapNode airnode(CONTENT_AIR); + MapNode waternode(c_water_source); + MapNode lavanode(c_lava_source); + + v3s16 startp(orp.X, orp.Y, orp.Z); + startp += of; + + v3f fp = orp + vec * f; + fp.X += 0.1f * ps->range(-10, 10); + fp.Z += 0.1f * ps->range(-10, 10); + v3s16 cp(fp.X, fp.Y, fp.Z); + + s16 d0 = -rs / 2; + s16 d1 = d0 + rs; + if (randomize_xz) { + d0 += ps->range(-1, 1); + d1 += ps->range(-1, 1); + } + + for (s16 z0 = d0; z0 <= d1; z0++) { + s16 si = rs / 2 - MYMAX(0, abs(z0) - rs / 7 - 1); + for (s16 x0 = -si - ps->range(0,1); x0 <= si - 1 + ps->range(0,1); x0++) { + if (tunnel_above_ground) + continue; + + s16 maxabsxz = MYMAX(abs(x0), abs(z0)); + s16 si2 = rs / 2 - MYMAX(0, maxabsxz - rs / 7 - 1); + for (s16 y0 = -si2; y0 <= si2; y0++) { + if (large_cave_is_flat) { + // Make large caves not so tall + if (rs > 7 && abs(y0) >= rs / 3) + continue; + } + + v3s16 p(cp.X + x0, cp.Y + y0, cp.Z + z0); + p += of; + + if (!vm->m_area.contains(p)) + continue; + + u32 i = vm->m_area.index(p); + content_t c = vm->m_data[i].getContent(); + if (!ndef->get(c).is_ground_content) + continue; + + if (large_cave) { + int full_ymin = node_min.Y - MAP_BLOCKSIZE; + int full_ymax = node_max.Y + MAP_BLOCKSIZE; + + if (full_ymin < water_level && full_ymax > water_level) { + vm->m_data[i] = (p.Y <= water_level) ? waternode : airnode; + } else if (full_ymax < water_level) { + vm->m_data[i] = (p.Y < startp.Y - 2) ? lavanode : airnode; + } else { + vm->m_data[i] = airnode; + } + } else { + if (c == CONTENT_IGNORE || c == CONTENT_AIR) + continue; + + vm->m_data[i] = airnode; + vm->m_flags[i] |= VMANIP_FLAG_CAVE; + } + } + } + } +} + + +inline s16 CavesV6::getSurfaceFromHeightmap(v3s16 p) +{ + if (heightmap != NULL && + p.Z >= node_min.Z && p.Z <= node_max.Z && + p.X >= node_min.X && p.X <= node_max.X) { + u32 index = (p.Z - node_min.Z) * ystride + (p.X - node_min.X); + return heightmap[index]; + } + + return water_level; + +} diff --git a/src/mapgen/cavegen.h b/src/mapgen/cavegen.h new file mode 100644 index 000000000..ce146e0cd --- /dev/null +++ b/src/mapgen/cavegen.h @@ -0,0 +1,242 @@ +/* +Minetest +Copyright (C) 2010-2013 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +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. +*/ + +#pragma once + +#define VMANIP_FLAG_CAVE VOXELFLAG_CHECKED1 + +class GenerateNotifier; + +/* + CavesNoiseIntersection is a cave digging algorithm that carves smooth, + web-like, continuous tunnels at points where the density of the intersection + between two separate 3d noises is above a certain value. This value, + cave_width, can be modified to set the effective width of these tunnels. + + This algorithm is relatively heavyweight, taking ~80ms to generate an + 80x80x80 chunk of map on a modern processor. Use sparingly! + + TODO(hmmmm): Remove dependency on biomes + TODO(hmmmm): Find alternative to overgeneration as solution for sunlight issue +*/ +class CavesNoiseIntersection +{ +public: + CavesNoiseIntersection(INodeDefManager *nodedef, BiomeManager *biomemgr, + v3s16 chunksize, NoiseParams *np_cave1, NoiseParams *np_cave2, + s32 seed, float cave_width); + ~CavesNoiseIntersection(); + + void generateCaves(MMVManip *vm, v3s16 nmin, v3s16 nmax, u8 *biomemap); + +private: + INodeDefManager *m_ndef; + BiomeManager *m_bmgr; + + // configurable parameters + v3s16 m_csize; + float m_cave_width; + + // intermediate state variables + u16 m_ystride; + u16 m_zstride_1d; + + Noise *noise_cave1; + Noise *noise_cave2; +}; + +/* + CavernsNoise is a cave digging algorithm +*/ +class CavernsNoise +{ +public: + CavernsNoise(INodeDefManager *nodedef, v3s16 chunksize, NoiseParams *np_cavern, + s32 seed, float cavern_limit, float cavern_taper, + float cavern_threshold); + ~CavernsNoise(); + + bool generateCaverns(MMVManip *vm, v3s16 nmin, v3s16 nmax); + +private: + INodeDefManager *m_ndef; + + // configurable parameters + v3s16 m_csize; + float m_cavern_limit; + float m_cavern_taper; + float m_cavern_threshold; + + // intermediate state variables + u16 m_ystride; + u16 m_zstride_1d; + + Noise *noise_cavern; + + content_t c_water_source; + content_t c_lava_source; +}; + +/* + CavesRandomWalk is an implementation of a cave-digging algorithm that + operates on the principle of a "random walk" to approximate the stochiastic + activity of cavern development. + + In summary, this algorithm works by carving a randomly sized tunnel in a + random direction a random amount of times, randomly varying in width. + All randomness here is uniformly distributed; alternative distributions have + not yet been implemented. + + This algorithm is very fast, executing in less than 1ms on average for an + 80x80x80 chunk of map on a modern processor. +*/ +class CavesRandomWalk +{ +public: + MMVManip *vm; + INodeDefManager *ndef; + GenerateNotifier *gennotify; + s16 *heightmap; + + // configurable parameters + s32 seed; + int water_level; + int lava_depth; + NoiseParams *np_caveliquids; + + // intermediate state variables + u16 ystride; + + s16 min_tunnel_diameter; + s16 max_tunnel_diameter; + u16 tunnel_routepoints; + int part_max_length_rs; + + bool large_cave; + bool large_cave_is_flat; + bool flooded; + + s16 max_stone_y; + v3s16 node_min; + v3s16 node_max; + + v3f orp; // starting point, relative to caved space + v3s16 of; // absolute coordinates of caved space + v3s16 ar; // allowed route area + s16 rs; // tunnel radius size + v3f main_direction; + + s16 route_y_min; + s16 route_y_max; + + PseudoRandom *ps; + + content_t c_water_source; + content_t c_lava_source; + + // ndef is a mandatory parameter. + // If gennotify is NULL, generation events are not logged. + CavesRandomWalk(INodeDefManager *ndef, GenerateNotifier *gennotify = NULL, + s32 seed = 0, int water_level = 1, + content_t water_source = CONTENT_IGNORE, + content_t lava_source = CONTENT_IGNORE, int lava_depth = -256); + + // vm and ps are mandatory parameters. + // If heightmap is NULL, the surface level at all points is assumed to + // be water_level. + void makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, PseudoRandom *ps, + bool is_large_cave, int max_stone_height, s16 *heightmap); + +private: + void makeTunnel(bool dirswitch); + void carveRoute(v3f vec, float f, bool randomize_xz); + + inline bool isPosAboveSurface(v3s16 p); +}; + +/* + CavesV6 is the original version of caves used with Mapgen V6. + + Though it uses the same fundamental algorithm as CavesRandomWalk, it is made + separate to preserve the exact sequence of PseudoRandom calls - any change + to this ordering results in the output being radically different. + Because caves in Mapgen V6 are responsible for a large portion of the basic + terrain shape, modifying this will break our contract of reverse + compatibility for a 'stable' mapgen such as V6. + + tl;dr, + *** DO NOT TOUCH THIS CLASS UNLESS YOU KNOW WHAT YOU ARE DOING *** +*/ +class CavesV6 +{ +public: + MMVManip *vm; + INodeDefManager *ndef; + GenerateNotifier *gennotify; + PseudoRandom *ps; + PseudoRandom *ps2; + + // configurable parameters + s16 *heightmap; + content_t c_water_source; + content_t c_lava_source; + int water_level; + + // intermediate state variables + u16 ystride; + + s16 min_tunnel_diameter; + s16 max_tunnel_diameter; + u16 tunnel_routepoints; + int part_max_length_rs; + + bool large_cave; + bool large_cave_is_flat; + + v3s16 node_min; + v3s16 node_max; + + v3f orp; // starting point, relative to caved space + v3s16 of; // absolute coordinates of caved space + v3s16 ar; // allowed route area + s16 rs; // tunnel radius size + v3f main_direction; + + s16 route_y_min; + s16 route_y_max; + + // ndef is a mandatory parameter. + // If gennotify is NULL, generation events are not logged. + CavesV6(INodeDefManager *ndef, GenerateNotifier *gennotify = NULL, + int water_level = 1, content_t water_source = CONTENT_IGNORE, + content_t lava_source = CONTENT_IGNORE); + + // vm, ps, and ps2 are mandatory parameters. + // If heightmap is NULL, the surface level at all points is assumed to + // be water_level. + void makeCave(MMVManip *vm, v3s16 nmin, v3s16 nmax, PseudoRandom *ps, + PseudoRandom *ps2, bool is_large_cave, int max_stone_height, + s16 *heightmap = NULL); + +private: + void makeTunnel(bool dirswitch); + void carveRoute(v3f vec, float f, bool randomize_xz, bool tunnel_above_ground); + + inline s16 getSurfaceFromHeightmap(v3s16 p); +}; diff --git a/src/mapgen/dungeongen.cpp b/src/mapgen/dungeongen.cpp new file mode 100644 index 000000000..fa867b398 --- /dev/null +++ b/src/mapgen/dungeongen.cpp @@ -0,0 +1,677 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@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 "dungeongen.h" +#include "mapgen.h" +#include "voxel.h" +#include "noise.h" +#include "mapblock.h" +#include "mapnode.h" +#include "map.h" +#include "nodedef.h" +#include "settings.h" + +//#define DGEN_USE_TORCHES + +NoiseParams nparams_dungeon_density(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0); +NoiseParams nparams_dungeon_alt_wall(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); + + +/////////////////////////////////////////////////////////////////////////////// + + +DungeonGen::DungeonGen(INodeDefManager *ndef, + GenerateNotifier *gennotify, DungeonParams *dparams) +{ + assert(ndef); + + this->ndef = ndef; + this->gennotify = gennotify; + +#ifdef DGEN_USE_TORCHES + c_torch = ndef->getId("default:torch"); +#endif + + if (dparams) { + memcpy(&dp, dparams, sizeof(dp)); + } else { + // Default dungeon parameters + dp.seed = 0; + + dp.c_water = ndef->getId("mapgen_water_source"); + dp.c_river_water = ndef->getId("mapgen_river_water_source"); + dp.c_wall = ndef->getId("mapgen_cobble"); + dp.c_alt_wall = ndef->getId("mapgen_mossycobble"); + dp.c_stair = ndef->getId("mapgen_stair_cobble"); + + if (dp.c_river_water == CONTENT_IGNORE) + dp.c_river_water = ndef->getId("mapgen_water_source"); + + dp.diagonal_dirs = false; + dp.only_in_ground = true; + dp.holesize = v3s16(1, 2, 1); + dp.corridor_len_min = 1; + dp.corridor_len_max = 13; + dp.room_size_min = v3s16(4, 4, 4); + dp.room_size_max = v3s16(8, 6, 8); + dp.room_size_large_min = v3s16(8, 8, 8); + dp.room_size_large_max = v3s16(16, 16, 16); + dp.rooms_min = 2; + dp.rooms_max = 16; + dp.y_min = -MAX_MAP_GENERATION_LIMIT; + dp.y_max = MAX_MAP_GENERATION_LIMIT; + dp.notifytype = GENNOTIFY_DUNGEON; + + dp.np_density = nparams_dungeon_density; + dp.np_alt_wall = nparams_dungeon_alt_wall; + } +} + + +void DungeonGen::generate(MMVManip *vm, u32 bseed, v3s16 nmin, v3s16 nmax) +{ + assert(vm); + + //TimeTaker t("gen dungeons"); + if (nmin.Y < dp.y_min || nmax.Y > dp.y_max) + return; + + float nval_density = NoisePerlin3D(&dp.np_density, nmin.X, nmin.Y, nmin.Z, dp.seed); + if (nval_density < 1.0f) + return; + + static const bool preserve_ignore = !g_settings->getBool("projecting_dungeons"); + + this->vm = vm; + this->blockseed = bseed; + random.seed(bseed + 2); + + // Dungeon generator doesn't modify places which have this set + vm->clearFlag(VMANIP_FLAG_DUNGEON_INSIDE | VMANIP_FLAG_DUNGEON_PRESERVE); + + if (dp.only_in_ground) { + // Set all air and water to be untouchable to make dungeons open to + // caves and open air. Optionally set ignore to be untouchable to + // prevent protruding dungeons. + for (s16 z = nmin.Z; z <= nmax.Z; z++) { + for (s16 y = nmin.Y; y <= nmax.Y; y++) { + u32 i = vm->m_area.index(nmin.X, y, z); + for (s16 x = nmin.X; x <= nmax.X; x++) { + content_t c = vm->m_data[i].getContent(); + if (c == CONTENT_AIR || c == dp.c_water || + (preserve_ignore && c == CONTENT_IGNORE) || + c == dp.c_river_water) + vm->m_flags[i] |= VMANIP_FLAG_DUNGEON_PRESERVE; + i++; + } + } + } + } + + // Add them + for (u32 i = 0; i < floor(nval_density); i++) + makeDungeon(v3s16(1, 1, 1) * MAP_BLOCKSIZE); + + // Optionally convert some structure to alternative structure + if (dp.c_alt_wall == CONTENT_IGNORE) + return; + + for (s16 z = nmin.Z; z <= nmax.Z; z++) + for (s16 y = nmin.Y; y <= nmax.Y; y++) { + u32 i = vm->m_area.index(nmin.X, y, z); + for (s16 x = nmin.X; x <= nmax.X; x++) { + if (vm->m_data[i].getContent() == dp.c_wall) { + if (NoisePerlin3D(&dp.np_alt_wall, x, y, z, blockseed) > 0.0f) + vm->m_data[i].setContent(dp.c_alt_wall); + } + i++; + } + } + + //printf("== gen dungeons: %dms\n", t.stop()); +} + + +void DungeonGen::makeDungeon(v3s16 start_padding) +{ + const v3s16 &areasize = vm->m_area.getExtent(); + v3s16 roomsize; + v3s16 roomplace; + + /* + Find place for first room. + There is a 1 in 4 chance of the first room being 'large', + all other rooms are not 'large'. + */ + bool fits = false; + for (u32 i = 0; i < 100 && !fits; i++) { + bool is_large_room = ((random.next() & 3) == 1); + if (is_large_room) { + roomsize.Z = random.range( + dp.room_size_large_min.Z, dp.room_size_large_max.Z); + roomsize.Y = random.range( + dp.room_size_large_min.Y, dp.room_size_large_max.Y); + roomsize.X = random.range( + dp.room_size_large_min.X, dp.room_size_large_max.X); + } else { + roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z); + roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y); + roomsize.X = random.range(dp.room_size_min.X, dp.room_size_max.X); + } + + // start_padding is used to disallow starting the generation of + // a dungeon in a neighboring generation chunk + roomplace = vm->m_area.MinEdge + start_padding; + roomplace.Z += random.range(0, areasize.Z - roomsize.Z - start_padding.Z); + roomplace.Y += random.range(0, areasize.Y - roomsize.Y - start_padding.Y); + roomplace.X += random.range(0, areasize.X - roomsize.X - start_padding.X); + + /* + Check that we're not putting the room to an unknown place, + otherwise it might end up floating in the air + */ + fits = true; + for (s16 z = 0; z < roomsize.Z; z++) + for (s16 y = 0; y < roomsize.Y; y++) + for (s16 x = 0; x < roomsize.X; x++) { + v3s16 p = roomplace + v3s16(x, y, z); + u32 vi = vm->m_area.index(p); + if ((vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) || + vm->m_data[vi].getContent() == CONTENT_IGNORE) { + fits = false; + break; + } + } + } + // No place found + if (!fits) + return; + + /* + Stores the center position of the last room made, so that + a new corridor can be started from the last room instead of + the new room, if chosen so. + */ + v3s16 last_room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2); + + u32 room_count = random.range(dp.rooms_min, dp.rooms_max); + for (u32 i = 0; i < room_count; i++) { + // Make a room to the determined place + makeRoom(roomsize, roomplace); + + v3s16 room_center = roomplace + v3s16(roomsize.X / 2, 1, roomsize.Z / 2); + if (gennotify) + gennotify->addEvent(dp.notifytype, room_center); + +#ifdef DGEN_USE_TORCHES + // Place torch at room center (for testing) + vm->m_data[vm->m_area.index(room_center)] = MapNode(c_torch); +#endif + + // Quit if last room + if (i == room_count - 1) + break; + + // Determine walker start position + + bool start_in_last_room = (random.range(0, 2) != 0); + + v3s16 walker_start_place; + + if (start_in_last_room) { + walker_start_place = last_room_center; + } else { + walker_start_place = room_center; + // Store center of current room as the last one + last_room_center = room_center; + } + + // Create walker and find a place for a door + v3s16 doorplace; + v3s16 doordir; + + m_pos = walker_start_place; + if (!findPlaceForDoor(doorplace, doordir)) + return; + + if (random.range(0, 1) == 0) + // Make the door + makeDoor(doorplace, doordir); + else + // Don't actually make a door + doorplace -= doordir; + + // Make a random corridor starting from the door + v3s16 corridor_end; + v3s16 corridor_end_dir; + makeCorridor(doorplace, doordir, corridor_end, corridor_end_dir); + + // Find a place for a random sized room + roomsize.Z = random.range(dp.room_size_min.Z, dp.room_size_max.Z); + roomsize.Y = random.range(dp.room_size_min.Y, dp.room_size_max.Y); + roomsize.X = random.range(dp.room_size_min.X, dp.room_size_max.X); + + m_pos = corridor_end; + m_dir = corridor_end_dir; + if (!findPlaceForRoomDoor(roomsize, doorplace, doordir, roomplace)) + return; + + if (random.range(0, 1) == 0) + // Make the door + makeDoor(doorplace, doordir); + else + // Don't actually make a door + roomplace -= doordir; + + } +} + + +void DungeonGen::makeRoom(v3s16 roomsize, v3s16 roomplace) +{ + MapNode n_wall(dp.c_wall); + MapNode n_air(CONTENT_AIR); + + // Make +-X walls + for (s16 z = 0; z < roomsize.Z; z++) + for (s16 y = 0; y < roomsize.Y; y++) { + { + v3s16 p = roomplace + v3s16(0, y, z); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_wall; + } + { + v3s16 p = roomplace + v3s16(roomsize.X - 1, y, z); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_wall; + } + } + + // Make +-Z walls + for (s16 x = 0; x < roomsize.X; x++) + for (s16 y = 0; y < roomsize.Y; y++) { + { + v3s16 p = roomplace + v3s16(x, y, 0); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_wall; + } + { + v3s16 p = roomplace + v3s16(x, y, roomsize.Z - 1); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_wall; + } + } + + // Make +-Y walls (floor and ceiling) + for (s16 z = 0; z < roomsize.Z; z++) + for (s16 x = 0; x < roomsize.X; x++) { + { + v3s16 p = roomplace + v3s16(x, 0, z); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_wall; + } + { + v3s16 p = roomplace + v3s16(x,roomsize. Y - 1, z); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & VMANIP_FLAG_DUNGEON_UNTOUCHABLE) + continue; + vm->m_data[vi] = n_wall; + } + } + + // Fill with air + for (s16 z = 1; z < roomsize.Z - 1; z++) + for (s16 y = 1; y < roomsize.Y - 1; y++) + for (s16 x = 1; x < roomsize.X - 1; x++) { + v3s16 p = roomplace + v3s16(x, y, z); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + vm->m_flags[vi] |= VMANIP_FLAG_DUNGEON_UNTOUCHABLE; + vm->m_data[vi] = n_air; + } +} + + +void DungeonGen::makeFill(v3s16 place, v3s16 size, + u8 avoid_flags, MapNode n, u8 or_flags) +{ + for (s16 z = 0; z < size.Z; z++) + for (s16 y = 0; y < size.Y; y++) + for (s16 x = 0; x < size.X; x++) { + v3s16 p = place + v3s16(x, y, z); + if (!vm->m_area.contains(p)) + continue; + u32 vi = vm->m_area.index(p); + if (vm->m_flags[vi] & avoid_flags) + continue; + vm->m_flags[vi] |= or_flags; + vm->m_data[vi] = n; + } +} + + +void DungeonGen::makeHole(v3s16 place) +{ + makeFill(place, dp.holesize, 0, MapNode(CONTENT_AIR), + VMANIP_FLAG_DUNGEON_INSIDE); +} + + +void DungeonGen::makeDoor(v3s16 doorplace, v3s16 doordir) +{ + makeHole(doorplace); + +#ifdef DGEN_USE_TORCHES + // Place torch (for testing) + vm->m_data[vm->m_area.index(doorplace)] = MapNode(c_torch); +#endif +} + + +void DungeonGen::makeCorridor(v3s16 doorplace, v3s16 doordir, + v3s16 &result_place, v3s16 &result_dir) +{ + makeHole(doorplace); + v3s16 p0 = doorplace; + v3s16 dir = doordir; + u32 length = random.range(dp.corridor_len_min, dp.corridor_len_max); + u32 partlength = random.range(dp.corridor_len_min, dp.corridor_len_max); + u32 partcount = 0; + s16 make_stairs = 0; + + if (random.next() % 2 == 0 && partlength >= 3) + make_stairs = random.next() % 2 ? 1 : -1; + + for (u32 i = 0; i < length; i++) { + v3s16 p = p0 + dir; + if (partcount != 0) + p.Y += make_stairs; + + // Check segment of minimum size corridor is in voxelmanip + if (vm->m_area.contains(p) && vm->m_area.contains(p + v3s16(0, 1, 0))) { + if (make_stairs) { + makeFill(p + v3s16(-1, -1, -1), + dp.holesize + v3s16(2, 3, 2), + VMANIP_FLAG_DUNGEON_UNTOUCHABLE, + MapNode(dp.c_wall), + 0); + makeHole(p); + makeHole(p - dir); + + // TODO: fix stairs code so it works 100% + // (quite difficult) + + // exclude stairs from the bottom step + // exclude stairs from diagonal steps + if (((dir.X ^ dir.Z) & 1) && + (((make_stairs == 1) && i != 0) || + ((make_stairs == -1) && i != length - 1))) { + // rotate face 180 deg if + // making stairs backwards + int facedir = dir_to_facedir(dir * make_stairs); + v3s16 ps = p; + u16 stair_width = (dir.Z != 0) ? dp.holesize.X : dp.holesize.Z; + // Stair width direction vector + v3s16 swv = (dir.Z != 0) ? v3s16(1, 0, 0) : v3s16(0, 0, 1); + + for (u16 st = 0; st < stair_width; st++) { + u32 vi = vm->m_area.index(ps.X - dir.X, ps.Y - 1, ps.Z - dir.Z); + if (vm->m_area.contains(ps + v3s16(-dir.X, -1, -dir.Z)) && + vm->m_data[vi].getContent() == dp.c_wall) + vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir); + + vi = vm->m_area.index(ps.X, ps.Y, ps.Z); + if (vm->m_area.contains(ps) && + vm->m_data[vi].getContent() == dp.c_wall) + vm->m_data[vi] = MapNode(dp.c_stair, 0, facedir); + + ps += swv; + } + } + } else { + makeFill(p + v3s16(-1, -1, -1), + dp.holesize + v3s16(2, 2, 2), + VMANIP_FLAG_DUNGEON_UNTOUCHABLE, + MapNode(dp.c_wall), + 0); + makeHole(p); + } + + p0 = p; + } else { + // Can't go here, turn away + dir = turn_xz(dir, random.range(0, 1)); + make_stairs = -make_stairs; + partcount = 0; + partlength = random.range(1, length); + continue; + } + + partcount++; + if (partcount >= partlength) { + partcount = 0; + + dir = random_turn(random, dir); + + partlength = random.range(1, length); + + make_stairs = 0; + if (random.next() % 2 == 0 && partlength >= 3) + make_stairs = random.next() % 2 ? 1 : -1; + } + } + result_place = p0; + result_dir = dir; +} + + +bool DungeonGen::findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir) +{ + for (u32 i = 0; i < 100; i++) { + v3s16 p = m_pos + m_dir; + v3s16 p1 = p + v3s16(0, 1, 0); + if (!vm->m_area.contains(p) || !vm->m_area.contains(p1) || i % 4 == 0) { + randomizeDir(); + continue; + } + if (vm->getNodeNoExNoEmerge(p).getContent() == dp.c_wall && + vm->getNodeNoExNoEmerge(p1).getContent() == dp.c_wall) { + // Found wall, this is a good place! + result_place = p; + result_dir = m_dir; + // Randomize next direction + randomizeDir(); + return true; + } + /* + Determine where to move next + */ + // Jump one up if the actual space is there + if (vm->getNodeNoExNoEmerge(p + + v3s16(0, 0, 0)).getContent() == dp.c_wall && + vm->getNodeNoExNoEmerge(p + + v3s16(0, 1, 0)).getContent() == CONTENT_AIR && + vm->getNodeNoExNoEmerge(p + + v3s16(0, 2, 0)).getContent() == CONTENT_AIR) + p += v3s16(0,1,0); + // Jump one down if the actual space is there + if (vm->getNodeNoExNoEmerge(p + + v3s16(0, 1, 0)).getContent() == dp.c_wall && + vm->getNodeNoExNoEmerge(p + + v3s16(0, 0, 0)).getContent() == CONTENT_AIR && + vm->getNodeNoExNoEmerge(p + + v3s16(0, -1, 0)).getContent() == CONTENT_AIR) + p += v3s16(0, -1, 0); + // Check if walking is now possible + if (vm->getNodeNoExNoEmerge(p).getContent() != CONTENT_AIR || + vm->getNodeNoExNoEmerge(p + + v3s16(0, 1, 0)).getContent() != CONTENT_AIR) { + // Cannot continue walking here + randomizeDir(); + continue; + } + // Move there + m_pos = p; + } + return false; +} + + +bool DungeonGen::findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace, + v3s16 &result_doordir, v3s16 &result_roomplace) +{ + for (s16 trycount = 0; trycount < 30; trycount++) { + v3s16 doorplace; + v3s16 doordir; + bool r = findPlaceForDoor(doorplace, doordir); + if (!r) + continue; + v3s16 roomplace; + // X east, Z north, Y up + if (doordir == v3s16(1, 0, 0)) // X+ + roomplace = doorplace + + v3s16(0, -1, random.range(-roomsize.Z + 2, -2)); + if (doordir == v3s16(-1, 0, 0)) // X- + roomplace = doorplace + + v3s16(-roomsize.X + 1, -1, random.range(-roomsize.Z + 2, -2)); + if (doordir == v3s16(0, 0, 1)) // Z+ + roomplace = doorplace + + v3s16(random.range(-roomsize.X + 2, -2), -1, 0); + if (doordir == v3s16(0, 0, -1)) // Z- + roomplace = doorplace + + v3s16(random.range(-roomsize.X + 2, -2), -1, -roomsize.Z + 1); + + // Check fit + bool fits = true; + for (s16 z = 1; z < roomsize.Z - 1; z++) + for (s16 y = 1; y < roomsize.Y - 1; y++) + for (s16 x = 1; x < roomsize.X - 1; x++) { + v3s16 p = roomplace + v3s16(x, y, z); + if (!vm->m_area.contains(p)) { + fits = false; + break; + } + if (vm->m_flags[vm->m_area.index(p)] & VMANIP_FLAG_DUNGEON_INSIDE) { + fits = false; + break; + } + } + if (!fits) { + // Find new place + continue; + } + result_doorplace = doorplace; + result_doordir = doordir; + result_roomplace = roomplace; + return true; + } + return false; +} + + +v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs) +{ + // Make diagonal directions somewhat rare + if (diagonal_dirs && (random.next() % 4 == 0)) { + v3s16 dir; + int trycount = 0; + + do { + trycount++; + + dir.Z = random.next() % 3 - 1; + dir.Y = 0; + dir.X = random.next() % 3 - 1; + } while ((dir.X == 0 || dir.Z == 0) && trycount < 10); + + return dir; + } + + if (random.next() % 2 == 0) + return random.next() % 2 ? v3s16(-1, 0, 0) : v3s16(1, 0, 0); + + return random.next() % 2 ? v3s16(0, 0, -1) : v3s16(0, 0, 1); +} + + +v3s16 turn_xz(v3s16 olddir, int t) +{ + v3s16 dir; + if (t == 0) { + // Turn right + dir.X = olddir.Z; + dir.Z = -olddir.X; + dir.Y = olddir.Y; + } else { + // Turn left + dir.X = -olddir.Z; + dir.Z = olddir.X; + dir.Y = olddir.Y; + } + return dir; +} + + +v3s16 random_turn(PseudoRandom &random, v3s16 olddir) +{ + int turn = random.range(0, 2); + v3s16 dir; + if (turn == 0) + // Go straight + dir = olddir; + else if (turn == 1) + // Turn right + dir = turn_xz(olddir, 0); + else + // Turn left + dir = turn_xz(olddir, 1); + return dir; +} + + +int dir_to_facedir(v3s16 d) +{ + if (abs(d.X) > abs(d.Z)) + return d.X < 0 ? 3 : 1; + + return d.Z < 0 ? 2 : 0; +} diff --git a/src/mapgen/dungeongen.h b/src/mapgen/dungeongen.h new file mode 100644 index 000000000..6799db79e --- /dev/null +++ b/src/mapgen/dungeongen.h @@ -0,0 +1,110 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@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. +*/ + +#pragma once + +#include "voxel.h" +#include "noise.h" +#include "mapgen.h" + +#define VMANIP_FLAG_DUNGEON_INSIDE VOXELFLAG_CHECKED1 +#define VMANIP_FLAG_DUNGEON_PRESERVE VOXELFLAG_CHECKED2 +#define VMANIP_FLAG_DUNGEON_UNTOUCHABLE (\ + VMANIP_FLAG_DUNGEON_INSIDE|VMANIP_FLAG_DUNGEON_PRESERVE) + +class MMVManip; +class INodeDefManager; + +v3s16 rand_ortho_dir(PseudoRandom &random, bool diagonal_dirs); +v3s16 turn_xz(v3s16 olddir, int t); +v3s16 random_turn(PseudoRandom &random, v3s16 olddir); +int dir_to_facedir(v3s16 d); + + +struct DungeonParams { + s32 seed; + + content_t c_water; + content_t c_river_water; + content_t c_wall; + content_t c_alt_wall; + content_t c_stair; + + bool diagonal_dirs; + bool only_in_ground; + v3s16 holesize; + u16 corridor_len_min; + u16 corridor_len_max; + v3s16 room_size_min; + v3s16 room_size_max; + v3s16 room_size_large_min; + v3s16 room_size_large_max; + u16 rooms_min; + u16 rooms_max; + s16 y_min; + s16 y_max; + GenNotifyType notifytype; + + NoiseParams np_density; + NoiseParams np_alt_wall; +}; + +class DungeonGen { +public: + MMVManip *vm; + INodeDefManager *ndef; + GenerateNotifier *gennotify; + + u32 blockseed; + PseudoRandom random; + v3s16 csize; + + content_t c_torch; + DungeonParams dp; + + // RoomWalker + v3s16 m_pos; + v3s16 m_dir; + + DungeonGen(INodeDefManager *ndef, + GenerateNotifier *gennotify, DungeonParams *dparams); + + void generate(MMVManip *vm, u32 bseed, + v3s16 full_node_min, v3s16 full_node_max); + + void makeDungeon(v3s16 start_padding); + void makeRoom(v3s16 roomsize, v3s16 roomplace); + void makeCorridor(v3s16 doorplace, v3s16 doordir, + v3s16 &result_place, v3s16 &result_dir); + void makeDoor(v3s16 doorplace, v3s16 doordir); + void makeFill(v3s16 place, v3s16 size, u8 avoid_flags, MapNode n, u8 or_flags); + void makeHole(v3s16 place); + + bool findPlaceForDoor(v3s16 &result_place, v3s16 &result_dir); + bool findPlaceForRoomDoor(v3s16 roomsize, v3s16 &result_doorplace, + v3s16 &result_doordir, v3s16 &result_roomplace); + + inline void randomizeDir() + { + m_dir = rand_ortho_dir(random, dp.diagonal_dirs); + } +}; + +extern NoiseParams nparams_dungeon_density; +extern NoiseParams nparams_dungeon_alt_wall; diff --git a/src/mapgen/mapgen.cpp b/src/mapgen/mapgen.cpp new file mode 100644 index 000000000..f73e16dd0 --- /dev/null +++ b/src/mapgen/mapgen.cpp @@ -0,0 +1,1140 @@ +/* +Minetest +Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2015-2017 paramat + +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 "voxel.h" +#include "noise.h" +#include "gamedef.h" +#include "mg_biome.h" +#include "mapblock.h" +#include "mapnode.h" +#include "map.h" +#include "content_sao.h" +#include "nodedef.h" +#include "emerge.h" +#include "voxelalgorithms.h" +#include "porting.h" +#include "profiler.h" +#include "settings.h" +#include "treegen.h" +#include "serialization.h" +#include "util/serialize.h" +#include "util/numeric.h" +#include "filesys.h" +#include "log.h" +#include "mapgen_carpathian.h" +#include "mapgen_flat.h" +#include "mapgen_fractal.h" +#include "mapgen_v5.h" +#include "mapgen_v6.h" +#include "mapgen_v7.h" +#include "mapgen_valleys.h" +#include "mapgen_singlenode.h" +#include "cavegen.h" +#include "dungeongen.h" + +FlagDesc flagdesc_mapgen[] = { + {"caves", MG_CAVES}, + {"dungeons", MG_DUNGEONS}, + {"light", MG_LIGHT}, + {"decorations", MG_DECORATIONS}, + {NULL, 0} +}; + +FlagDesc flagdesc_gennotify[] = { + {"dungeon", 1 << GENNOTIFY_DUNGEON}, + {"temple", 1 << GENNOTIFY_TEMPLE}, + {"cave_begin", 1 << GENNOTIFY_CAVE_BEGIN}, + {"cave_end", 1 << GENNOTIFY_CAVE_END}, + {"large_cave_begin", 1 << GENNOTIFY_LARGECAVE_BEGIN}, + {"large_cave_end", 1 << GENNOTIFY_LARGECAVE_END}, + {"decoration", 1 << GENNOTIFY_DECORATION}, + {NULL, 0} +}; + +struct MapgenDesc { + const char *name; + bool is_user_visible; +}; + +//// +//// Built-in mapgens +//// + +static MapgenDesc g_reg_mapgens[] = { + {"v5", true}, + {"v6", true}, + {"v7", true}, + {"flat", true}, + {"fractal", true}, + {"valleys", true}, + {"singlenode", true}, + {"carpathian", true}, +}; + +STATIC_ASSERT( + ARRLEN(g_reg_mapgens) == MAPGEN_INVALID, + registered_mapgens_is_wrong_size); + +//// +//// Mapgen +//// + +Mapgen::Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge) : + gennotify(emerge->gen_notify_on, &emerge->gen_notify_on_deco_ids) +{ + id = mapgenid; + water_level = params->water_level; + mapgen_limit = params->mapgen_limit; + flags = params->flags; + csize = v3s16(1, 1, 1) * (params->chunksize * MAP_BLOCKSIZE); + + /* + We are losing half our entropy by doing this, but it is necessary to + preserve reverse compatibility. If the top half of our current 64 bit + seeds ever starts getting used, existing worlds will break due to a + different hash outcome and no way to differentiate between versions. + + A solution could be to add a new bit to designate that the top half of + the seed value should be used, essentially a 1-bit version code, but + this would require increasing the total size of a seed to 9 bytes (yuck) + + It's probably okay if this never gets fixed. 4.2 billion possibilities + ought to be enough for anyone. + */ + seed = (s32)params->seed; + + ndef = emerge->ndef; +} + + +MapgenType Mapgen::getMapgenType(const std::string &mgname) +{ + for (size_t i = 0; i != ARRLEN(g_reg_mapgens); i++) { + if (mgname == g_reg_mapgens[i].name) + return (MapgenType)i; + } + + return MAPGEN_INVALID; +} + + +const char *Mapgen::getMapgenName(MapgenType mgtype) +{ + size_t index = (size_t)mgtype; + if (index == MAPGEN_INVALID || index >= ARRLEN(g_reg_mapgens)) + return "invalid"; + + return g_reg_mapgens[index].name; +} + + +Mapgen *Mapgen::createMapgen(MapgenType mgtype, int mgid, + MapgenParams *params, EmergeManager *emerge) +{ + switch (mgtype) { + case MAPGEN_CARPATHIAN: + return new MapgenCarpathian(mgid, (MapgenCarpathianParams *)params, emerge); + case MAPGEN_FLAT: + return new MapgenFlat(mgid, (MapgenFlatParams *)params, emerge); + case MAPGEN_FRACTAL: + return new MapgenFractal(mgid, (MapgenFractalParams *)params, emerge); + case MAPGEN_SINGLENODE: + return new MapgenSinglenode(mgid, (MapgenSinglenodeParams *)params, emerge); + case MAPGEN_V5: + return new MapgenV5(mgid, (MapgenV5Params *)params, emerge); + case MAPGEN_V6: + return new MapgenV6(mgid, (MapgenV6Params *)params, emerge); + case MAPGEN_V7: + return new MapgenV7(mgid, (MapgenV7Params *)params, emerge); + case MAPGEN_VALLEYS: + return new MapgenValleys(mgid, (MapgenValleysParams *)params, emerge); + default: + return NULL; + } +} + + +MapgenParams *Mapgen::createMapgenParams(MapgenType mgtype) +{ + switch (mgtype) { + case MAPGEN_CARPATHIAN: + return new MapgenCarpathianParams; + case MAPGEN_FLAT: + return new MapgenFlatParams; + case MAPGEN_FRACTAL: + return new MapgenFractalParams; + case MAPGEN_SINGLENODE: + return new MapgenSinglenodeParams; + case MAPGEN_V5: + return new MapgenV5Params; + case MAPGEN_V6: + return new MapgenV6Params; + case MAPGEN_V7: + return new MapgenV7Params; + case MAPGEN_VALLEYS: + return new MapgenValleysParams; + default: + return NULL; + } +} + + +void Mapgen::getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden) +{ + for (u32 i = 0; i != ARRLEN(g_reg_mapgens); i++) { + if (include_hidden || g_reg_mapgens[i].is_user_visible) + mgnames->push_back(g_reg_mapgens[i].name); + } +} + + +u32 Mapgen::getBlockSeed(v3s16 p, s32 seed) +{ + return (u32)seed + + p.Z * 38134234 + + p.Y * 42123 + + p.X * 23; +} + + +u32 Mapgen::getBlockSeed2(v3s16 p, s32 seed) +{ + u32 n = 1619 * p.X + 31337 * p.Y + 52591 * p.Z + 1013 * seed; + n = (n >> 13) ^ n; + return (n * (n * n * 60493 + 19990303) + 1376312589); +} + + +// Returns Y one under area minimum if not found +s16 Mapgen::findGroundLevelFull(v2s16 p2d) +{ + const v3s16 &em = vm->m_area.getExtent(); + s16 y_nodes_max = vm->m_area.MaxEdge.Y; + s16 y_nodes_min = vm->m_area.MinEdge.Y; + u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y); + s16 y; + + for (y = y_nodes_max; y >= y_nodes_min; y--) { + MapNode &n = vm->m_data[i]; + if (ndef->get(n).walkable) + break; + + vm->m_area.add_y(em, i, -1); + } + return (y >= y_nodes_min) ? y : y_nodes_min - 1; +} + + +// Returns -MAX_MAP_GENERATION_LIMIT if not found +s16 Mapgen::findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax) +{ + const v3s16 &em = vm->m_area.getExtent(); + u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y); + s16 y; + + for (y = ymax; y >= ymin; y--) { + MapNode &n = vm->m_data[i]; + if (ndef->get(n).walkable) + break; + + vm->m_area.add_y(em, i, -1); + } + return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT; +} + + +// Returns -MAX_MAP_GENERATION_LIMIT if not found or if ground is found first +s16 Mapgen::findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax) +{ + const v3s16 &em = vm->m_area.getExtent(); + u32 i = vm->m_area.index(p2d.X, ymax, p2d.Y); + s16 y; + + for (y = ymax; y >= ymin; y--) { + MapNode &n = vm->m_data[i]; + if (ndef->get(n).walkable) + return -MAX_MAP_GENERATION_LIMIT; + + if (ndef->get(n).isLiquid()) + break; + + vm->m_area.add_y(em, i, -1); + } + return (y >= ymin) ? y : -MAX_MAP_GENERATION_LIMIT; +} + + +void Mapgen::updateHeightmap(v3s16 nmin, v3s16 nmax) +{ + if (!heightmap) + return; + + //TimeTaker t("Mapgen::updateHeightmap", NULL, PRECISION_MICRO); + int index = 0; + for (s16 z = nmin.Z; z <= nmax.Z; z++) { + for (s16 x = nmin.X; x <= nmax.X; x++, index++) { + s16 y = findGroundLevel(v2s16(x, z), nmin.Y, nmax.Y); + + heightmap[index] = y; + } + } +} + + +void Mapgen::getSurfaces(v2s16 p2d, s16 ymin, s16 ymax, + s16 *floors, s16 *ceilings, u16 *num_floors, u16 *num_ceilings) +{ + u16 floor_i = 0; + u16 ceiling_i = 0; + const v3s16 &em = vm->m_area.getExtent(); + + bool is_walkable = false; + u32 vi = vm->m_area.index(p2d.X, ymax, p2d.Y); + MapNode mn_max = vm->m_data[vi]; + bool walkable_above = ndef->get(mn_max).walkable; + vm->m_area.add_y(em, vi, -1); + + for (s16 y = ymax - 1; y >= ymin; y--) { + MapNode mn = vm->m_data[vi]; + is_walkable = ndef->get(mn).walkable; + + if (is_walkable && !walkable_above) { + floors[floor_i] = y; + floor_i++; + } else if (!is_walkable && walkable_above) { + ceilings[ceiling_i] = y + 1; + ceiling_i++; + } + + vm->m_area.add_y(em, vi, -1); + walkable_above = is_walkable; + } + + *num_floors = floor_i; + *num_ceilings = ceiling_i; +} + + +inline bool Mapgen::isLiquidHorizontallyFlowable(u32 vi, v3s16 em) +{ + u32 vi_neg_x = vi; + vm->m_area.add_x(em, vi_neg_x, -1); + if (vm->m_data[vi_neg_x].getContent() != CONTENT_IGNORE) { + const ContentFeatures &c_nx = ndef->get(vm->m_data[vi_neg_x]); + if (c_nx.floodable && !c_nx.isLiquid()) + return true; + } + u32 vi_pos_x = vi; + vm->m_area.add_x(em, vi_pos_x, +1); + if (vm->m_data[vi_pos_x].getContent() != CONTENT_IGNORE) { + const ContentFeatures &c_px = ndef->get(vm->m_data[vi_pos_x]); + if (c_px.floodable && !c_px.isLiquid()) + return true; + } + u32 vi_neg_z = vi; + vm->m_area.add_z(em, vi_neg_z, -1); + if (vm->m_data[vi_neg_z].getContent() != CONTENT_IGNORE) { + const ContentFeatures &c_nz = ndef->get(vm->m_data[vi_neg_z]); + if (c_nz.floodable && !c_nz.isLiquid()) + return true; + } + u32 vi_pos_z = vi; + vm->m_area.add_z(em, vi_pos_z, +1); + if (vm->m_data[vi_pos_z].getContent() != CONTENT_IGNORE) { + const ContentFeatures &c_pz = ndef->get(vm->m_data[vi_pos_z]); + if (c_pz.floodable && !c_pz.isLiquid()) + return true; + } + return false; +} + +void Mapgen::updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nmax) +{ + bool isignored, isliquid, wasignored, wasliquid, waschecked, waspushed; + const v3s16 &em = vm->m_area.getExtent(); + + for (s16 z = nmin.Z + 1; z <= nmax.Z - 1; z++) + for (s16 x = nmin.X + 1; x <= nmax.X - 1; x++) { + wasignored = true; + wasliquid = false; + waschecked = false; + waspushed = false; + + u32 vi = vm->m_area.index(x, nmax.Y, z); + for (s16 y = nmax.Y; y >= nmin.Y; y--) { + isignored = vm->m_data[vi].getContent() == CONTENT_IGNORE; + isliquid = ndef->get(vm->m_data[vi]).isLiquid(); + + if (isignored || wasignored || isliquid == wasliquid) { + // Neither topmost node of liquid column nor topmost node below column + waschecked = false; + waspushed = false; + } else if (isliquid) { + // This is the topmost node in the column + bool ispushed = false; + if (isLiquidHorizontallyFlowable(vi, em)) { + trans_liquid->push_back(v3s16(x, y, z)); + ispushed = true; + } + // Remember waschecked and waspushed to avoid repeated + // checks/pushes in case the column consists of only this node + waschecked = true; + waspushed = ispushed; + } else { + // This is the topmost node below a liquid column + u32 vi_above = vi; + vm->m_area.add_y(em, vi_above, 1); + if (!waspushed && (ndef->get(vm->m_data[vi]).floodable || + (!waschecked && isLiquidHorizontallyFlowable(vi_above, em)))) { + // Push back the lowest node in the column which is one + // node above this one + trans_liquid->push_back(v3s16(x, y + 1, z)); + } + } + + wasliquid = isliquid; + wasignored = isignored; + vm->m_area.add_y(em, vi, -1); + } + } +} + + +void Mapgen::setLighting(u8 light, v3s16 nmin, v3s16 nmax) +{ + ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG); + VoxelArea a(nmin, nmax); + + for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) { + for (int y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) { + u32 i = vm->m_area.index(a.MinEdge.X, y, z); + for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++, i++) + vm->m_data[i].param1 = light; + } + } +} + + +void Mapgen::lightSpread(VoxelArea &a, v3s16 p, u8 light) +{ + if (light <= 1 || !a.contains(p)) + return; + + u32 vi = vm->m_area.index(p); + MapNode &n = vm->m_data[vi]; + + // Decay light in each of the banks separately + u8 light_day = light & 0x0F; + if (light_day > 0) + light_day -= 0x01; + + u8 light_night = light & 0xF0; + if (light_night > 0) + light_night -= 0x10; + + // Bail out only if we have no more light from either bank to propogate, or + // we hit a solid block that light cannot pass through. + if ((light_day <= (n.param1 & 0x0F) && + light_night <= (n.param1 & 0xF0)) || + !ndef->get(n).light_propagates) + return; + + // Since this recursive function only terminates when there is no light from + // either bank left, we need to take the max of both banks into account for + // the case where spreading has stopped for one light bank but not the other. + light = MYMAX(light_day, n.param1 & 0x0F) | + MYMAX(light_night, n.param1 & 0xF0); + + n.param1 = light; + + lightSpread(a, p + v3s16(0, 0, 1), light); + lightSpread(a, p + v3s16(0, 1, 0), light); + lightSpread(a, p + v3s16(1, 0, 0), light); + lightSpread(a, p - v3s16(0, 0, 1), light); + lightSpread(a, p - v3s16(0, 1, 0), light); + lightSpread(a, p - v3s16(1, 0, 0), light); +} + + +void Mapgen::calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax, + bool propagate_shadow) +{ + ScopeProfiler sp(g_profiler, "EmergeThread: mapgen lighting update", SPT_AVG); + //TimeTaker t("updateLighting"); + + propagateSunlight(nmin, nmax, propagate_shadow); + spreadLight(full_nmin, full_nmax); + + //printf("updateLighting: %dms\n", t.stop()); +} + + +void Mapgen::propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow) +{ + //TimeTaker t("propagateSunlight"); + VoxelArea a(nmin, nmax); + bool block_is_underground = (water_level >= nmax.Y); + const v3s16 &em = vm->m_area.getExtent(); + + // NOTE: Direct access to the low 4 bits of param1 is okay here because, + // by definition, sunlight will never be in the night lightbank. + + for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) { + for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++) { + // see if we can get a light value from the overtop + u32 i = vm->m_area.index(x, a.MaxEdge.Y + 1, z); + if (vm->m_data[i].getContent() == CONTENT_IGNORE) { + if (block_is_underground) + continue; + } else if ((vm->m_data[i].param1 & 0x0F) != LIGHT_SUN && + propagate_shadow) { + continue; + } + vm->m_area.add_y(em, i, -1); + + for (int y = a.MaxEdge.Y; y >= a.MinEdge.Y; y--) { + MapNode &n = vm->m_data[i]; + if (!ndef->get(n).sunlight_propagates) + break; + n.param1 = LIGHT_SUN; + vm->m_area.add_y(em, i, -1); + } + } + } + //printf("propagateSunlight: %dms\n", t.stop()); +} + + +void Mapgen::spreadLight(v3s16 nmin, v3s16 nmax) +{ + //TimeTaker t("spreadLight"); + VoxelArea a(nmin, nmax); + + for (int z = a.MinEdge.Z; z <= a.MaxEdge.Z; z++) { + for (int y = a.MinEdge.Y; y <= a.MaxEdge.Y; y++) { + u32 i = vm->m_area.index(a.MinEdge.X, y, z); + for (int x = a.MinEdge.X; x <= a.MaxEdge.X; x++, i++) { + MapNode &n = vm->m_data[i]; + if (n.getContent() == CONTENT_IGNORE) + continue; + + const ContentFeatures &cf = ndef->get(n); + if (!cf.light_propagates) + continue; + + // TODO(hmmmmm): Abstract away direct param1 accesses with a + // wrapper, but something lighter than MapNode::get/setLight + + u8 light_produced = cf.light_source; + if (light_produced) + n.param1 = light_produced | (light_produced << 4); + + u8 light = n.param1; + if (light) { + lightSpread(a, v3s16(x, y, z + 1), light); + lightSpread(a, v3s16(x, y + 1, z ), light); + lightSpread(a, v3s16(x + 1, y, z ), light); + lightSpread(a, v3s16(x, y, z - 1), light); + lightSpread(a, v3s16(x, y - 1, z ), light); + lightSpread(a, v3s16(x - 1, y, z ), light); + } + } + } + } + + //printf("spreadLight: %dms\n", t.stop()); +} + + +//// +//// MapgenBasic +//// + +MapgenBasic::MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge) + : Mapgen(mapgenid, params, emerge) +{ + this->m_emerge = emerge; + this->m_bmgr = emerge->biomemgr; + + //// Here, 'stride' refers to the number of elements needed to skip to index + //// an adjacent element for that coordinate in noise/height/biome maps + //// (*not* vmanip content map!) + + // Note there is no X stride explicitly defined. Items adjacent in the X + // coordinate are assumed to be adjacent in memory as well (i.e. stride of 1). + + // Number of elements to skip to get to the next Y coordinate + this->ystride = csize.X; + + // Number of elements to skip to get to the next Z coordinate + this->zstride = csize.X * csize.Y; + + // Z-stride value for maps oversized for 1-down overgeneration + this->zstride_1d = csize.X * (csize.Y + 1); + + // Z-stride value for maps oversized for 1-up 1-down overgeneration + this->zstride_1u1d = csize.X * (csize.Y + 2); + + //// Allocate heightmap + this->heightmap = new s16[csize.X * csize.Z]; + + //// Initialize biome generator + // TODO(hmmmm): should we have a way to disable biomemanager biomes? + biomegen = m_bmgr->createBiomeGen(BIOMEGEN_ORIGINAL, params->bparams, csize); + biomemap = biomegen->biomemap; + + //// Look up some commonly used content + c_stone = ndef->getId("mapgen_stone"); + c_desert_stone = ndef->getId("mapgen_desert_stone"); + c_sandstone = ndef->getId("mapgen_sandstone"); + c_water_source = ndef->getId("mapgen_water_source"); + c_river_water_source = ndef->getId("mapgen_river_water_source"); + c_lava_source = ndef->getId("mapgen_lava_source"); + + // Fall back to more basic content if not defined + // river_water_source cannot fallback to water_source because river water + // needs to be non-renewable and have a short flow range. + if (c_desert_stone == CONTENT_IGNORE) + c_desert_stone = c_stone; + if (c_sandstone == CONTENT_IGNORE) + c_sandstone = c_stone; + + //// Content used for dungeon generation + c_cobble = ndef->getId("mapgen_cobble"); + c_mossycobble = ndef->getId("mapgen_mossycobble"); + c_stair_cobble = ndef->getId("mapgen_stair_cobble"); + c_stair_desert_stone = ndef->getId("mapgen_stair_desert_stone"); + c_sandstonebrick = ndef->getId("mapgen_sandstonebrick"); + c_stair_sandstone_block = ndef->getId("mapgen_stair_sandstone_block"); + + // Fall back to more basic content if not defined + if (c_mossycobble == CONTENT_IGNORE) + c_mossycobble = c_cobble; + if (c_stair_cobble == CONTENT_IGNORE) + c_stair_cobble = c_cobble; + if (c_stair_desert_stone == CONTENT_IGNORE) + c_stair_desert_stone = c_desert_stone; + if (c_sandstonebrick == CONTENT_IGNORE) + c_sandstonebrick = c_sandstone; + if (c_stair_sandstone_block == CONTENT_IGNORE) + c_stair_sandstone_block = c_sandstonebrick; +} + + +MapgenBasic::~MapgenBasic() +{ + delete biomegen; + delete []heightmap; +} + + +void MapgenBasic::generateBiomes(MgStoneType *mgstone_type, + content_t *biome_stone) +{ + // can't generate biomes without a biome generator! + assert(biomegen); + assert(biomemap); + + const v3s16 &em = vm->m_area.getExtent(); + u32 index = 0; + MgStoneType stone_type = MGSTONE_OTHER; + content_t c_biome_stone = c_stone; + + noise_filler_depth->perlinMap2D(node_min.X, node_min.Z); + + 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; + u16 depth_riverbed = 0; + s16 biome_y_min = -MAX_MAP_GENERATION_LIMIT; + 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 river_water_above = c_above == c_river_water_source; + bool water_above = c_above == c_water_source || river_water_above; + + biomemap[index] = BIOME_NONE; + + // 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 (re)calculated: + // 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. + // 4. When stone or water is detected just below a biome's lower limit. + bool is_stone_surface = (c == c_stone) && + (air_above || water_above || !biome || y < biome_y_min); // 1, 3, 4 + + bool is_water_surface = + (c == c_water_source || c == c_river_water_source) && + (air_above || !biome || y < biome_y_min); // 2, 3, 4 + + if (is_stone_surface || is_water_surface) { + // (Re)calculate biome + biome = biomegen->getBiomeAtIndex(index, y); + + if (biomemap[index] == BIOME_NONE && is_stone_surface) + biomemap[index] = biome->index; + + depth_top = biome->depth_top; + base_filler = MYMAX(depth_top + + biome->depth_filler + + noise_filler_depth->result[index], 0.0f); + depth_water_top = biome->depth_water_top; + depth_riverbed = biome->depth_riverbed; + biome_y_min = biome->y_min; + + // Detect stone type for dungeons during every biome calculation. + // If none detected the last selected biome stone is chosen. + if (biome->c_stone == c_stone) + stone_type = MGSTONE_STONE; + else if (biome->c_stone == c_desert_stone) + stone_type = MGSTONE_DESERT_STONE; + else if (biome->c_stone == c_sandstone) + stone_type = MGSTONE_SANDSTONE; + + c_biome_stone = biome->c_stone; + } + + 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 (river_water_above) { + if (nplaced < depth_riverbed) { + vm->m_data[vi] = MapNode(biome->c_riverbed); + nplaced++; + } else { + nplaced = U16_MAX; // Disable top/filler placement + river_water_above = false; + } + } else 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); + nplaced = U16_MAX; // Disable top/filler placement + } + + 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 = 0; // Enable riverbed placement for next surface + air_above = false; + water_above = true; + river_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); + } + } + + *mgstone_type = stone_type; + *biome_stone = c_biome_stone; +} + + +void MapgenBasic::dustTopNodes() +{ + if (node_max.Y < water_level) + return; + + const 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 *)m_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; + + CavesNoiseIntersection caves_noise(ndef, m_bmgr, csize, + &np_cave1, &np_cave2, seed, cave_width); + + caves_noise.generateCaves(vm, node_min, node_max, biomemap); + + 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++) { + CavesRandomWalk cave(ndef, &gennotify, seed, water_level, + c_water_source, CONTENT_IGNORE, lava_depth); + + cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, heightmap); + } +} + + +bool MapgenBasic::generateCaverns(s16 max_stone_y) +{ + if (node_min.Y > max_stone_y || node_min.Y > cavern_limit) + return false; + + CavernsNoise caverns_noise(ndef, csize, &np_cavern, + seed, cavern_limit, cavern_taper, cavern_threshold); + + return caverns_noise.generateCaverns(vm, node_min, node_max); +} + + +void MapgenBasic::generateDungeons(s16 max_stone_y, + MgStoneType stone_type, content_t biome_stone) +{ + if (max_stone_y < node_min.Y) + return; + + DungeonParams dp; + + dp.seed = seed; + dp.c_water = c_water_source; + dp.c_river_water = c_river_water_source; + + dp.only_in_ground = true; + dp.corridor_len_min = 1; + dp.corridor_len_max = 13; + dp.rooms_min = 2; + dp.rooms_max = 16; + dp.y_min = -MAX_MAP_GENERATION_LIMIT; + dp.y_max = MAX_MAP_GENERATION_LIMIT; + + dp.np_density = nparams_dungeon_density; + dp.np_alt_wall = nparams_dungeon_alt_wall; + + switch (stone_type) { + default: + case MGSTONE_STONE: + dp.c_wall = c_cobble; + dp.c_alt_wall = c_mossycobble; + dp.c_stair = c_stair_cobble; + + dp.diagonal_dirs = false; + dp.holesize = v3s16(1, 2, 1); + dp.room_size_min = v3s16(4, 4, 4); + dp.room_size_max = v3s16(8, 6, 8); + dp.room_size_large_min = v3s16(8, 8, 8); + dp.room_size_large_max = v3s16(16, 16, 16); + dp.notifytype = GENNOTIFY_DUNGEON; + break; + case MGSTONE_DESERT_STONE: + dp.c_wall = c_desert_stone; + dp.c_alt_wall = CONTENT_IGNORE; + dp.c_stair = c_stair_desert_stone; + + dp.diagonal_dirs = true; + dp.holesize = v3s16(2, 3, 2); + dp.room_size_min = v3s16(6, 9, 6); + dp.room_size_max = v3s16(10, 11, 10); + dp.room_size_large_min = v3s16(10, 13, 10); + dp.room_size_large_max = v3s16(18, 21, 18); + dp.notifytype = GENNOTIFY_TEMPLE; + break; + case MGSTONE_SANDSTONE: + dp.c_wall = c_sandstonebrick; + dp.c_alt_wall = CONTENT_IGNORE; + dp.c_stair = c_stair_sandstone_block; + + dp.diagonal_dirs = false; + dp.holesize = v3s16(2, 2, 2); + dp.room_size_min = v3s16(6, 4, 6); + dp.room_size_max = v3s16(10, 6, 10); + dp.room_size_large_min = v3s16(10, 8, 10); + dp.room_size_large_max = v3s16(18, 16, 18); + dp.notifytype = GENNOTIFY_DUNGEON; + break; + case MGSTONE_OTHER: + dp.c_wall = biome_stone; + dp.c_alt_wall = biome_stone; + dp.c_stair = biome_stone; + + dp.diagonal_dirs = false; + dp.holesize = v3s16(1, 2, 1); + dp.room_size_min = v3s16(4, 4, 4); + dp.room_size_max = v3s16(8, 6, 8); + dp.room_size_large_min = v3s16(8, 8, 8); + dp.room_size_large_max = v3s16(16, 16, 16); + dp.notifytype = GENNOTIFY_DUNGEON; + break; + } + + DungeonGen dgen(ndef, &gennotify, &dp); + dgen.generate(vm, blockseed, full_node_min, full_node_max); +} + + +//// +//// GenerateNotifier +//// + +GenerateNotifier::GenerateNotifier(u32 notify_on, + std::set<u32> *notify_on_deco_ids) +{ + m_notify_on = notify_on; + m_notify_on_deco_ids = notify_on_deco_ids; +} + + +void GenerateNotifier::setNotifyOn(u32 notify_on) +{ + m_notify_on = notify_on; +} + + +void GenerateNotifier::setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids) +{ + m_notify_on_deco_ids = notify_on_deco_ids; +} + + +bool GenerateNotifier::addEvent(GenNotifyType type, v3s16 pos, u32 id) +{ + if (!(m_notify_on & (1 << type))) + return false; + + if (type == GENNOTIFY_DECORATION && + m_notify_on_deco_ids->find(id) == m_notify_on_deco_ids->end()) + return false; + + GenNotifyEvent gne; + gne.type = type; + gne.pos = pos; + gne.id = id; + m_notify_events.push_back(gne); + + return true; +} + + +void GenerateNotifier::getEvents( + std::map<std::string, std::vector<v3s16> > &event_map, + bool peek_events) +{ + std::list<GenNotifyEvent>::iterator it; + + for (it = m_notify_events.begin(); it != m_notify_events.end(); ++it) { + GenNotifyEvent &gn = *it; + std::string name = (gn.type == GENNOTIFY_DECORATION) ? + "decoration#"+ itos(gn.id) : + flagdesc_gennotify[gn.type].name; + + event_map[name].push_back(gn.pos); + } + + if (!peek_events) + m_notify_events.clear(); +} + + +//// +//// MapgenParams +//// + + +MapgenParams::~MapgenParams() +{ + delete bparams; +} + + +void MapgenParams::readParams(const Settings *settings) +{ + std::string seed_str; + const char *seed_name = (settings == g_settings) ? "fixed_map_seed" : "seed"; + + if (settings->getNoEx(seed_name, seed_str)) { + if (!seed_str.empty()) + seed = read_seed(seed_str.c_str()); + else + myrand_bytes(&seed, sizeof(seed)); + } + + std::string mg_name; + if (settings->getNoEx("mg_name", mg_name)) { + mgtype = Mapgen::getMapgenType(mg_name); + if (mgtype == MAPGEN_INVALID) + mgtype = MAPGEN_DEFAULT; + } + + settings->getS16NoEx("water_level", water_level); + settings->getS16NoEx("mapgen_limit", mapgen_limit); + settings->getS16NoEx("chunksize", chunksize); + settings->getFlagStrNoEx("mg_flags", flags, flagdesc_mapgen); + + delete bparams; + bparams = BiomeManager::createBiomeParams(BIOMEGEN_ORIGINAL); + if (bparams) { + bparams->readParams(settings); + bparams->seed = seed; + } +} + + +void MapgenParams::writeParams(Settings *settings) const +{ + settings->set("mg_name", Mapgen::getMapgenName(mgtype)); + settings->setU64("seed", seed); + settings->setS16("water_level", water_level); + settings->setS16("mapgen_limit", mapgen_limit); + settings->setS16("chunksize", chunksize); + settings->setFlagStr("mg_flags", flags, flagdesc_mapgen, U32_MAX); + + if (bparams) + bparams->writeParams(settings); +} + +// Calculate edges of outermost generated mapchunks (less than +// 'mapgen_limit'), and corresponding exact limits for SAO entities. +void MapgenParams::calcMapgenEdges() +{ + if (m_mapgen_edges_calculated) + return; + + // Central chunk offset, in blocks + s16 ccoff_b = -chunksize / 2; + // Chunksize, in nodes + s32 csize_n = chunksize * MAP_BLOCKSIZE; + // Minp/maxp of central chunk, in nodes + s16 ccmin = ccoff_b * MAP_BLOCKSIZE; + s16 ccmax = ccmin + csize_n - 1; + // Fullminp/fullmaxp of central chunk, in nodes + s16 ccfmin = ccmin - MAP_BLOCKSIZE; + s16 ccfmax = ccmax + MAP_BLOCKSIZE; + // Effective mapgen limit, in blocks + // Uses same calculation as ServerMap::blockpos_over_mapgen_limit(v3s16 p) + s16 mapgen_limit_b = rangelim(mapgen_limit, + 0, MAX_MAP_GENERATION_LIMIT) / MAP_BLOCKSIZE; + // Effective mapgen limits, in nodes + s16 mapgen_limit_min = -mapgen_limit_b * MAP_BLOCKSIZE; + s16 mapgen_limit_max = (mapgen_limit_b + 1) * MAP_BLOCKSIZE - 1; + // Number of complete chunks from central chunk fullminp/fullmaxp + // to effective mapgen limits. + s16 numcmin = MYMAX((ccfmin - mapgen_limit_min) / csize_n, 0); + s16 numcmax = MYMAX((mapgen_limit_max - ccfmax) / csize_n, 0); + // Mapgen edges, in nodes + mapgen_edge_min = ccmin - numcmin * csize_n; + mapgen_edge_max = ccmax + numcmax * csize_n; + // SAO position limits, in Irrlicht units + m_sao_limit_min = mapgen_edge_min * BS - 3.0f; + m_sao_limit_max = mapgen_edge_max * BS + 3.0f; + + m_mapgen_edges_calculated = true; +} + + +bool MapgenParams::saoPosOverLimit(const v3f &p) +{ + if (!m_mapgen_edges_calculated) + calcMapgenEdges(); + + return p.X < m_sao_limit_min || + p.X > m_sao_limit_max || + p.Y < m_sao_limit_min || + p.Y > m_sao_limit_max || + p.Z < m_sao_limit_min || + p.Z > m_sao_limit_max; +} + + +s32 MapgenParams::getSpawnRangeMax() +{ + calcMapgenEdges(); + + return MYMIN(-mapgen_edge_min, mapgen_edge_max); +} diff --git a/src/mapgen/mapgen.h b/src/mapgen/mapgen.h new file mode 100644 index 000000000..8994fdc00 --- /dev/null +++ b/src/mapgen/mapgen.h @@ -0,0 +1,302 @@ +/* +Minetest +Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2015-2017 paramat + +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. +*/ + +#pragma once + +#include "noise.h" +#include "nodedef.h" +#include "util/string.h" +#include "util/container.h" + +#define MAPGEN_DEFAULT MAPGEN_V7 +#define MAPGEN_DEFAULT_NAME "v7" + +/////////////////// Mapgen flags +#define MG_TREES 0x01 // Deprecated. Moved into mgv6 flags +#define MG_CAVES 0x02 +#define MG_DUNGEONS 0x04 +#define MG_FLAT 0x08 // Deprecated. Moved into mgv6 flags +#define MG_LIGHT 0x10 +#define MG_DECORATIONS 0x20 + +typedef u8 biome_t; // copy from mg_biome.h to avoid an unnecessary include + +class Settings; +class MMVManip; +class INodeDefManager; + +extern FlagDesc flagdesc_mapgen[]; +extern FlagDesc flagdesc_gennotify[]; + +class Biome; +class BiomeGen; +struct BiomeParams; +class BiomeManager; +class EmergeManager; +class MapBlock; +class VoxelManipulator; +struct BlockMakeData; +class VoxelArea; +class Map; + +enum MapgenObject { + MGOBJ_VMANIP, + MGOBJ_HEIGHTMAP, + MGOBJ_BIOMEMAP, + MGOBJ_HEATMAP, + MGOBJ_HUMIDMAP, + MGOBJ_GENNOTIFY +}; + +enum GenNotifyType { + GENNOTIFY_DUNGEON, + GENNOTIFY_TEMPLE, + GENNOTIFY_CAVE_BEGIN, + GENNOTIFY_CAVE_END, + GENNOTIFY_LARGECAVE_BEGIN, + GENNOTIFY_LARGECAVE_END, + GENNOTIFY_DECORATION, + NUM_GENNOTIFY_TYPES +}; + +enum MgStoneType { + MGSTONE_STONE, + MGSTONE_DESERT_STONE, + MGSTONE_SANDSTONE, + MGSTONE_OTHER, +}; + +struct GenNotifyEvent { + GenNotifyType type; + v3s16 pos; + u32 id; +}; + +class GenerateNotifier { +public: + GenerateNotifier() = default; + GenerateNotifier(u32 notify_on, std::set<u32> *notify_on_deco_ids); + + void setNotifyOn(u32 notify_on); + void setNotifyOnDecoIds(std::set<u32> *notify_on_deco_ids); + + bool addEvent(GenNotifyType type, v3s16 pos, u32 id=0); + void getEvents(std::map<std::string, std::vector<v3s16> > &event_map, + bool peek_events=false); + +private: + u32 m_notify_on = 0; + std::set<u32> *m_notify_on_deco_ids; + std::list<GenNotifyEvent> m_notify_events; +}; + +enum MapgenType { + MAPGEN_V5, + MAPGEN_V6, + MAPGEN_V7, + MAPGEN_FLAT, + MAPGEN_FRACTAL, + MAPGEN_VALLEYS, + MAPGEN_SINGLENODE, + MAPGEN_CARPATHIAN, + MAPGEN_INVALID, +}; + +struct MapgenParams { + MapgenParams() = default; + virtual ~MapgenParams(); + + MapgenType mgtype = MAPGEN_DEFAULT; + s16 chunksize = 5; + u64 seed = 0; + s16 water_level = 1; + s16 mapgen_limit = MAX_MAP_GENERATION_LIMIT; + u32 flags = MG_CAVES | MG_LIGHT | MG_DECORATIONS; + + BiomeParams *bparams = nullptr; + + s16 mapgen_edge_min = -MAX_MAP_GENERATION_LIMIT; + s16 mapgen_edge_max = MAX_MAP_GENERATION_LIMIT; + + virtual void readParams(const Settings *settings); + virtual void writeParams(Settings *settings) const; + + bool saoPosOverLimit(const v3f &p); + s32 getSpawnRangeMax(); + +private: + void calcMapgenEdges(); + + float m_sao_limit_min = -MAX_MAP_GENERATION_LIMIT * BS; + float m_sao_limit_max = MAX_MAP_GENERATION_LIMIT * BS; + bool m_mapgen_edges_calculated = false; +}; + + +/* + 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: + s32 seed = 0; + int water_level = 0; + int mapgen_limit = 0; + u32 flags = 0; + bool generating = false; + int id = -1; + + MMVManip *vm = nullptr; + INodeDefManager *ndef = nullptr; + + u32 blockseed; + s16 *heightmap = nullptr; + biome_t *biomemap = nullptr; + v3s16 csize; + + BiomeGen *biomegen = nullptr; + GenerateNotifier gennotify; + + Mapgen() = default; + Mapgen(int mapgenid, MapgenParams *params, EmergeManager *emerge); + virtual ~Mapgen() = default; + DISABLE_CLASS_COPY(Mapgen); + + virtual MapgenType getType() const { return MAPGEN_INVALID; } + + static u32 getBlockSeed(v3s16 p, s32 seed); + static u32 getBlockSeed2(v3s16 p, s32 seed); + s16 findGroundLevelFull(v2s16 p2d); + s16 findGroundLevel(v2s16 p2d, s16 ymin, s16 ymax); + s16 findLiquidSurface(v2s16 p2d, s16 ymin, s16 ymax); + void updateHeightmap(v3s16 nmin, v3s16 nmax); + void getSurfaces(v2s16 p2d, s16 ymin, s16 ymax, + s16 *floors, s16 *ceilings, u16 *num_floors, u16 *num_ceilings); + + void updateLiquid(UniqueQueue<v3s16> *trans_liquid, v3s16 nmin, v3s16 nmax); + + void setLighting(u8 light, v3s16 nmin, v3s16 nmax); + void lightSpread(VoxelArea &a, v3s16 p, u8 light); + void calcLighting(v3s16 nmin, v3s16 nmax, v3s16 full_nmin, v3s16 full_nmax, + bool propagate_shadow = true); + void propagateSunlight(v3s16 nmin, v3s16 nmax, bool propagate_shadow); + void spreadLight(v3s16 nmin, v3s16 nmax); + + virtual void makeChunk(BlockMakeData *data) {} + virtual int getGroundLevelAtPoint(v2s16 p) { return 0; } + + // getSpawnLevelAtPoint() is a function within each mapgen that returns a + // suitable y co-ordinate for player spawn ('suitable' usually meaning + // within 16 nodes of water_level). If a suitable spawn level cannot be + // found at the specified (X, Z) 'MAX_MAP_GENERATION_LIMIT' is returned to + // signify this and to cause Server::findSpawnPos() to try another (X, Z). + virtual int getSpawnLevelAtPoint(v2s16 p) { return 0; } + + // Mapgen management functions + static MapgenType getMapgenType(const std::string &mgname); + static const char *getMapgenName(MapgenType mgtype); + static Mapgen *createMapgen(MapgenType mgtype, int mgid, + MapgenParams *params, EmergeManager *emerge); + static MapgenParams *createMapgenParams(MapgenType mgtype); + static void getMapgenNames(std::vector<const char *> *mgnames, bool include_hidden); + +private: + // isLiquidHorizontallyFlowable() is a helper function for updateLiquid() + // that checks whether there are floodable nodes without liquid beneath + // the node at index vi. + inline bool isLiquidHorizontallyFlowable(u32 vi, v3s16 em); +}; + +/* + 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: + MapgenBasic(int mapgenid, MapgenParams *params, EmergeManager *emerge); + virtual ~MapgenBasic(); + + virtual void generateCaves(s16 max_stone_y, s16 large_cave_depth); + virtual bool generateCaverns(s16 max_stone_y); + virtual void generateDungeons(s16 max_stone_y, + MgStoneType stone_type, content_t biome_stone); + virtual void generateBiomes(MgStoneType *mgstone_type, + content_t *biome_stone); + virtual void dustTopNodes(); + +protected: + EmergeManager *m_emerge; + BiomeManager *m_bmgr; + + Noise *noise_filler_depth; + + v3s16 node_min; + v3s16 node_max; + v3s16 full_node_min; + v3s16 full_node_max; + + // Content required for generateBiomes + content_t c_stone; + content_t c_desert_stone; + content_t c_sandstone; + content_t c_water_source; + content_t c_river_water_source; + content_t c_lava_source; + + // Content required for generateDungeons + content_t c_cobble; + content_t c_stair_cobble; + content_t c_mossycobble; + content_t c_stair_desert_stone; + content_t c_sandstonebrick; + content_t c_stair_sandstone_block; + + int ystride; + int zstride; + int zstride_1d; + int zstride_1u1d; + + u32 spflags; + + NoiseParams np_cave1; + NoiseParams np_cave2; + NoiseParams np_cavern; + float cave_width; + float cavern_limit; + float cavern_taper; + float cavern_threshold; + int lava_depth; +}; diff --git a/src/mapgen/mapgen_carpathian.cpp b/src/mapgen/mapgen_carpathian.cpp new file mode 100644 index 000000000..9884ce40b --- /dev/null +++ b/src/mapgen/mapgen_carpathian.cpp @@ -0,0 +1,454 @@ +/* +Minetest +Copyright (C) 2010-2016 paramat, Matt Gregory +Copyright (C) 2010-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2017 vlapsley, Vaughan Lapsley <vlapsley@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 <cmath> +#include "mapgen.h" +#include "voxel.h" +#include "noise.h" +#include "mapblock.h" +#include "mapnode.h" +#include "map.h" +#include "content_sao.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_carpathian.h" + + +FlagDesc flagdesc_mapgen_carpathian[] = { + {"caverns", MGCARPATHIAN_CAVERNS}, + {NULL, 0} +}; + + +/////////////////////////////////////////////////////////////////////////////// + + +MapgenCarpathian::MapgenCarpathian( + int mapgenid, MapgenCarpathianParams *params, EmergeManager *emerge) + : MapgenBasic(mapgenid, params, emerge) +{ + spflags = params->spflags; + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; + lava_depth = params->lava_depth; + cavern_limit = params->cavern_limit; + cavern_taper = params->cavern_taper; + cavern_threshold = params->cavern_threshold; + grad_wl = 1 - water_level; + + //// 2D Terrain noise + noise_base = new Noise(¶ms->np_base, seed, csize.X, csize.Z); + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + noise_height1 = new Noise(¶ms->np_height1, seed, csize.X, csize.Z); + noise_height2 = new Noise(¶ms->np_height2, seed, csize.X, csize.Z); + noise_height3 = new Noise(¶ms->np_height3, seed, csize.X, csize.Z); + noise_height4 = new Noise(¶ms->np_height4, seed, csize.X, csize.Z); + noise_hills_terrain = new Noise(¶ms->np_hills_terrain, seed, csize.X, csize.Z); + noise_ridge_terrain = new Noise(¶ms->np_ridge_terrain, seed, csize.X, csize.Z); + noise_step_terrain = new Noise(¶ms->np_step_terrain, seed, csize.X, csize.Z); + noise_hills = new Noise(¶ms->np_hills, seed, csize.X, csize.Z); + noise_ridge_mnt = new Noise(¶ms->np_ridge_mnt, seed, csize.X, csize.Z); + noise_step_mnt = new Noise(¶ms->np_step_mnt, seed, csize.X, csize.Z); + + //// 3D terrain noise + // 1 up 1 down overgeneration + noise_mnt_var = new Noise(¶ms->np_mnt_var, seed, csize.X, csize.Y + 2, csize.Z); + + //// Cave noise + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; + MapgenBasic::np_cavern = params->np_cavern; +} + + +MapgenCarpathian::~MapgenCarpathian() +{ + delete noise_base; + delete noise_filler_depth; + delete noise_height1; + delete noise_height2; + delete noise_height3; + delete noise_height4; + delete noise_hills_terrain; + delete noise_ridge_terrain; + delete noise_step_terrain; + delete noise_hills; + delete noise_ridge_mnt; + delete noise_step_mnt; + delete noise_mnt_var; +} + + +MapgenCarpathianParams::MapgenCarpathianParams(): + np_base (12, 1, v3f(2557, 2557, 2557), 6538, 4, 0.8, 0.5), + np_filler_depth (0, 1, v3f(128, 128, 128), 261, 3, 0.7, 2.0), + np_height1 (0, 5, v3f(251, 251, 251), 9613, 5, 0.5, 2.0), + np_height2 (0, 5, v3f(383, 383, 383), 1949, 5, 0.5, 2.0), + np_height3 (0, 5, v3f(509, 509, 509), 3211, 5, 0.5, 2.0), + np_height4 (0, 5, v3f(631, 631, 631), 1583, 5, 0.5, 2.0), + np_hills_terrain (1, 1, v3f(1301, 1301, 1301), 1692, 5, 0.5, 2.0), + np_ridge_terrain (1, 1, v3f(1889, 1889, 1889), 3568, 5, 0.5, 2.0), + np_step_terrain (1, 1, v3f(1889, 1889, 1889), 4157, 5, 0.5, 2.0), + np_hills (0, 3, v3f(257, 257, 257), 6604, 6, 0.5, 2.0), + np_ridge_mnt (0, 12, v3f(743, 743, 743), 5520, 6, 0.7, 2.0), + np_step_mnt (0, 8, v3f(509, 509, 509), 2590, 6, 0.6, 2.0), + np_mnt_var (0, 1, v3f(499, 499, 499), 2490, 5, 0.55, 2.0), + np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), + np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), + np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0) +{ +} + + +void MapgenCarpathianParams::readParams(const Settings *settings) +{ + settings->getFlagStrNoEx("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian); + settings->getFloatNoEx("mgcarpathian_cave_width", cave_width); + settings->getS16NoEx("mgcarpathian_large_cave_depth", large_cave_depth); + settings->getS16NoEx("mgcarpathian_lava_depth", lava_depth); + settings->getS16NoEx("mgcarpathian_cavern_limit", cavern_limit); + settings->getS16NoEx("mgcarpathian_cavern_taper", cavern_taper); + settings->getFloatNoEx("mgcarpathian_cavern_threshold", cavern_threshold); + + settings->getNoiseParams("mgcarpathian_np_base", np_base); + settings->getNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgcarpathian_np_height1", np_height1); + settings->getNoiseParams("mgcarpathian_np_height2", np_height2); + settings->getNoiseParams("mgcarpathian_np_height3", np_height3); + settings->getNoiseParams("mgcarpathian_np_height4", np_height4); + settings->getNoiseParams("mgcarpathian_np_hills_terrain", np_hills_terrain); + settings->getNoiseParams("mgcarpathian_np_ridge_terrain", np_ridge_terrain); + settings->getNoiseParams("mgcarpathian_np_step_terrain", np_step_terrain); + settings->getNoiseParams("mgcarpathian_np_hills", np_hills); + settings->getNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt); + settings->getNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt); + settings->getNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var); + settings->getNoiseParams("mgcarpathian_np_cave1", np_cave1); + settings->getNoiseParams("mgcarpathian_np_cave2", np_cave2); + settings->getNoiseParams("mgcarpathian_np_cavern", np_cavern); +} + + +void MapgenCarpathianParams::writeParams(Settings *settings) const +{ + settings->setFlagStr("mgcarpathian_spflags", spflags, flagdesc_mapgen_carpathian, U32_MAX); + settings->setFloat("mgcarpathian_cave_width", cave_width); + settings->setS16("mgcarpathian_large_cave_depth", large_cave_depth); + settings->setS16("mgcarpathian_lava_depth", lava_depth); + settings->setS16("mgcarpathian_cavern_limit", cavern_limit); + settings->setS16("mgcarpathian_cavern_taper", cavern_taper); + settings->setFloat("mgcarpathian_cavern_threshold", cavern_threshold); + + settings->setNoiseParams("mgcarpathian_np_base", np_base); + settings->setNoiseParams("mgcarpathian_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgcarpathian_np_height1", np_height1); + settings->setNoiseParams("mgcarpathian_np_height2", np_height2); + settings->setNoiseParams("mgcarpathian_np_height3", np_height3); + settings->setNoiseParams("mgcarpathian_np_height4", np_height4); + settings->setNoiseParams("mgcarpathian_np_hills_terrain", np_hills_terrain); + settings->setNoiseParams("mgcarpathian_np_ridge_terrain", np_ridge_terrain); + settings->setNoiseParams("mgcarpathian_np_step_terrain", np_step_terrain); + settings->setNoiseParams("mgcarpathian_np_hills", np_hills); + settings->setNoiseParams("mgcarpathian_np_ridge_mnt", np_ridge_mnt); + settings->setNoiseParams("mgcarpathian_np_step_mnt", np_step_mnt); + settings->setNoiseParams("mgcarpathian_np_mnt_var", np_mnt_var); + settings->setNoiseParams("mgcarpathian_np_cave1", np_cave1); + settings->setNoiseParams("mgcarpathian_np_cave2", np_cave2); + settings->setNoiseParams("mgcarpathian_np_cavern", np_cavern); +} + + +/////////////////////////////////////////////////////////////////////////////// + + +// Lerp function +inline float MapgenCarpathian::getLerp(float noise1, float noise2, float mod) +{ + return noise1 + mod * (noise2 - noise1); +} + +// Steps function +float MapgenCarpathian::getSteps(float noise) +{ + float w = 0.5f; + float k = floor(noise / w); + float f = (noise - k * w) / w; + float s = std::fmin(2.f * f, 1.f); + return (k + s) * w; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +void MapgenCarpathian::makeChunk(BlockMakeData *data) +{ + // Pre-conditions + assert(data->vmanip); + assert(data->nodedef); + assert(data->blockpos_requested.X >= data->blockpos_min.X && + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); + assert(data->blockpos_requested.X <= data->blockpos_max.X && + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); + + this->generating = true; + this->vm = data->vmanip; + this->ndef = data->nodedef; + + 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); + + // Create a block-specific seed + blockseed = getBlockSeed2(full_node_min, seed); + + // Generate terrain + s16 stone_surface_max_y = generateTerrain(); + + // Create heightmap + updateHeightmap(node_min, node_max); + + // Init biome generator, place biome-specific nodes, and build biomemap + biomegen->calcBiomeNoise(node_min); + + MgStoneType mgstone_type; + content_t biome_stone; + generateBiomes(&mgstone_type, &biome_stone); + + // Generate caverns, tunnels and classic caves + if (flags & MG_CAVES) { + bool has_cavern = false; + // Generate caverns + if (spflags & MGCARPATHIAN_CAVERNS) + has_cavern = generateCaverns(stone_surface_max_y); + // Generate tunnels and classic caves + if (has_cavern) + // Disable classic caves in this mapchunk by setting + // 'large cave depth' to world base. Avoids excessive liquid in + // large caverns and floating blobs of overgenerated liquid. + generateCaves(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT); + else + generateCaves(stone_surface_max_y, large_cave_depth); + } + + // Generate dungeons + if (flags & MG_DUNGEONS) + generateDungeons(stone_surface_max_y, mgstone_type, biome_stone); + + // Generate the registered decorations + if (flags & MG_DECORATIONS) + m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max); + + // Generate the registered ores + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + + // Sprinkle some dust on top after everything else was generated + 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); + } + + this->generating = false; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +int MapgenCarpathian::getSpawnLevelAtPoint(v2s16 p) +{ + s16 level_at_point = terrainLevelAtPoint(p.X, p.Y); + if (level_at_point <= water_level || level_at_point > water_level + 32) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + + return level_at_point; +} + + +float MapgenCarpathian::terrainLevelAtPoint(s16 x, s16 z) +{ + float ground = NoisePerlin2D(&noise_base->np, x, z, seed); + float height1 = NoisePerlin2D(&noise_height1->np, x, z, seed); + float height2 = NoisePerlin2D(&noise_height2->np, x, z, seed); + float height3 = NoisePerlin2D(&noise_height3->np, x, z, seed); + float height4 = NoisePerlin2D(&noise_height4->np, x, z, seed); + float hter = NoisePerlin2D(&noise_hills_terrain->np, x, z, seed); + float rter = NoisePerlin2D(&noise_ridge_terrain->np, x, z, seed); + float ster = NoisePerlin2D(&noise_step_terrain->np, x, z, seed); + float n_hills = NoisePerlin2D(&noise_hills->np, x, z, seed); + float n_ridge_mnt = NoisePerlin2D(&noise_ridge_mnt->np, x, z, seed); + float n_step_mnt = NoisePerlin2D(&noise_step_mnt->np, x, z, seed); + + int height = -MAX_MAP_GENERATION_LIMIT; + + for (s16 y = 1; y <= 30; y++) { + float mnt_var = NoisePerlin3D(&noise_mnt_var->np, x, y, z, seed); + + // Gradient & shallow seabed + s32 grad = (y < water_level) ? grad_wl + (water_level - y) * 3 : 1 - y; + + // Hill/Mountain height (hilliness) + float hill1 = getLerp(height1, height2, mnt_var); + float hill2 = getLerp(height3, height4, mnt_var); + float hill3 = getLerp(height3, height2, mnt_var); + float hill4 = getLerp(height1, height4, mnt_var); + float hilliness = std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4)); + + // Rolling hills + float hill_mnt = hilliness * pow(n_hills, 2.f); + float hills = pow(hter, 3.f) * hill_mnt; + + // Ridged mountains + float ridge_mnt = hilliness * (1.f - fabs(n_ridge_mnt)); + float ridged_mountains = pow(rter, 3.f) * ridge_mnt; + + // Step (terraced) mountains + float step_mnt = hilliness * getSteps(n_step_mnt); + float step_mountains = pow(ster, 3.f) * step_mnt; + + // Final terrain level + float mountains = hills + ridged_mountains + step_mountains; + float surface_level = ground + mountains + grad; + + if (y > surface_level && height < 0) + height = y; + } + + return height; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +int MapgenCarpathian::generateTerrain() +{ + MapNode mn_air(CONTENT_AIR); + MapNode mn_stone(c_stone); + MapNode mn_water(c_water_source); + + s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; + u32 index2d = 0; + u32 index3d = 0; + + // Calculate noise for terrain generation + noise_base->perlinMap2D(node_min.X, node_min.Z); + noise_height1->perlinMap2D(node_min.X, node_min.Z); + noise_height2->perlinMap2D(node_min.X, node_min.Z); + noise_height3->perlinMap2D(node_min.X, node_min.Z); + noise_height4->perlinMap2D(node_min.X, node_min.Z); + noise_hills_terrain->perlinMap2D(node_min.X, node_min.Z); + noise_ridge_terrain->perlinMap2D(node_min.X, node_min.Z); + noise_step_terrain->perlinMap2D(node_min.X, node_min.Z); + noise_hills->perlinMap2D(node_min.X, node_min.Z); + noise_ridge_mnt->perlinMap2D(node_min.X, node_min.Z); + noise_step_mnt->perlinMap2D(node_min.X, node_min.Z); + noise_mnt_var->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + + //// Place nodes + for (s16 z = node_min.Z; z <= node_max.Z; z++) { + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { + u32 vi = vm->m_area.index(node_min.X, y, z); + for (s16 x = node_min.X; x <= node_max.X; + x++, vi++, index2d++, index3d++) { + if (vm->m_data[vi].getContent() != CONTENT_IGNORE) + continue; + + // Base terrain + float ground = noise_base->result[index2d]; + + // Gradient & shallow seabed + s32 grad = (y < water_level) ? grad_wl + (water_level - y) * 3 : 1 - y; + + // Hill/Mountain height (hilliness) + float height1 = noise_height1->result[index2d]; + float height2 = noise_height2->result[index2d]; + float height3 = noise_height3->result[index2d]; + float height4 = noise_height4->result[index2d]; + float mnt_var = noise_mnt_var->result[index3d]; + // Combine height noises and apply 3D variation + float hill1 = getLerp(height1, height2, mnt_var); + float hill2 = getLerp(height3, height4, mnt_var); + float hill3 = getLerp(height3, height2, mnt_var); + float hill4 = getLerp(height1, height4, mnt_var); + // 'hilliness' determines whether hills/mountains are + // small or large + float hilliness = std::fmax(std::fmin(hill1, hill2), std::fmin(hill3, hill4)); + + // Rolling hills + float hter = noise_hills_terrain->result[index2d]; + float n_hills = noise_hills->result[index2d]; + float hill_mnt = hilliness * pow(n_hills, 2.f); + float hills = pow(fabs(hter), 3.f) * hill_mnt; + + // Ridged mountains + float rter = noise_ridge_terrain->result[index2d]; + float n_ridge_mnt = noise_ridge_mnt->result[index2d]; + float ridge_mnt = hilliness * (1.f - fabs(n_ridge_mnt)); + float ridged_mountains = pow(fabs(rter), 3.f) * ridge_mnt; + + // Step (terraced) mountains + float ster = noise_step_terrain->result[index2d]; + float n_step_mnt = noise_step_mnt->result[index2d]; + float step_mnt = hilliness * getSteps(n_step_mnt); + float step_mountains = pow(fabs(ster), 3.f) * step_mnt; + + // Final terrain level + float mountains = hills + ridged_mountains + step_mountains; + float surface_level = ground + mountains + grad; + + if (y < surface_level) { + vm->m_data[vi] = mn_stone; // Stone + if (y > stone_surface_max_y) + stone_surface_max_y = y; + } else if (y <= water_level) { + vm->m_data[vi] = mn_water; // Sea water + } else { + vm->m_data[vi] = mn_air; // Air + } + } + index2d -= ystride; + } + index2d += ystride; + } + + return stone_surface_max_y; +} diff --git a/src/mapgen/mapgen_carpathian.h b/src/mapgen/mapgen_carpathian.h new file mode 100644 index 000000000..7dbccde2a --- /dev/null +++ b/src/mapgen/mapgen_carpathian.h @@ -0,0 +1,102 @@ +/* +Minetest +Copyright (C) 2010-2016 paramat, Matt Gregory +Copyright (C) 2010-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2017 vlapsley, Vaughan Lapsley <vlapsley@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. +*/ + +#pragma once + +#include "mapgen.h" + +///////// Mapgen Carpathian flags +#define MGCARPATHIAN_CAVERNS 0x01 + +class BiomeManager; + +extern FlagDesc flagdesc_mapgen_carpathian[]; + + +struct MapgenCarpathianParams : public MapgenParams +{ + u32 spflags = MGCARPATHIAN_CAVERNS; + float cave_width = 0.09f; + s16 large_cave_depth = -33; + s16 lava_depth = -256; + s16 cavern_limit = -256; + s16 cavern_taper = 256; + float cavern_threshold = 0.7f; + + NoiseParams np_base; + NoiseParams np_filler_depth; + NoiseParams np_height1; + NoiseParams np_height2; + NoiseParams np_height3; + NoiseParams np_height4; + NoiseParams np_hills_terrain; + NoiseParams np_ridge_terrain; + NoiseParams np_step_terrain; + NoiseParams np_hills; + NoiseParams np_ridge_mnt; + NoiseParams np_step_mnt; + NoiseParams np_mnt_var; + NoiseParams np_cave1; + NoiseParams np_cave2; + NoiseParams np_cavern; + + MapgenCarpathianParams(); + ~MapgenCarpathianParams() = default; + + void readParams(const Settings *settings); + void writeParams(Settings *settings) const; +}; + +class MapgenCarpathian : public MapgenBasic +{ +public: + MapgenCarpathian(int mapgenid, MapgenCarpathianParams *params, + EmergeManager *emerge); + ~MapgenCarpathian(); + + virtual MapgenType getType() const { return MAPGEN_CARPATHIAN; } + + float getSteps(float noise); + inline float getLerp(float noise1, float noise2, float mod); + + virtual void makeChunk(BlockMakeData *data); + int getSpawnLevelAtPoint(v2s16 p); + +private: + s16 large_cave_depth; + s32 grad_wl; + + Noise *noise_base; + Noise *noise_height1; + Noise *noise_height2; + Noise *noise_height3; + Noise *noise_height4; + Noise *noise_hills_terrain; + Noise *noise_ridge_terrain; + Noise *noise_step_terrain; + Noise *noise_hills; + Noise *noise_ridge_mnt; + Noise *noise_step_mnt; + Noise *noise_mnt_var; + + float terrainLevelAtPoint(s16 x, s16 z); + int generateTerrain(); +}; diff --git a/src/mapgen/mapgen_flat.cpp b/src/mapgen/mapgen_flat.cpp new file mode 100644 index 000000000..57c20d470 --- /dev/null +++ b/src/mapgen/mapgen_flat.cpp @@ -0,0 +1,274 @@ +/* +Minetest +Copyright (C) 2015-2017 paramat +Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +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 "voxel.h" +#include "noise.h" +#include "mapblock.h" +#include "mapnode.h" +#include "map.h" +#include "content_sao.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_flat.h" + + +FlagDesc flagdesc_mapgen_flat[] = { + {"lakes", MGFLAT_LAKES}, + {"hills", MGFLAT_HILLS}, + {NULL, 0} +}; + +/////////////////////////////////////////////////////////////////////////////////////// + + +MapgenFlat::MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *emerge) + : MapgenBasic(mapgenid, params, emerge) +{ + spflags = params->spflags; + ground_level = params->ground_level; + large_cave_depth = params->large_cave_depth; + lava_depth = params->lava_depth; + cave_width = params->cave_width; + lake_threshold = params->lake_threshold; + lake_steepness = params->lake_steepness; + hill_threshold = params->hill_threshold; + hill_steepness = params->hill_steepness; + + // 2D noise + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + + if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) + noise_terrain = new Noise(¶ms->np_terrain, seed, csize.X, csize.Z); + // 3D noise + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; +} + + +MapgenFlat::~MapgenFlat() +{ + delete noise_filler_depth; + + if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) + delete noise_terrain; +} + + +MapgenFlatParams::MapgenFlatParams(): + np_terrain (0, 1, v3f(600, 600, 600), 7244, 5, 0.6, 2.0), + np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0), + np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), + np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0) +{ +} + + +void MapgenFlatParams::readParams(const Settings *settings) +{ + settings->getFlagStrNoEx("mgflat_spflags", spflags, flagdesc_mapgen_flat); + settings->getS16NoEx("mgflat_ground_level", ground_level); + settings->getS16NoEx("mgflat_large_cave_depth", large_cave_depth); + settings->getS16NoEx("mgflat_lava_depth", lava_depth); + settings->getFloatNoEx("mgflat_cave_width", cave_width); + settings->getFloatNoEx("mgflat_lake_threshold", lake_threshold); + settings->getFloatNoEx("mgflat_lake_steepness", lake_steepness); + settings->getFloatNoEx("mgflat_hill_threshold", hill_threshold); + settings->getFloatNoEx("mgflat_hill_steepness", hill_steepness); + + settings->getNoiseParams("mgflat_np_terrain", np_terrain); + settings->getNoiseParams("mgflat_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgflat_np_cave1", np_cave1); + settings->getNoiseParams("mgflat_np_cave2", np_cave2); +} + + +void MapgenFlatParams::writeParams(Settings *settings) const +{ + settings->setFlagStr("mgflat_spflags", spflags, flagdesc_mapgen_flat, U32_MAX); + settings->setS16("mgflat_ground_level", ground_level); + settings->setS16("mgflat_large_cave_depth", large_cave_depth); + settings->setS16("mgflat_lava_depth", lava_depth); + settings->setFloat("mgflat_cave_width", cave_width); + settings->setFloat("mgflat_lake_threshold", lake_threshold); + settings->setFloat("mgflat_lake_steepness", lake_steepness); + settings->setFloat("mgflat_hill_threshold", hill_threshold); + settings->setFloat("mgflat_hill_steepness", hill_steepness); + + settings->setNoiseParams("mgflat_np_terrain", np_terrain); + settings->setNoiseParams("mgflat_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgflat_np_cave1", np_cave1); + settings->setNoiseParams("mgflat_np_cave2", np_cave2); +} + + +///////////////////////////////////////////////////////////////// + + +int MapgenFlat::getSpawnLevelAtPoint(v2s16 p) +{ + s16 level_at_point = ground_level; + float n_terrain = 0.0f; + if ((spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS)) + n_terrain = NoisePerlin2D(&noise_terrain->np, p.X, p.Y, seed); + + if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) { + level_at_point = ground_level - + (lake_threshold - n_terrain) * lake_steepness; + } else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) { + level_at_point = ground_level + + (n_terrain - hill_threshold) * hill_steepness; + } + + if (ground_level < water_level) // Ocean world, allow spawn in water + return MYMAX(level_at_point, water_level); + + if (level_at_point > water_level) + return level_at_point; // Spawn on land + + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point +} + + +void MapgenFlat::makeChunk(BlockMakeData *data) +{ + // Pre-conditions + assert(data->vmanip); + assert(data->nodedef); + assert(data->blockpos_requested.X >= data->blockpos_min.X && + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); + assert(data->blockpos_requested.X <= data->blockpos_max.X && + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); + + this->generating = true; + this->vm = data->vmanip; + this->ndef = data->nodedef; + //TimeTaker t("makeChunk"); + + 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); + + blockseed = getBlockSeed2(full_node_min, seed); + + // Generate base terrain, mountains, and ridges with initial heightmaps + s16 stone_surface_max_y = generateTerrain(); + + // Create heightmap + updateHeightmap(node_min, node_max); + + // Init biome generator, place biome-specific nodes, and build biomemap + biomegen->calcBiomeNoise(node_min); + + MgStoneType mgstone_type; + content_t biome_stone; + generateBiomes(&mgstone_type, &biome_stone); + + if (flags & MG_CAVES) + generateCaves(stone_surface_max_y, large_cave_depth); + + if (flags & MG_DUNGEONS) + generateDungeons(stone_surface_max_y, mgstone_type, biome_stone); + + // Generate the registered decorations + if (flags & MG_DECORATIONS) + m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max); + + // Generate the registered ores + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + + // Sprinkle some dust on top after everything else was generated + dustTopNodes(); + + //printf("makeChunk: %dms\n", t.stop()); + + updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); + + if (flags & MG_LIGHT) + calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0), + full_node_min, full_node_max); + + //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE, + // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF); + + this->generating = false; +} + + +s16 MapgenFlat::generateTerrain() +{ + MapNode n_air(CONTENT_AIR); + MapNode n_stone(c_stone); + MapNode n_water(c_water_source); + + const v3s16 &em = vm->m_area.getExtent(); + s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; + u32 ni2d = 0; + + bool use_noise = (spflags & MGFLAT_LAKES) || (spflags & MGFLAT_HILLS); + if (use_noise) + noise_terrain->perlinMap2D(node_min.X, node_min.Z); + + for (s16 z = node_min.Z; z <= node_max.Z; z++) + for (s16 x = node_min.X; x <= node_max.X; x++, ni2d++) { + s16 stone_level = ground_level; + float n_terrain = use_noise ? noise_terrain->result[ni2d] : 0.0f; + + if ((spflags & MGFLAT_LAKES) && n_terrain < lake_threshold) { + s16 depress = (lake_threshold - n_terrain) * lake_steepness; + stone_level = ground_level - depress; + } else if ((spflags & MGFLAT_HILLS) && n_terrain > hill_threshold) { + s16 rise = (n_terrain - hill_threshold) * hill_steepness; + stone_level = ground_level + rise; + } + + 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++) { + if (vm->m_data[vi].getContent() == CONTENT_IGNORE) { + if (y <= stone_level) { + vm->m_data[vi] = n_stone; + if (y > stone_surface_max_y) + stone_surface_max_y = y; + } else if (y <= water_level) { + vm->m_data[vi] = n_water; + } else { + vm->m_data[vi] = n_air; + } + } + vm->m_area.add_y(em, vi, 1); + } + } + + return stone_surface_max_y; +} diff --git a/src/mapgen/mapgen_flat.h b/src/mapgen/mapgen_flat.h new file mode 100644 index 000000000..635d40625 --- /dev/null +++ b/src/mapgen/mapgen_flat.h @@ -0,0 +1,76 @@ +/* +Minetest +Copyright (C) 2015-2017 paramat +Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +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. +*/ + +#pragma once + +#include "mapgen.h" + +/////// Mapgen Flat flags +#define MGFLAT_LAKES 0x01 +#define MGFLAT_HILLS 0x02 + +class BiomeManager; + +extern FlagDesc flagdesc_mapgen_flat[]; + +struct MapgenFlatParams : public MapgenParams +{ + u32 spflags = 0; + s16 ground_level = 8; + s16 large_cave_depth = -33; + s16 lava_depth = -256; + float cave_width = 0.09f; + float lake_threshold = -0.45f; + float lake_steepness = 48.0f; + float hill_threshold = 0.45f; + float hill_steepness = 64.0f; + NoiseParams np_terrain; + NoiseParams np_filler_depth; + NoiseParams np_cave1; + NoiseParams np_cave2; + + MapgenFlatParams(); + ~MapgenFlatParams() = default; + + void readParams(const Settings *settings); + void writeParams(Settings *settings) const; +}; + +class MapgenFlat : public MapgenBasic +{ +public: + MapgenFlat(int mapgenid, MapgenFlatParams *params, EmergeManager *emerge); + ~MapgenFlat(); + + virtual MapgenType getType() const { return MAPGEN_FLAT; } + + virtual void makeChunk(BlockMakeData *data); + int getSpawnLevelAtPoint(v2s16 p); + s16 generateTerrain(); + +private: + s16 ground_level; + s16 large_cave_depth; + float lake_threshold; + float lake_steepness; + float hill_threshold; + float hill_steepness; + Noise *noise_terrain; +}; diff --git a/src/mapgen/mapgen_fractal.cpp b/src/mapgen/mapgen_fractal.cpp new file mode 100644 index 000000000..b06885826 --- /dev/null +++ b/src/mapgen/mapgen_fractal.cpp @@ -0,0 +1,404 @@ +/* +Minetest +Copyright (C) 2015-2017 paramat +Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +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 "voxel.h" +#include "noise.h" +#include "mapblock.h" +#include "mapnode.h" +#include "map.h" +#include "content_sao.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_fractal.h" + + +FlagDesc flagdesc_mapgen_fractal[] = { + {NULL, 0} +}; + +/////////////////////////////////////////////////////////////////////////////////////// + + +MapgenFractal::MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeManager *emerge) + : MapgenBasic(mapgenid, params, emerge) +{ + spflags = params->spflags; + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; + lava_depth = params->lava_depth; + fractal = params->fractal; + iterations = params->iterations; + scale = params->scale; + offset = params->offset; + slice_w = params->slice_w; + julia_x = params->julia_x; + julia_y = params->julia_y; + julia_z = params->julia_z; + julia_w = params->julia_w; + + //// 2D terrain noise + noise_seabed = new Noise(¶ms->np_seabed, seed, csize.X, csize.Z); + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; + + formula = fractal / 2 + fractal % 2; + julia = fractal % 2 == 0; +} + + +MapgenFractal::~MapgenFractal() +{ + delete noise_seabed; + delete noise_filler_depth; +} + + +MapgenFractalParams::MapgenFractalParams(): + np_seabed (-14, 9, v3f(600, 600, 600), 41900, 5, 0.6, 2.0), + np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0), + np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), + np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0) +{ +} + + +void MapgenFractalParams::readParams(const Settings *settings) +{ + settings->getFlagStrNoEx("mgfractal_spflags", spflags, flagdesc_mapgen_fractal); + settings->getFloatNoEx("mgfractal_cave_width", cave_width); + settings->getS16NoEx("mgfractal_large_cave_depth", large_cave_depth); + settings->getS16NoEx("mgfractal_lava_depth", lava_depth); + settings->getU16NoEx("mgfractal_fractal", fractal); + settings->getU16NoEx("mgfractal_iterations", iterations); + settings->getV3FNoEx("mgfractal_scale", scale); + settings->getV3FNoEx("mgfractal_offset", offset); + settings->getFloatNoEx("mgfractal_slice_w", slice_w); + settings->getFloatNoEx("mgfractal_julia_x", julia_x); + settings->getFloatNoEx("mgfractal_julia_y", julia_y); + settings->getFloatNoEx("mgfractal_julia_z", julia_z); + settings->getFloatNoEx("mgfractal_julia_w", julia_w); + + settings->getNoiseParams("mgfractal_np_seabed", np_seabed); + settings->getNoiseParams("mgfractal_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgfractal_np_cave1", np_cave1); + settings->getNoiseParams("mgfractal_np_cave2", np_cave2); +} + + +void MapgenFractalParams::writeParams(Settings *settings) const +{ + settings->setFlagStr("mgfractal_spflags", spflags, flagdesc_mapgen_fractal, U32_MAX); + settings->setFloat("mgfractal_cave_width", cave_width); + settings->setS16("mgfractal_large_cave_depth", large_cave_depth); + settings->setS16("mgfractal_lava_depth", lava_depth); + settings->setU16("mgfractal_fractal", fractal); + settings->setU16("mgfractal_iterations", iterations); + settings->setV3F("mgfractal_scale", scale); + settings->setV3F("mgfractal_offset", offset); + settings->setFloat("mgfractal_slice_w", slice_w); + settings->setFloat("mgfractal_julia_x", julia_x); + settings->setFloat("mgfractal_julia_y", julia_y); + settings->setFloat("mgfractal_julia_z", julia_z); + settings->setFloat("mgfractal_julia_w", julia_w); + + settings->setNoiseParams("mgfractal_np_seabed", np_seabed); + settings->setNoiseParams("mgfractal_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgfractal_np_cave1", np_cave1); + settings->setNoiseParams("mgfractal_np_cave2", np_cave2); +} + + +///////////////////////////////////////////////////////////////// + + +int MapgenFractal::getSpawnLevelAtPoint(v2s16 p) +{ + bool solid_below = false; // Dry solid node is present below to spawn on + u8 air_count = 0; // Consecutive air nodes above the dry solid node + s16 seabed_level = NoisePerlin2D(&noise_seabed->np, p.X, p.Y, seed); + // Seabed can rise above water_level or might be raised to create dry land + s16 search_start = MYMAX(seabed_level, water_level + 1); + if (seabed_level > water_level) + solid_below = true; + + for (s16 y = search_start; y <= search_start + 128; y++) { + if (getFractalAtPoint(p.X, y, p.Y)) { // Fractal node + solid_below = true; + air_count = 0; + } else if (solid_below) { // Air above solid node + air_count++; + // 3 to account for snowblock dust + if (air_count == 3) + return y - 2; + } + } + + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point +} + + +void MapgenFractal::makeChunk(BlockMakeData *data) +{ + // Pre-conditions + assert(data->vmanip); + assert(data->nodedef); + assert(data->blockpos_requested.X >= data->blockpos_min.X && + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); + assert(data->blockpos_requested.X <= data->blockpos_max.X && + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); + + this->generating = true; + this->vm = data->vmanip; + this->ndef = data->nodedef; + //TimeTaker t("makeChunk"); + + 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); + + blockseed = getBlockSeed2(full_node_min, seed); + + // Generate base terrain, mountains, and ridges with initial heightmaps + s16 stone_surface_max_y = generateTerrain(); + + // Create heightmap + updateHeightmap(node_min, node_max); + + // Init biome generator, place biome-specific nodes, and build biomemap + biomegen->calcBiomeNoise(node_min); + + MgStoneType mgstone_type; + content_t biome_stone; + generateBiomes(&mgstone_type, &biome_stone); + + if (flags & MG_CAVES) + generateCaves(stone_surface_max_y, large_cave_depth); + + if (flags & MG_DUNGEONS) + generateDungeons(stone_surface_max_y, mgstone_type, biome_stone); + + // Generate the registered decorations + if (flags & MG_DECORATIONS) + m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max); + + // Generate the registered ores + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + + // Sprinkle some dust on top after everything else was generated + dustTopNodes(); + + //printf("makeChunk: %dms\n", t.stop()); + + updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); + + if (flags & MG_LIGHT) + calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0), + full_node_min, full_node_max); + + //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE, + // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF); + + this->generating = false; +} + + +bool MapgenFractal::getFractalAtPoint(s16 x, s16 y, s16 z) +{ + float cx, cy, cz, cw, ox, oy, oz, ow; + + if (julia) { // Julia set + cx = julia_x; + cy = julia_y; + cz = julia_z; + cw = julia_w; + ox = (float)x / scale.X - offset.X; + oy = (float)y / scale.Y - offset.Y; + oz = (float)z / scale.Z - offset.Z; + ow = slice_w; + } else { // Mandelbrot set + cx = (float)x / scale.X - offset.X; + cy = (float)y / scale.Y - offset.Y; + cz = (float)z / scale.Z - offset.Z; + cw = slice_w; + ox = 0.0f; + oy = 0.0f; + oz = 0.0f; + ow = 0.0f; + } + + float nx = 0.0f; + float ny = 0.0f; + float nz = 0.0f; + float nw = 0.0f; + + for (u16 iter = 0; iter < iterations; iter++) { + switch (formula) { + default: + case 1: // 4D "Roundy" + nx = ox * ox - oy * oy - oz * oz - ow * ow + cx; + ny = 2.0f * (ox * oy + oz * ow) + cy; + nz = 2.0f * (ox * oz + oy * ow) + cz; + nw = 2.0f * (ox * ow + oy * oz) + cw; + break; + case 2: // 4D "Squarry" + nx = ox * ox - oy * oy - oz * oz - ow * ow + cx; + ny = 2.0f * (ox * oy + oz * ow) + cy; + nz = 2.0f * (ox * oz + oy * ow) + cz; + nw = 2.0f * (ox * ow - oy * oz) + cw; + break; + case 3: // 4D "Mandy Cousin" + nx = ox * ox - oy * oy - oz * oz + ow * ow + cx; + ny = 2.0f * (ox * oy + oz * ow) + cy; + nz = 2.0f * (ox * oz + oy * ow) + cz; + nw = 2.0f * (ox * ow + oy * oz) + cw; + break; + case 4: // 4D "Variation" + nx = ox * ox - oy * oy - oz * oz - ow * ow + cx; + ny = 2.0f * (ox * oy + oz * ow) + cy; + nz = 2.0f * (ox * oz - oy * ow) + cz; + nw = 2.0f * (ox * ow + oy * oz) + cw; + break; + case 5: // 3D "Mandelbrot/Mandelbar" + nx = ox * ox - oy * oy - oz * oz + cx; + ny = 2.0f * ox * oy + cy; + nz = -2.0f * ox * oz + cz; + break; + case 6: // 3D "Christmas Tree" + // Altering the formula here is necessary to avoid division by zero + if (fabs(oz) < 0.000000001f) { + nx = ox * ox - oy * oy - oz * oz + cx; + ny = 2.0f * oy * ox + cy; + nz = 4.0f * oz * ox + cz; + } else { + float a = (2.0f * ox) / (sqrt(oy * oy + oz * oz)); + nx = ox * ox - oy * oy - oz * oz + cx; + ny = a * (oy * oy - oz * oz) + cy; + nz = a * 2.0f * oy * oz + cz; + } + break; + case 7: // 3D "Mandelbulb" + if (fabs(oy) < 0.000000001f) { + nx = ox * ox - oz * oz + cx; + ny = cy; + nz = -2.0f * oz * sqrt(ox * ox) + cz; + } else { + float a = 1.0f - (oz * oz) / (ox * ox + oy * oy); + nx = (ox * ox - oy * oy) * a + cx; + ny = 2.0f * ox * oy * a + cy; + nz = -2.0f * oz * sqrt(ox * ox + oy * oy) + cz; + } + break; + case 8: // 3D "Cosine Mandelbulb" + if (fabs(oy) < 0.000000001f) { + nx = 2.0f * ox * oz + cx; + ny = 4.0f * oy * oz + cy; + nz = oz * oz - ox * ox - oy * oy + cz; + } else { + float a = (2.0f * oz) / sqrt(ox * ox + oy * oy); + nx = (ox * ox - oy * oy) * a + cx; + ny = 2.0f * ox * oy * a + cy; + nz = oz * oz - ox * ox - oy * oy + cz; + } + break; + case 9: // 4D "Mandelbulb" + float rxy = sqrt(ox * ox + oy * oy); + float rxyz = sqrt(ox * ox + oy * oy + oz * oz); + if (fabs(ow) < 0.000000001f && fabs(oz) < 0.000000001f) { + nx = (ox * ox - oy * oy) + cx; + ny = 2.0f * ox * oy + cy; + nz = -2.0f * rxy * oz + cz; + nw = 2.0f * rxyz * ow + cw; + } else { + float a = 1.0f - (ow * ow) / (rxyz * rxyz); + float b = a * (1.0f - (oz * oz) / (rxy * rxy)); + nx = (ox * ox - oy * oy) * b + cx; + ny = 2.0f * ox * oy * b + cy; + nz = -2.0f * rxy * oz * a + cz; + nw = 2.0f * rxyz * ow + cw; + } + break; + } + + if (nx * nx + ny * ny + nz * nz + nw * nw > 4.0f) + return false; + + ox = nx; + oy = ny; + oz = nz; + ow = nw; + } + + return true; +} + + +s16 MapgenFractal::generateTerrain() +{ + MapNode n_air(CONTENT_AIR); + MapNode n_stone(c_stone); + MapNode n_water(c_water_source); + + s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; + u32 index2d = 0; + + noise_seabed->perlinMap2D(node_min.X, node_min.Z); + + for (s16 z = node_min.Z; z <= node_max.Z; z++) { + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { + u32 vi = vm->m_area.index(node_min.X, y, z); + for (s16 x = node_min.X; x <= node_max.X; x++, vi++, index2d++) { + if (vm->m_data[vi].getContent() == CONTENT_IGNORE) { + s16 seabed_height = noise_seabed->result[index2d]; + + if (y <= seabed_height || getFractalAtPoint(x, y, z)) { + vm->m_data[vi] = n_stone; + if (y > stone_surface_max_y) + stone_surface_max_y = y; + } else if (y <= water_level) { + vm->m_data[vi] = n_water; + } else { + vm->m_data[vi] = n_air; + } + } + } + index2d -= ystride; + } + index2d += ystride; + } + + return stone_surface_max_y; +} diff --git a/src/mapgen/mapgen_fractal.h b/src/mapgen/mapgen_fractal.h new file mode 100644 index 000000000..be4941581 --- /dev/null +++ b/src/mapgen/mapgen_fractal.h @@ -0,0 +1,87 @@ +/* +Minetest +Copyright (C) 2015-2017 paramat +Copyright (C) 2015-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +Fractal formulas from http://www.bugman123.com/Hypercomplex/index.html +by Paul Nylander, and from http://www.fractalforums.com, thank you. + +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. +*/ + +#pragma once + +#include "mapgen.h" + +class BiomeManager; + +extern FlagDesc flagdesc_mapgen_fractal[]; + +struct MapgenFractalParams : public MapgenParams +{ + u32 spflags = 0; + float cave_width = 0.09f; + s16 large_cave_depth = -33; + s16 lava_depth = -256; + u16 fractal = 1; + u16 iterations = 11; + v3f scale = v3f(4096.0, 1024.0, 4096.0); + v3f offset = v3f(1.52, 0.0, 0.0); + float slice_w = 0.0f; + float julia_x = 0.267f; + float julia_y = 0.2f; + float julia_z = 0.133f; + float julia_w = 0.067f; + NoiseParams np_seabed; + NoiseParams np_filler_depth; + NoiseParams np_cave1; + NoiseParams np_cave2; + + MapgenFractalParams(); + ~MapgenFractalParams() = default; + + void readParams(const Settings *settings); + void writeParams(Settings *settings) const; +}; + +class MapgenFractal : public MapgenBasic +{ +public: + MapgenFractal(int mapgenid, MapgenFractalParams *params, EmergeManager *emerge); + ~MapgenFractal(); + + virtual MapgenType getType() const { return MAPGEN_FRACTAL; } + + virtual void makeChunk(BlockMakeData *data); + int getSpawnLevelAtPoint(v2s16 p); + bool getFractalAtPoint(s16 x, s16 y, s16 z); + s16 generateTerrain(); + +private: + u16 formula; + bool julia; + + s16 large_cave_depth; + u16 fractal; + u16 iterations; + v3f scale; + v3f offset; + float slice_w; + float julia_x; + float julia_y; + float julia_z; + float julia_w; + Noise *noise_seabed; +}; diff --git a/src/mapgen/mapgen_singlenode.cpp b/src/mapgen/mapgen_singlenode.cpp new file mode 100644 index 000000000..ee8746b83 --- /dev/null +++ b/src/mapgen/mapgen_singlenode.cpp @@ -0,0 +1,102 @@ +/* +Minetest +Copyright (C) 2013-2015 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2015-2017 paramat + +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_singlenode.h" +#include "voxel.h" +#include "mapblock.h" +#include "mapnode.h" +#include "map.h" +#include "nodedef.h" +#include "voxelalgorithms.h" +#include "emerge.h" + + +MapgenSinglenode::MapgenSinglenode(int mapgenid, + MapgenParams *params, EmergeManager *emerge) + : Mapgen(mapgenid, params, emerge) +{ + flags = params->flags; + + INodeDefManager *ndef = emerge->ndef; + + c_node = ndef->getId("mapgen_singlenode"); + if (c_node == CONTENT_IGNORE) + c_node = CONTENT_AIR; + + MapNode n_node(c_node); + set_light = (ndef->get(n_node).sunlight_propagates) ? LIGHT_SUN : 0x00; +} + + +//////////////////////// Map generator + +void MapgenSinglenode::makeChunk(BlockMakeData *data) +{ + // Pre-conditions + assert(data->vmanip); + assert(data->nodedef); + assert(data->blockpos_requested.X >= data->blockpos_min.X && + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); + assert(data->blockpos_requested.X <= data->blockpos_max.X && + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); + + this->generating = true; + this->vm = data->vmanip; + this->ndef = data->nodedef; + + v3s16 blockpos_min = data->blockpos_min; + v3s16 blockpos_max = data->blockpos_max; + + // Area of central chunk + v3s16 node_min = blockpos_min * MAP_BLOCKSIZE; + v3s16 node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1); + + blockseed = getBlockSeed2(node_min, data->seed); + + MapNode n_node(c_node); + + for (s16 z = node_min.Z; z <= node_max.Z; z++) + for (s16 y = node_min.Y; y <= node_max.Y; y++) { + u32 i = vm->m_area.index(node_min.X, y, z); + for (s16 x = node_min.X; x <= node_max.X; x++) { + if (vm->m_data[i].getContent() == CONTENT_IGNORE) + vm->m_data[i] = n_node; + i++; + } + } + + // Add top and bottom side of water to transforming_liquid queue + updateLiquid(&data->transforming_liquid, node_min, node_max); + + // Set lighting + if ((flags & MG_LIGHT) && set_light == LIGHT_SUN) + setLighting(LIGHT_SUN, node_min, node_max); + + this->generating = false; +} + + +int MapgenSinglenode::getSpawnLevelAtPoint(v2s16 p) +{ + return 0; +} diff --git a/src/mapgen/mapgen_singlenode.h b/src/mapgen/mapgen_singlenode.h new file mode 100644 index 000000000..7678613e5 --- /dev/null +++ b/src/mapgen/mapgen_singlenode.h @@ -0,0 +1,49 @@ +/* +Minetest +Copyright (C) 2013-2015 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2015-2017 paramat + +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. +*/ + +#pragma once + +#include "mapgen.h" + +struct MapgenSinglenodeParams : public MapgenParams +{ + MapgenSinglenodeParams() = default; + ~MapgenSinglenodeParams() = default; + + void readParams(const Settings *settings) {} + void writeParams(Settings *settings) const {} +}; + +class MapgenSinglenode : public Mapgen +{ +public: + u32 flags; + content_t c_node; + u8 set_light; + + MapgenSinglenode(int mapgenid, MapgenParams *params, EmergeManager *emerge); + ~MapgenSinglenode() = default; + + virtual MapgenType getType() const { return MAPGEN_SINGLENODE; } + + void makeChunk(BlockMakeData *data); + int getSpawnLevelAtPoint(v2s16 p); +}; diff --git a/src/mapgen/mapgen_v5.cpp b/src/mapgen/mapgen_v5.cpp new file mode 100644 index 000000000..5f5487288 --- /dev/null +++ b/src/mapgen/mapgen_v5.cpp @@ -0,0 +1,295 @@ +/* +Minetest +Copyright (C) 2014-2017 paramat +Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +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 "voxel.h" +#include "noise.h" +#include "mapblock.h" +#include "mapnode.h" +#include "map.h" +#include "content_sao.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_v5.h" + + +FlagDesc flagdesc_mapgen_v5[] = { + {"caverns", MGV5_CAVERNS}, + {NULL, 0} +}; + + +MapgenV5::MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge) + : MapgenBasic(mapgenid, params, emerge) +{ + spflags = params->spflags; + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; + lava_depth = params->lava_depth; + cavern_limit = params->cavern_limit; + cavern_taper = params->cavern_taper; + cavern_threshold = params->cavern_threshold; + + // Terrain noise + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + noise_factor = new Noise(¶ms->np_factor, seed, csize.X, csize.Z); + noise_height = new Noise(¶ms->np_height, seed, csize.X, csize.Z); + + // 3D terrain noise + // 1-up 1-down overgeneration + noise_ground = new Noise(¶ms->np_ground, seed, csize.X, csize.Y + 2, csize.Z); + // 1 down overgeneration + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; + MapgenBasic::np_cavern = params->np_cavern; +} + + +MapgenV5::~MapgenV5() +{ + delete noise_filler_depth; + delete noise_factor; + delete noise_height; + delete noise_ground; +} + + +MapgenV5Params::MapgenV5Params(): + np_filler_depth (0, 1, v3f(150, 150, 150), 261, 4, 0.7, 2.0), + np_factor (0, 1, v3f(250, 250, 250), 920381, 3, 0.45, 2.0), + np_height (0, 10, v3f(250, 250, 250), 84174, 4, 0.5, 2.0), + np_ground (0, 40, v3f(80, 80, 80), 983240, 4, 0.55, 2.0, NOISE_FLAG_EASED), + np_cave1 (0, 12, v3f(50, 50, 50), 52534, 4, 0.5, 2.0), + np_cave2 (0, 12, v3f(50, 50, 50), 10325, 4, 0.5, 2.0), + np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0) +{ +} + + +void MapgenV5Params::readParams(const Settings *settings) +{ + settings->getFlagStrNoEx("mgv5_spflags", spflags, flagdesc_mapgen_v5); + settings->getFloatNoEx("mgv5_cave_width", cave_width); + settings->getS16NoEx("mgv5_large_cave_depth", large_cave_depth); + settings->getS16NoEx("mgv5_lava_depth", lava_depth); + settings->getS16NoEx("mgv5_cavern_limit", cavern_limit); + settings->getS16NoEx("mgv5_cavern_taper", cavern_taper); + settings->getFloatNoEx("mgv5_cavern_threshold", cavern_threshold); + + settings->getNoiseParams("mgv5_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgv5_np_factor", np_factor); + settings->getNoiseParams("mgv5_np_height", np_height); + settings->getNoiseParams("mgv5_np_ground", np_ground); + settings->getNoiseParams("mgv5_np_cave1", np_cave1); + settings->getNoiseParams("mgv5_np_cave2", np_cave2); + settings->getNoiseParams("mgv5_np_cavern", np_cavern); +} + + +void MapgenV5Params::writeParams(Settings *settings) const +{ + settings->setFlagStr("mgv5_spflags", spflags, flagdesc_mapgen_v5, U32_MAX); + settings->setFloat("mgv5_cave_width", cave_width); + settings->setS16("mgv5_large_cave_depth", large_cave_depth); + settings->setS16("mgv5_lava_depth", lava_depth); + settings->setS16("mgv5_cavern_limit", cavern_limit); + settings->setS16("mgv5_cavern_taper", cavern_taper); + settings->setFloat("mgv5_cavern_threshold", cavern_threshold); + + settings->setNoiseParams("mgv5_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgv5_np_factor", np_factor); + settings->setNoiseParams("mgv5_np_height", np_height); + settings->setNoiseParams("mgv5_np_ground", np_ground); + settings->setNoiseParams("mgv5_np_cave1", np_cave1); + settings->setNoiseParams("mgv5_np_cave2", np_cave2); + settings->setNoiseParams("mgv5_np_cavern", np_cavern); +} + + +int MapgenV5::getSpawnLevelAtPoint(v2s16 p) +{ + + float f = 0.55 + NoisePerlin2D(&noise_factor->np, p.X, p.Y, seed); + if (f < 0.01) + f = 0.01; + else if (f >= 1.0) + f *= 1.6; + float h = NoisePerlin2D(&noise_height->np, p.X, p.Y, seed); + + // noise_height 'offset' is the average level of terrain. At least 50% of + // terrain will be below this. + // Raising the maximum spawn level above 'water_level + 16' is necessary + // for when noise_height 'offset' is set much higher than water_level. + s16 max_spawn_y = MYMAX(noise_height->np.offset, water_level + 16); + + // Starting spawn search at max_spawn_y + 128 ensures 128 nodes of open + // space above spawn position. Avoids spawning in possibly sealed voids. + for (s16 y = max_spawn_y + 128; y >= water_level; y--) { + float n_ground = NoisePerlin3D(&noise_ground->np, p.X, y, p.Y, seed); + + if (n_ground * f > y - h) { // If solid + if (y < water_level || y > max_spawn_y) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + + // y + 2 because y is surface and due to biome 'dust' nodes. + return y + 2; + } + } + // Unsuitable spawn position, no ground found + return MAX_MAP_GENERATION_LIMIT; +} + + +void MapgenV5::makeChunk(BlockMakeData *data) +{ + // Pre-conditions + assert(data->vmanip); + assert(data->nodedef); + assert(data->blockpos_requested.X >= data->blockpos_min.X && + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); + assert(data->blockpos_requested.X <= data->blockpos_max.X && + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); + + this->generating = true; + this->vm = data->vmanip; + this->ndef = data->nodedef; + //TimeTaker t("makeChunk"); + + 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); + + // Create a block-specific seed + blockseed = getBlockSeed2(full_node_min, seed); + + // Generate base terrain + s16 stone_surface_max_y = generateBaseTerrain(); + + // Create heightmap + updateHeightmap(node_min, node_max); + + // Init biome generator, place biome-specific nodes, and build biomemap + biomegen->calcBiomeNoise(node_min); + + MgStoneType mgstone_type; + content_t biome_stone; + generateBiomes(&mgstone_type, &biome_stone); + + // Generate caverns, tunnels and classic caves + if (flags & MG_CAVES) { + bool near_cavern = false; + // Generate caverns + if (spflags & MGV5_CAVERNS) + near_cavern = generateCaverns(stone_surface_max_y); + // Generate tunnels and classic caves + if (near_cavern) + // Disable classic caves in this mapchunk by setting + // 'large cave depth' to world base. Avoids excessive liquid in + // large caverns and floating blobs of overgenerated liquid. + generateCaves(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT); + else + generateCaves(stone_surface_max_y, large_cave_depth); + } + + // Generate dungeons and desert temples + if (flags & MG_DUNGEONS) + generateDungeons(stone_surface_max_y, mgstone_type, biome_stone); + + // Generate the registered decorations + if (flags & MG_DECORATIONS) + m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max); + + // Generate the registered ores + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + + // Sprinkle some dust on top after everything else was generated + dustTopNodes(); + + //printf("makeChunk: %dms\n", t.stop()); + + // Add top and bottom side of water to transforming_liquid queue + 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); + } + + this->generating = false; +} + + +int MapgenV5::generateBaseTerrain() +{ + u32 index = 0; + u32 index2d = 0; + int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; + + noise_factor->perlinMap2D(node_min.X, node_min.Z); + noise_height->perlinMap2D(node_min.X, node_min.Z); + noise_ground->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + + for (s16 z=node_min.Z; z<=node_max.Z; z++) { + for (s16 y=node_min.Y - 1; y<=node_max.Y + 1; y++) { + u32 vi = vm->m_area.index(node_min.X, y, z); + for (s16 x=node_min.X; x<=node_max.X; x++, vi++, index++, index2d++) { + if (vm->m_data[vi].getContent() != CONTENT_IGNORE) + continue; + + float f = 0.55 + noise_factor->result[index2d]; + if (f < 0.01) + f = 0.01; + else if (f >= 1.0) + f *= 1.6; + float h = noise_height->result[index2d]; + + if (noise_ground->result[index] * f < y - h) { + if (y <= water_level) + vm->m_data[vi] = MapNode(c_water_source); + else + vm->m_data[vi] = MapNode(CONTENT_AIR); + } else { + vm->m_data[vi] = MapNode(c_stone); + if (y > stone_surface_max_y) + stone_surface_max_y = y; + } + } + index2d -= ystride; + } + index2d += ystride; + } + + return stone_surface_max_y; +} diff --git a/src/mapgen/mapgen_v5.h b/src/mapgen/mapgen_v5.h new file mode 100644 index 000000000..44b0a09e7 --- /dev/null +++ b/src/mapgen/mapgen_v5.h @@ -0,0 +1,74 @@ +/* +Minetest +Copyright (C) 2014-2017 paramat +Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> + +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. +*/ + +#pragma once + +#include "mapgen.h" + +///////// Mapgen V5 flags +#define MGV5_CAVERNS 0x01 + +class BiomeManager; + +extern FlagDesc flagdesc_mapgen_v5[]; + +struct MapgenV5Params : public MapgenParams +{ + u32 spflags = MGV5_CAVERNS; + float cave_width = 0.125f; + s16 large_cave_depth = -256; + s16 lava_depth = -256; + s16 cavern_limit = -256; + s16 cavern_taper = 256; + float cavern_threshold = 0.7f; + + NoiseParams np_filler_depth; + NoiseParams np_factor; + NoiseParams np_height; + NoiseParams np_ground; + NoiseParams np_cave1; + NoiseParams np_cave2; + NoiseParams np_cavern; + + MapgenV5Params(); + ~MapgenV5Params() = default; + + void readParams(const Settings *settings); + void writeParams(Settings *settings) const; +}; + +class MapgenV5 : public MapgenBasic +{ +public: + MapgenV5(int mapgenid, MapgenV5Params *params, EmergeManager *emerge); + ~MapgenV5(); + + virtual MapgenType getType() const { return MAPGEN_V5; } + + virtual void makeChunk(BlockMakeData *data); + int getSpawnLevelAtPoint(v2s16 p); + int generateBaseTerrain(); + +private: + s16 large_cave_depth; + Noise *noise_factor; + Noise *noise_height; + Noise *noise_ground; +}; diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp new file mode 100644 index 000000000..6dcf834d2 --- /dev/null +++ b/src/mapgen/mapgen_v6.cpp @@ -0,0 +1,1123 @@ +/* +Minetest +Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2014-2017 paramat + +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 "voxel.h" +#include "noise.h" +#include "mapblock.h" +#include "mapnode.h" +#include "map.h" +//#include "serverobject.h" +#include "content_sao.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 "treegen.h" +#include "mg_ore.h" +#include "mg_decoration.h" +#include "mapgen_v6.h" + + +FlagDesc flagdesc_mapgen_v6[] = { + {"jungles", MGV6_JUNGLES}, + {"biomeblend", MGV6_BIOMEBLEND}, + {"mudflow", MGV6_MUDFLOW}, + {"snowbiomes", MGV6_SNOWBIOMES}, + {"flat", MGV6_FLAT}, + {"trees", MGV6_TREES}, + {NULL, 0} +}; + + +///////////////////////////////////////////////////////////////////////////// + + +MapgenV6::MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge) + : Mapgen(mapgenid, params, emerge) +{ + m_emerge = emerge; + ystride = csize.X; //////fix this + + heightmap = new s16[csize.X * csize.Z]; + + spflags = params->spflags; + freq_desert = params->freq_desert; + freq_beach = params->freq_beach; + + np_cave = ¶ms->np_cave; + np_humidity = ¶ms->np_humidity; + np_trees = ¶ms->np_trees; + np_apple_trees = ¶ms->np_apple_trees; + + //// Create noise objects + noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Y); + noise_terrain_higher = new Noise(¶ms->np_terrain_higher, seed, csize.X, csize.Y); + noise_steepness = new Noise(¶ms->np_steepness, seed, csize.X, csize.Y); + noise_height_select = new Noise(¶ms->np_height_select, seed, csize.X, csize.Y); + noise_mud = new Noise(¶ms->np_mud, seed, csize.X, csize.Y); + noise_beach = new Noise(¶ms->np_beach, seed, csize.X, csize.Y); + noise_biome = new Noise(¶ms->np_biome, seed, + csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE); + noise_humidity = new Noise(¶ms->np_humidity, seed, + csize.X + 2 * MAP_BLOCKSIZE, csize.Y + 2 * MAP_BLOCKSIZE); + + //// Resolve nodes to be used + INodeDefManager *ndef = emerge->ndef; + + c_stone = ndef->getId("mapgen_stone"); + c_dirt = ndef->getId("mapgen_dirt"); + c_dirt_with_grass = ndef->getId("mapgen_dirt_with_grass"); + c_sand = ndef->getId("mapgen_sand"); + c_water_source = ndef->getId("mapgen_water_source"); + c_lava_source = ndef->getId("mapgen_lava_source"); + c_gravel = ndef->getId("mapgen_gravel"); + c_desert_stone = ndef->getId("mapgen_desert_stone"); + c_desert_sand = ndef->getId("mapgen_desert_sand"); + c_dirt_with_snow = ndef->getId("mapgen_dirt_with_snow"); + c_snow = ndef->getId("mapgen_snow"); + c_snowblock = ndef->getId("mapgen_snowblock"); + c_ice = ndef->getId("mapgen_ice"); + + if (c_gravel == CONTENT_IGNORE) + c_gravel = c_stone; + if (c_desert_stone == CONTENT_IGNORE) + c_desert_stone = c_stone; + if (c_desert_sand == CONTENT_IGNORE) + c_desert_sand = c_sand; + if (c_dirt_with_snow == CONTENT_IGNORE) + c_dirt_with_snow = c_dirt_with_grass; + if (c_snow == CONTENT_IGNORE) + c_snow = CONTENT_AIR; + if (c_snowblock == CONTENT_IGNORE) + c_snowblock = c_dirt_with_grass; + if (c_ice == CONTENT_IGNORE) + c_ice = c_water_source; + + c_cobble = ndef->getId("mapgen_cobble"); + c_mossycobble = ndef->getId("mapgen_mossycobble"); + c_stair_cobble = ndef->getId("mapgen_stair_cobble"); + c_stair_desert_stone = ndef->getId("mapgen_stair_desert_stone"); + + if (c_mossycobble == CONTENT_IGNORE) + c_mossycobble = c_cobble; + if (c_stair_cobble == CONTENT_IGNORE) + c_stair_cobble = c_cobble; + if (c_stair_desert_stone == CONTENT_IGNORE) + c_stair_desert_stone = c_desert_stone; +} + + +MapgenV6::~MapgenV6() +{ + delete noise_terrain_base; + delete noise_terrain_higher; + delete noise_steepness; + delete noise_height_select; + delete noise_mud; + delete noise_beach; + delete noise_biome; + delete noise_humidity; + + delete[] heightmap; +} + + +MapgenV6Params::MapgenV6Params(): + np_terrain_base (-4, 20.0, v3f(250.0, 250.0, 250.0), 82341, 5, 0.6, 2.0), + np_terrain_higher (20, 16.0, v3f(500.0, 500.0, 500.0), 85039, 5, 0.6, 2.0), + np_steepness (0.85, 0.5, v3f(125.0, 125.0, 125.0), -932, 5, 0.7, 2.0), + np_height_select (0, 1.0, v3f(250.0, 250.0, 250.0), 4213, 5, 0.69, 2.0), + np_mud (4, 2.0, v3f(200.0, 200.0, 200.0), 91013, 3, 0.55, 2.0), + np_beach (0, 1.0, v3f(250.0, 250.0, 250.0), 59420, 3, 0.50, 2.0), + np_biome (0, 1.0, v3f(500.0, 500.0, 500.0), 9130, 3, 0.50, 2.0), + np_cave (6, 6.0, v3f(250.0, 250.0, 250.0), 34329, 3, 0.50, 2.0), + np_humidity (0.5, 0.5, v3f(500.0, 500.0, 500.0), 72384, 3, 0.50, 2.0), + np_trees (0, 1.0, v3f(125.0, 125.0, 125.0), 2, 4, 0.66, 2.0), + np_apple_trees (0, 1.0, v3f(100.0, 100.0, 100.0), 342902, 3, 0.45, 2.0) +{ +} + + +void MapgenV6Params::readParams(const Settings *settings) +{ + settings->getFlagStrNoEx("mgv6_spflags", spflags, flagdesc_mapgen_v6); + settings->getFloatNoEx("mgv6_freq_desert", freq_desert); + settings->getFloatNoEx("mgv6_freq_beach", freq_beach); + + settings->getNoiseParams("mgv6_np_terrain_base", np_terrain_base); + settings->getNoiseParams("mgv6_np_terrain_higher", np_terrain_higher); + settings->getNoiseParams("mgv6_np_steepness", np_steepness); + settings->getNoiseParams("mgv6_np_height_select", np_height_select); + settings->getNoiseParams("mgv6_np_mud", np_mud); + settings->getNoiseParams("mgv6_np_beach", np_beach); + settings->getNoiseParams("mgv6_np_biome", np_biome); + settings->getNoiseParams("mgv6_np_cave", np_cave); + settings->getNoiseParams("mgv6_np_humidity", np_humidity); + settings->getNoiseParams("mgv6_np_trees", np_trees); + settings->getNoiseParams("mgv6_np_apple_trees", np_apple_trees); +} + + +void MapgenV6Params::writeParams(Settings *settings) const +{ + settings->setFlagStr("mgv6_spflags", spflags, flagdesc_mapgen_v6, U32_MAX); + settings->setFloat("mgv6_freq_desert", freq_desert); + settings->setFloat("mgv6_freq_beach", freq_beach); + + settings->setNoiseParams("mgv6_np_terrain_base", np_terrain_base); + settings->setNoiseParams("mgv6_np_terrain_higher", np_terrain_higher); + settings->setNoiseParams("mgv6_np_steepness", np_steepness); + settings->setNoiseParams("mgv6_np_height_select", np_height_select); + settings->setNoiseParams("mgv6_np_mud", np_mud); + settings->setNoiseParams("mgv6_np_beach", np_beach); + settings->setNoiseParams("mgv6_np_biome", np_biome); + settings->setNoiseParams("mgv6_np_cave", np_cave); + settings->setNoiseParams("mgv6_np_humidity", np_humidity); + settings->setNoiseParams("mgv6_np_trees", np_trees); + settings->setNoiseParams("mgv6_np_apple_trees", np_apple_trees); +} + + +//////////////////////// Some helper functions for the map generator + +// Returns Y one under area minimum if not found +s16 MapgenV6::find_stone_level(v2s16 p2d) +{ + const v3s16 &em = vm->m_area.getExtent(); + s16 y_nodes_max = vm->m_area.MaxEdge.Y; + s16 y_nodes_min = vm->m_area.MinEdge.Y; + u32 i = vm->m_area.index(p2d.X, y_nodes_max, p2d.Y); + s16 y; + + for (y = y_nodes_max; y >= y_nodes_min; y--) { + content_t c = vm->m_data[i].getContent(); + if (c != CONTENT_IGNORE && (c == c_stone || c == c_desert_stone)) + break; + + vm->m_area.add_y(em, i, -1); + } + return (y >= y_nodes_min) ? y : y_nodes_min - 1; +} + + +// Required by mapgen.h +bool MapgenV6::block_is_underground(u64 seed, v3s16 blockpos) +{ + /*s16 minimum_groundlevel = (s16)get_sector_minimum_ground_level( + seed, v2s16(blockpos.X, blockpos.Z));*/ + // Nah, this is just a heuristic, just return something + s16 minimum_groundlevel = water_level; + + if(blockpos.Y * MAP_BLOCKSIZE + MAP_BLOCKSIZE <= minimum_groundlevel) + return true; + + return false; +} + + +//////////////////////// Base terrain height functions + +float MapgenV6::baseTerrainLevel(float terrain_base, float terrain_higher, + float steepness, float height_select) +{ + float base = 1 + terrain_base; + float higher = 1 + terrain_higher; + + // Limit higher ground level to at least base + if(higher < base) + higher = base; + + // Steepness factor of cliffs + float b = steepness; + b = rangelim(b, 0.0, 1000.0); + b = 5 * b * b * b * b * b * b * b; + b = rangelim(b, 0.5, 1000.0); + + // Values 1.5...100 give quite horrible looking slopes + if (b > 1.5 && b < 100.0) + b = (b < 10.0) ? 1.5 : 100.0; + + float a_off = -0.20; // Offset to more low + float a = 0.5 + b * (a_off + height_select); + a = rangelim(a, 0.0, 1.0); // Limit + + return base * (1.0 - a) + higher * a; +} + + +float MapgenV6::baseTerrainLevelFromNoise(v2s16 p) +{ + if (spflags & MGV6_FLAT) + return water_level; + + float terrain_base = NoisePerlin2D_PO(&noise_terrain_base->np, + p.X, 0.5, p.Y, 0.5, seed); + float terrain_higher = NoisePerlin2D_PO(&noise_terrain_higher->np, + p.X, 0.5, p.Y, 0.5, seed); + float steepness = NoisePerlin2D_PO(&noise_steepness->np, + p.X, 0.5, p.Y, 0.5, seed); + float height_select = NoisePerlin2D_PO(&noise_height_select->np, + p.X, 0.5, p.Y, 0.5, seed); + + return baseTerrainLevel(terrain_base, terrain_higher, + steepness, height_select); +} + + +float MapgenV6::baseTerrainLevelFromMap(v2s16 p) +{ + int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); + return baseTerrainLevelFromMap(index); +} + + +float MapgenV6::baseTerrainLevelFromMap(int index) +{ + if (spflags & MGV6_FLAT) + return water_level; + + float terrain_base = noise_terrain_base->result[index]; + float terrain_higher = noise_terrain_higher->result[index]; + float steepness = noise_steepness->result[index]; + float height_select = noise_height_select->result[index]; + + return baseTerrainLevel(terrain_base, terrain_higher, + steepness, height_select); +} + + +s16 MapgenV6::find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision) +{ + return baseTerrainLevelFromNoise(p2d) + MGV6_AVERAGE_MUD_AMOUNT; +} + + +int MapgenV6::getGroundLevelAtPoint(v2s16 p) +{ + return baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT; +} + + +int MapgenV6::getSpawnLevelAtPoint(v2s16 p) +{ + s16 level_at_point = baseTerrainLevelFromNoise(p) + MGV6_AVERAGE_MUD_AMOUNT; + if (level_at_point <= water_level || + level_at_point > water_level + 16) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + + return level_at_point; +} + + +//////////////////////// Noise functions + +float MapgenV6::getMudAmount(v2s16 p) +{ + int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); + return getMudAmount(index); +} + + +bool MapgenV6::getHaveBeach(v2s16 p) +{ + int index = (p.Y - node_min.Z) * ystride + (p.X - node_min.X); + return getHaveBeach(index); +} + + +BiomeV6Type MapgenV6::getBiome(v2s16 p) +{ + int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE) + + (p.X - full_node_min.X); + return getBiome(index, p); +} + + +float MapgenV6::getHumidity(v2s16 p) +{ + /*double noise = noise2d_perlin( + 0.5+(float)p.X/500, 0.5+(float)p.Y/500, + seed+72384, 4, 0.66); + noise = (noise + 1.0)/2.0;*/ + + int index = (p.Y - full_node_min.Z) * (ystride + 2 * MAP_BLOCKSIZE) + + (p.X - full_node_min.X); + float noise = noise_humidity->result[index]; + + if (noise < 0.0) + noise = 0.0; + if (noise > 1.0) + noise = 1.0; + return noise; +} + + +float MapgenV6::getTreeAmount(v2s16 p) +{ + /*double noise = noise2d_perlin( + 0.5+(float)p.X/125, 0.5+(float)p.Y/125, + seed+2, 4, 0.66);*/ + + float noise = NoisePerlin2D(np_trees, p.X, p.Y, seed); + float zeroval = -0.39; + if (noise < zeroval) + return 0; + + return 0.04 * (noise - zeroval) / (1.0 - zeroval); +} + + +bool MapgenV6::getHaveAppleTree(v2s16 p) +{ + /*is_apple_tree = noise2d_perlin( + 0.5+(float)p.X/100, 0.5+(float)p.Z/100, + data->seed+342902, 3, 0.45) > 0.2;*/ + + float noise = NoisePerlin2D(np_apple_trees, p.X, p.Y, seed); + + return noise > 0.2; +} + + +float MapgenV6::getMudAmount(int index) +{ + if (spflags & MGV6_FLAT) + return MGV6_AVERAGE_MUD_AMOUNT; + + /*return ((float)AVERAGE_MUD_AMOUNT + 2.0 * noise2d_perlin( + 0.5+(float)p.X/200, 0.5+(float)p.Y/200, + seed+91013, 3, 0.55));*/ + + return noise_mud->result[index]; +} + + +bool MapgenV6::getHaveBeach(int index) +{ + // Determine whether to have sand here + /*double sandnoise = noise2d_perlin( + 0.2+(float)p2d.X/250, 0.7+(float)p2d.Y/250, + seed+59420, 3, 0.50);*/ + + float sandnoise = noise_beach->result[index]; + return (sandnoise > freq_beach); +} + + +BiomeV6Type MapgenV6::getBiome(int index, v2s16 p) +{ + // Just do something very simple as for now + /*double d = noise2d_perlin( + 0.6+(float)p2d.X/250, 0.2+(float)p2d.Y/250, + seed+9130, 3, 0.50);*/ + + float d = noise_biome->result[index]; + float h = noise_humidity->result[index]; + + if (spflags & MGV6_SNOWBIOMES) { + float blend = (spflags & MGV6_BIOMEBLEND) ? noise2d(p.X, p.Y, seed) / 40 : 0; + + if (d > MGV6_FREQ_HOT + blend) { + if (h > MGV6_FREQ_JUNGLE + blend) + return BT_JUNGLE; + + return BT_DESERT; + } + + if (d < MGV6_FREQ_SNOW + blend) { + if (h > MGV6_FREQ_TAIGA + blend) + return BT_TAIGA; + + return BT_TUNDRA; + } + + return BT_NORMAL; + } + + if (d > freq_desert) + return BT_DESERT; + + if ((spflags & MGV6_BIOMEBLEND) && (d > freq_desert - 0.10) && + ((noise2d(p.X, p.Y, seed) + 1.0) > (freq_desert - d) * 20.0)) + return BT_DESERT; + + if ((spflags & MGV6_JUNGLES) && h > 0.75) + return BT_JUNGLE; + + return BT_NORMAL; + +} + + +u32 MapgenV6::get_blockseed(u64 seed, v3s16 p) +{ + s32 x = p.X, y = p.Y, z = p.Z; + return (u32)(seed % 0x100000000ULL) + z * 38134234 + y * 42123 + x * 23; +} + + +//////////////////////// Map generator + +void MapgenV6::makeChunk(BlockMakeData *data) +{ + // Pre-conditions + assert(data->vmanip); + assert(data->nodedef); + assert(data->blockpos_requested.X >= data->blockpos_min.X && + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); + assert(data->blockpos_requested.X <= data->blockpos_max.X && + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); + + this->generating = true; + this->vm = data->vmanip; + this->ndef = data->nodedef; + + // Hack: use minimum block coords for old code that assumes a single block + v3s16 blockpos_min = data->blockpos_min; + v3s16 blockpos_max = data->blockpos_max; + + // Area of central chunk + node_min = blockpos_min * MAP_BLOCKSIZE; + node_max = (blockpos_max + v3s16(1, 1, 1)) * MAP_BLOCKSIZE - v3s16(1, 1, 1); + + // Full allocated area + full_node_min = (blockpos_min - 1) * MAP_BLOCKSIZE; + full_node_max = (blockpos_max + 2) * MAP_BLOCKSIZE - v3s16(1, 1, 1); + + central_area_size = node_max - node_min + v3s16(1, 1, 1); + assert(central_area_size.X == central_area_size.Z); + + // Create a block-specific seed + blockseed = get_blockseed(data->seed, full_node_min); + + // Make some noise + calculateNoise(); + + // Maximum height of the stone surface and obstacles. + // This is used to guide the cave generation + s16 stone_surface_max_y; + + // Generate general ground level to full area + stone_surface_max_y = generateGround(); + + // Create initial heightmap to limit caves + updateHeightmap(node_min, node_max); + + const s16 max_spread_amount = MAP_BLOCKSIZE; + // Limit dirt flow area by 1 because mud is flown into neighbors. + s16 mudflow_minpos = -max_spread_amount + 1; + s16 mudflow_maxpos = central_area_size.X + max_spread_amount - 2; + + // Loop this part, it will make stuff look older and newer nicely + const u32 age_loops = 2; + for (u32 i_age = 0; i_age < age_loops; i_age++) { // Aging loop + // Make caves (this code is relatively horrible) + if (flags & MG_CAVES) + generateCaves(stone_surface_max_y); + + // Add mud to the central chunk + addMud(); + + // Flow mud away from steep edges + if (spflags & MGV6_MUDFLOW) + flowMud(mudflow_minpos, mudflow_maxpos); + + } + + // Update heightmap after mudflow + updateHeightmap(node_min, node_max); + + // Add dungeons + if ((flags & MG_DUNGEONS) && (stone_surface_max_y >= node_min.Y)) { + DungeonParams dp; + + dp.seed = seed; + dp.c_water = c_water_source; + dp.c_river_water = c_water_source; + + dp.only_in_ground = true; + dp.corridor_len_min = 1; + dp.corridor_len_max = 13; + dp.rooms_min = 2; + dp.rooms_max = 16; + dp.y_min = -MAX_MAP_GENERATION_LIMIT; + dp.y_max = MAX_MAP_GENERATION_LIMIT; + + dp.np_density + = NoiseParams(0.9, 0.5, v3f(500.0, 500.0, 500.0), 0, 2, 0.8, 2.0); + dp.np_alt_wall + = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); + + if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) { + dp.c_wall = c_desert_stone; + dp.c_alt_wall = CONTENT_IGNORE; + dp.c_stair = c_stair_desert_stone; + + dp.diagonal_dirs = true; + dp.holesize = v3s16(2, 3, 2); + dp.room_size_min = v3s16(6, 9, 6); + dp.room_size_max = v3s16(10, 11, 10); + dp.room_size_large_min = v3s16(10, 13, 10); + dp.room_size_large_max = v3s16(18, 21, 18); + dp.notifytype = GENNOTIFY_TEMPLE; + } else { + dp.c_wall = c_cobble; + dp.c_alt_wall = c_mossycobble; + dp.c_stair = c_stair_cobble; + + dp.diagonal_dirs = false; + dp.holesize = v3s16(1, 2, 1); + dp.room_size_min = v3s16(4, 4, 4); + dp.room_size_max = v3s16(8, 6, 8); + dp.room_size_large_min = v3s16(8, 8, 8); + dp.room_size_large_max = v3s16(16, 16, 16); + dp.notifytype = GENNOTIFY_DUNGEON; + } + + DungeonGen dgen(ndef, &gennotify, &dp); + dgen.generate(vm, blockseed, full_node_min, full_node_max); + } + + // Add top and bottom side of water to transforming_liquid queue + updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); + + // Add surface nodes + growGrass(); + + // Generate some trees, and add grass, if a jungle + if (spflags & MGV6_TREES) + placeTreesAndJungleGrass(); + + // Generate the registered decorations + if (flags & MG_DECORATIONS) + m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max); + + // Generate the registered ores + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + + // Calculate lighting + if (flags & MG_LIGHT) + calcLighting(node_min - v3s16(1, 1, 1) * MAP_BLOCKSIZE, + node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, + full_node_min, full_node_max); + + this->generating = false; +} + + +void MapgenV6::calculateNoise() +{ + int x = node_min.X; + int z = node_min.Z; + int fx = full_node_min.X; + int fz = full_node_min.Z; + + if (!(spflags & MGV6_FLAT)) { + noise_terrain_base->perlinMap2D_PO(x, 0.5, z, 0.5); + noise_terrain_higher->perlinMap2D_PO(x, 0.5, z, 0.5); + noise_steepness->perlinMap2D_PO(x, 0.5, z, 0.5); + noise_height_select->perlinMap2D_PO(x, 0.5, z, 0.5); + noise_mud->perlinMap2D_PO(x, 0.5, z, 0.5); + } + + noise_beach->perlinMap2D_PO(x, 0.2, z, 0.7); + + noise_biome->perlinMap2D_PO(fx, 0.6, fz, 0.2); + noise_humidity->perlinMap2D_PO(fx, 0.0, fz, 0.0); + // Humidity map does not need range limiting 0 to 1, + // only humidity at point does +} + + +int MapgenV6::generateGround() +{ + //TimeTaker timer1("Generating ground level"); + MapNode n_air(CONTENT_AIR), n_water_source(c_water_source); + MapNode n_stone(c_stone), n_desert_stone(c_desert_stone); + MapNode n_ice(c_ice); + int stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; + + 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++) { + // Surface height + s16 surface_y = (s16)baseTerrainLevelFromMap(index); + + // Log it + if (surface_y > stone_surface_max_y) + stone_surface_max_y = surface_y; + + BiomeV6Type bt = getBiome(v2s16(x, z)); + + // Fill ground with stone + const v3s16 &em = vm->m_area.getExtent(); + u32 i = vm->m_area.index(x, node_min.Y, z); + for (s16 y = node_min.Y; y <= node_max.Y; y++) { + if (vm->m_data[i].getContent() == CONTENT_IGNORE) { + if (y <= surface_y) { + vm->m_data[i] = (y >= MGV6_DESERT_STONE_BASE + && bt == BT_DESERT) ? + n_desert_stone : n_stone; + } else if (y <= water_level) { + vm->m_data[i] = (y >= MGV6_ICE_BASE + && bt == BT_TUNDRA) ? + n_ice : n_water_source; + } else { + vm->m_data[i] = n_air; + } + } + vm->m_area.add_y(em, i, 1); + } + } + + return stone_surface_max_y; +} + + +void MapgenV6::addMud() +{ + // 15ms @cs=8 + //TimeTaker timer1("add mud"); + MapNode n_dirt(c_dirt), n_gravel(c_gravel); + MapNode n_sand(c_sand), n_desert_sand(c_desert_sand); + MapNode addnode; + + 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++) { + // Randomize mud amount + s16 mud_add_amount = getMudAmount(index) / 2.0 + 0.5; + + // Find ground level + s16 surface_y = find_stone_level(v2s16(x, z)); /////////////////optimize this! + + // Handle area not found + if (surface_y == vm->m_area.MinEdge.Y - 1) + continue; + + BiomeV6Type bt = getBiome(v2s16(x, z)); + addnode = (bt == BT_DESERT) ? n_desert_sand : n_dirt; + + if (bt == BT_DESERT && surface_y + mud_add_amount <= water_level + 1) { + addnode = n_sand; + } else if (mud_add_amount <= 0) { + mud_add_amount = 1 - mud_add_amount; + addnode = n_gravel; + } else if (bt != BT_DESERT && getHaveBeach(index) && + surface_y + mud_add_amount <= water_level + 2) { + addnode = n_sand; + } + + if ((bt == BT_DESERT || bt == BT_TUNDRA) && surface_y > 20) + mud_add_amount = MYMAX(0, mud_add_amount - (surface_y - 20) / 5); + + /* If topmost node is grass, change it to mud. It might be if it was + // flown to there from a neighboring chunk and then converted. + u32 i = vm->m_area.index(x, surface_y, z); + if (vm->m_data[i].getContent() == c_dirt_with_grass) + vm->m_data[i] = n_dirt;*/ + + // Add mud on ground + s16 mudcount = 0; + const v3s16 &em = vm->m_area.getExtent(); + s16 y_start = surface_y + 1; + u32 i = vm->m_area.index(x, y_start, z); + for (s16 y = y_start; y <= node_max.Y; y++) { + if (mudcount >= mud_add_amount) + break; + + vm->m_data[i] = addnode; + mudcount++; + + vm->m_area.add_y(em, i, 1); + } + } +} + + +void MapgenV6::flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos) +{ + // 340ms @cs=8 + //TimeTaker timer1("flow mud"); + + // Iterate a few times + for (s16 k = 0; k < 3; k++) { + for (s16 z = mudflow_minpos; z <= mudflow_maxpos; z++) + for (s16 x = mudflow_minpos; x <= mudflow_maxpos; x++) { + // Invert coordinates every 2nd iteration + if (k % 2 == 0) { + x = mudflow_maxpos - (x - mudflow_minpos); + z = mudflow_maxpos - (z - mudflow_minpos); + } + + // Node position in 2d + v2s16 p2d = v2s16(node_min.X, node_min.Z) + v2s16(x, z); + + const v3s16 &em = vm->m_area.getExtent(); + u32 i = vm->m_area.index(p2d.X, node_max.Y, p2d.Y); + s16 y = node_max.Y; + + while (y >= node_min.Y) { + + for (;; y--) { + MapNode *n = NULL; + // Find mud + for (; y >= node_min.Y; y--) { + n = &vm->m_data[i]; + if (n->getContent() == c_dirt || + n->getContent() == c_dirt_with_grass || + n->getContent() == c_gravel) + break; + + vm->m_area.add_y(em, i, -1); + } + + // Stop if out of area + //if(vmanip.m_area.contains(i) == false) + if (y < node_min.Y) + break; + + if (n->getContent() == c_dirt || + n->getContent() == c_dirt_with_grass) { + // Make it exactly mud + n->setContent(c_dirt); + + // Don't flow it if the stuff under it is not mud + { + u32 i2 = i; + vm->m_area.add_y(em, i2, -1); + // Cancel if out of area + if (!vm->m_area.contains(i2)) + continue; + MapNode *n2 = &vm->m_data[i2]; + if (n2->getContent() != c_dirt && + n2->getContent() != c_dirt_with_grass) + continue; + } + } + + v3s16 dirs4[4] = { + v3s16(0, 0, 1), // back + v3s16(1, 0, 0), // right + v3s16(0, 0, -1), // front + v3s16(-1, 0, 0), // left + }; + + // Check that upper is walkable. Cancel + // dropping if upper keeps it in place. + u32 i3 = i; + vm->m_area.add_y(em, i3, 1); + MapNode *n3 = NULL; + + if (vm->m_area.contains(i3)) { + n3 = &vm->m_data[i3]; + if (ndef->get(*n3).walkable) + continue; + } + + // Drop mud on side + for (const v3s16 &dirp : dirs4) { + u32 i2 = i; + // Move to side + vm->m_area.add_p(em, i2, dirp); + // Fail if out of area + if (!vm->m_area.contains(i2)) + continue; + // Check that side is air + MapNode *n2 = &vm->m_data[i2]; + if (ndef->get(*n2).walkable) + continue; + // Check that under side is air + vm->m_area.add_y(em, i2, -1); + if (!vm->m_area.contains(i2)) + continue; + n2 = &vm->m_data[i2]; + if (ndef->get(*n2).walkable) + continue; + // Loop further down until not air + bool dropped_to_unknown = false; + do { + vm->m_area.add_y(em, i2, -1); + n2 = &vm->m_data[i2]; + // if out of known area + if (!vm->m_area.contains(i2) || + n2->getContent() == CONTENT_IGNORE) { + dropped_to_unknown = true; + break; + } + } while (!ndef->get(*n2).walkable); + // Loop one up so that we're in air + vm->m_area.add_y(em, i2, 1); + + // Move mud to new place. Outside mapchunk remove + // any decorations above removed or placed mud. + if (!dropped_to_unknown) + moveMud(i, i2, i3, p2d, em); + + // Done + break; + } + } + } + } + } +} + + +void MapgenV6::moveMud(u32 remove_index, u32 place_index, + u32 above_remove_index, v2s16 pos, v3s16 em) +{ + MapNode n_air(CONTENT_AIR); + // Copy mud from old place to new place + vm->m_data[place_index] = vm->m_data[remove_index]; + // Set old place to be air + vm->m_data[remove_index] = n_air; + // Outside the mapchunk decorations may need to be removed if above removed + // mud or if half-buried in placed mud. Placed mud is to the side of pos so + // use 'pos.X >= node_max.X' etc. + if (pos.X >= node_max.X || pos.X <= node_min.X || + pos.Y >= node_max.Z || pos.Y <= node_min.Z) { + // 'above remove' node is above removed mud. If it is not air, water or + // 'ignore' it is a decoration that needs removing. Also search upwards + // to remove a possible stacked decoration. + // Check for 'ignore' because stacked decorations can penetrate into + // 'ignore' nodes above the mapchunk. + while (vm->m_area.contains(above_remove_index) && + vm->m_data[above_remove_index].getContent() != CONTENT_AIR && + vm->m_data[above_remove_index].getContent() != c_water_source && + vm->m_data[above_remove_index].getContent() != CONTENT_IGNORE) { + vm->m_data[above_remove_index] = n_air; + vm->m_area.add_y(em, above_remove_index, 1); + } + // Mud placed may have partially-buried a stacked decoration, search + // above and remove. + vm->m_area.add_y(em, place_index, 1); + while (vm->m_area.contains(place_index) && + vm->m_data[place_index].getContent() != CONTENT_AIR && + vm->m_data[place_index].getContent() != c_water_source && + vm->m_data[place_index].getContent() != CONTENT_IGNORE) { + vm->m_data[place_index] = n_air; + vm->m_area.add_y(em, place_index, 1); + } + } +} + + +void MapgenV6::placeTreesAndJungleGrass() +{ + //TimeTaker t("placeTrees"); + if (node_max.Y < water_level) + return; + + PseudoRandom grassrandom(blockseed + 53); + content_t c_junglegrass = ndef->getId("mapgen_junglegrass"); + // if we don't have junglegrass, don't place cignore... that's bad + if (c_junglegrass == CONTENT_IGNORE) + c_junglegrass = CONTENT_AIR; + MapNode n_junglegrass(c_junglegrass); + const v3s16 &em = vm->m_area.getExtent(); + + // Divide area into parts + s16 div = 8; + s16 sidelen = central_area_size.X / div; + double area = sidelen * sidelen; + + // N.B. We must add jungle grass first, since tree leaves will + // obstruct the ground, giving us a false ground level + for (s16 z0 = 0; z0 < div; z0++) + for (s16 x0 = 0; x0 < div; x0++) { + // Center position of part of division + v2s16 p2d_center( + node_min.X + sidelen / 2 + sidelen * x0, + node_min.Z + sidelen / 2 + sidelen * z0 + ); + // Minimum edge of part of division + v2s16 p2d_min( + node_min.X + sidelen * x0, + node_min.Z + sidelen * z0 + ); + // Maximum edge of part of division + v2s16 p2d_max( + node_min.X + sidelen + sidelen * x0 - 1, + node_min.Z + sidelen + sidelen * z0 - 1 + ); + + // Get biome at center position of part of division + BiomeV6Type bt = getBiome(p2d_center); + + // Amount of trees + u32 tree_count; + if (bt == BT_JUNGLE || bt == BT_TAIGA || bt == BT_NORMAL) { + tree_count = area * getTreeAmount(p2d_center); + if (bt == BT_JUNGLE) + tree_count *= 4; + } else { + tree_count = 0; + } + + // Add jungle grass + if (bt == BT_JUNGLE) { + float humidity = getHumidity(p2d_center); + u32 grass_count = 5 * humidity * tree_count; + for (u32 i = 0; i < grass_count; i++) { + s16 x = grassrandom.range(p2d_min.X, p2d_max.X); + s16 z = grassrandom.range(p2d_min.Y, p2d_max.Y); + int mapindex = central_area_size.X * (z - node_min.Z) + + (x - node_min.X); + s16 y = heightmap[mapindex]; + if (y < water_level) + continue; + + u32 vi = vm->m_area.index(x, y, z); + // place on dirt_with_grass, since we know it is exposed to sunlight + if (vm->m_data[vi].getContent() == c_dirt_with_grass) { + vm->m_area.add_y(em, vi, 1); + vm->m_data[vi] = n_junglegrass; + } + } + } + + // Put trees in random places on part of division + for (u32 i = 0; i < tree_count; i++) { + s16 x = myrand_range(p2d_min.X, p2d_max.X); + s16 z = myrand_range(p2d_min.Y, p2d_max.Y); + int mapindex = central_area_size.X * (z - node_min.Z) + + (x - node_min.X); + s16 y = heightmap[mapindex]; + // Don't make a tree under water level + // Don't make a tree so high that it doesn't fit + if (y < water_level || y > node_max.Y - 6) + continue; + + v3s16 p(x, y, z); + // Trees grow only on mud and grass + { + u32 i = vm->m_area.index(p); + content_t c = vm->m_data[i].getContent(); + if (c != c_dirt && + c != c_dirt_with_grass && + c != c_dirt_with_snow) + continue; + } + p.Y++; + + // Make a tree + if (bt == BT_JUNGLE) { + treegen::make_jungletree(*vm, p, ndef, myrand()); + } else if (bt == BT_TAIGA) { + treegen::make_pine_tree(*vm, p - v3s16(0, 1, 0), ndef, myrand()); + } else if (bt == BT_NORMAL) { + bool is_apple_tree = (myrand_range(0, 3) == 0) && + getHaveAppleTree(v2s16(x, z)); + treegen::make_tree(*vm, p, is_apple_tree, ndef, myrand()); + } + } + } + //printf("placeTreesAndJungleGrass: %dms\n", t.stop()); +} + + +void MapgenV6::growGrass() // Add surface nodes +{ + MapNode n_dirt_with_grass(c_dirt_with_grass); + MapNode n_dirt_with_snow(c_dirt_with_snow); + MapNode n_snowblock(c_snowblock); + MapNode n_snow(c_snow); + const v3s16 &em = vm->m_area.getExtent(); + + u32 index = 0; + for (s16 z = full_node_min.Z; z <= full_node_max.Z; z++) + for (s16 x = full_node_min.X; x <= full_node_max.X; x++, index++) { + // Find the lowest surface to which enough light ends up to make + // grass grow. Basically just wait until not air and not leaves. + s16 surface_y = 0; + { + u32 i = vm->m_area.index(x, node_max.Y, z); + s16 y; + // Go to ground level + for (y = node_max.Y; y >= full_node_min.Y; y--) { + MapNode &n = vm->m_data[i]; + if (ndef->get(n).param_type != CPT_LIGHT || + ndef->get(n).liquid_type != LIQUID_NONE || + n.getContent() == c_ice) + break; + vm->m_area.add_y(em, i, -1); + } + surface_y = (y >= full_node_min.Y) ? y : full_node_min.Y; + } + + BiomeV6Type bt = getBiome(index, v2s16(x, z)); + u32 i = vm->m_area.index(x, surface_y, z); + content_t c = vm->m_data[i].getContent(); + if (surface_y >= water_level - 20) { + if (bt == BT_TAIGA && c == c_dirt) { + vm->m_data[i] = n_dirt_with_snow; + } else if (bt == BT_TUNDRA) { + if (c == c_dirt) { + vm->m_data[i] = n_snowblock; + vm->m_area.add_y(em, i, -1); + vm->m_data[i] = n_dirt_with_snow; + } else if (c == c_stone && surface_y < node_max.Y) { + vm->m_area.add_y(em, i, 1); + vm->m_data[i] = n_snowblock; + } + } else if (c == c_dirt) { + vm->m_data[i] = n_dirt_with_grass; + } + } + } +} + + +void MapgenV6::generateCaves(int max_stone_y) +{ + float cave_amount = NoisePerlin2D(np_cave, node_min.X, node_min.Y, seed); + int volume_nodes = (node_max.X - node_min.X + 1) * + (node_max.Y - node_min.Y + 1) * MAP_BLOCKSIZE; + cave_amount = MYMAX(0.0, cave_amount); + u32 caves_count = cave_amount * volume_nodes / 50000; + u32 bruises_count = 1; + PseudoRandom ps(blockseed + 21343); + PseudoRandom ps2(blockseed + 1032); + + if (ps.range(1, 6) == 1) + bruises_count = ps.range(0, ps.range(0, 2)); + + if (getBiome(v2s16(node_min.X, node_min.Z)) == BT_DESERT) { + caves_count /= 3; + bruises_count /= 3; + } + + for (u32 i = 0; i < caves_count + bruises_count; i++) { + CavesV6 cave(ndef, &gennotify, water_level, c_water_source, c_lava_source); + + bool large_cave = (i >= caves_count); + cave.makeCave(vm, node_min, node_max, &ps, &ps2, + large_cave, max_stone_y, heightmap); + } +} diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h new file mode 100644 index 000000000..9c8794217 --- /dev/null +++ b/src/mapgen/mapgen_v6.h @@ -0,0 +1,169 @@ +/* +Minetest +Copyright (C) 2010-2015 celeron55, Perttu Ahola <celeron55@gmail.com> +Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2014-2017 paramat + +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. +*/ + +#pragma once + +#include "mapgen.h" +#include "noise.h" + +#define MGV6_AVERAGE_MUD_AMOUNT 4 +#define MGV6_DESERT_STONE_BASE -32 +#define MGV6_ICE_BASE 0 +#define MGV6_FREQ_HOT 0.4 +#define MGV6_FREQ_SNOW -0.4 +#define MGV6_FREQ_TAIGA 0.5 +#define MGV6_FREQ_JUNGLE 0.5 + +//////////// Mapgen V6 flags +#define MGV6_JUNGLES 0x01 +#define MGV6_BIOMEBLEND 0x02 +#define MGV6_MUDFLOW 0x04 +#define MGV6_SNOWBIOMES 0x08 +#define MGV6_FLAT 0x10 +#define MGV6_TREES 0x20 + + +extern FlagDesc flagdesc_mapgen_v6[]; + + +enum BiomeV6Type +{ + BT_NORMAL, + BT_DESERT, + BT_JUNGLE, + BT_TUNDRA, + BT_TAIGA, +}; + + +struct MapgenV6Params : public MapgenParams { + u32 spflags = MGV6_JUNGLES | MGV6_SNOWBIOMES | MGV6_TREES | + MGV6_BIOMEBLEND | MGV6_MUDFLOW; + float freq_desert = 0.45f; + float freq_beach = 0.15f; + NoiseParams np_terrain_base; + NoiseParams np_terrain_higher; + NoiseParams np_steepness; + NoiseParams np_height_select; + NoiseParams np_mud; + NoiseParams np_beach; + NoiseParams np_biome; + NoiseParams np_cave; + NoiseParams np_humidity; + NoiseParams np_trees; + NoiseParams np_apple_trees; + + MapgenV6Params(); + ~MapgenV6Params() = default; + + void readParams(const Settings *settings); + void writeParams(Settings *settings) const; +}; + + +class MapgenV6 : public Mapgen { +public: + EmergeManager *m_emerge; + + int ystride; + u32 spflags; + + v3s16 node_min; + v3s16 node_max; + v3s16 full_node_min; + v3s16 full_node_max; + v3s16 central_area_size; + + Noise *noise_terrain_base; + Noise *noise_terrain_higher; + Noise *noise_steepness; + Noise *noise_height_select; + Noise *noise_mud; + Noise *noise_beach; + Noise *noise_biome; + Noise *noise_humidity; + NoiseParams *np_cave; + NoiseParams *np_humidity; + NoiseParams *np_trees; + NoiseParams *np_apple_trees; + float freq_desert; + float freq_beach; + + content_t c_stone; + content_t c_dirt; + content_t c_dirt_with_grass; + content_t c_sand; + content_t c_water_source; + content_t c_lava_source; + content_t c_gravel; + content_t c_desert_stone; + content_t c_desert_sand; + content_t c_dirt_with_snow; + content_t c_snow; + content_t c_snowblock; + content_t c_ice; + + content_t c_cobble; + content_t c_mossycobble; + content_t c_stair_cobble; + content_t c_stair_desert_stone; + + MapgenV6(int mapgenid, MapgenV6Params *params, EmergeManager *emerge); + ~MapgenV6(); + + virtual MapgenType getType() const { return MAPGEN_V6; } + + void makeChunk(BlockMakeData *data); + int getGroundLevelAtPoint(v2s16 p); + int getSpawnLevelAtPoint(v2s16 p); + + float baseTerrainLevel(float terrain_base, float terrain_higher, + float steepness, float height_select); + virtual float baseTerrainLevelFromNoise(v2s16 p); + virtual float baseTerrainLevelFromMap(v2s16 p); + virtual float baseTerrainLevelFromMap(int index); + + s16 find_stone_level(v2s16 p2d); + bool block_is_underground(u64 seed, v3s16 blockpos); + s16 find_ground_level_from_noise(u64 seed, v2s16 p2d, s16 precision); + + float getHumidity(v2s16 p); + float getTreeAmount(v2s16 p); + bool getHaveAppleTree(v2s16 p); + float getMudAmount(v2s16 p); + virtual float getMudAmount(int index); + bool getHaveBeach(v2s16 p); + bool getHaveBeach(int index); + BiomeV6Type getBiome(v2s16 p); + BiomeV6Type getBiome(int index, v2s16 p); + + u32 get_blockseed(u64 seed, v3s16 p); + + virtual void calculateNoise(); + int generateGround(); + void addMud(); + void flowMud(s16 &mudflow_minpos, s16 &mudflow_maxpos); + void moveMud(u32 remove_index, u32 place_index, + u32 above_remove_index, v2s16 pos, v3s16 em); + void growGrass(); + void placeTreesAndJungleGrass(); + virtual void generateCaves(int max_stone_y); +}; diff --git a/src/mapgen/mapgen_v7.cpp b/src/mapgen/mapgen_v7.cpp new file mode 100644 index 000000000..404780158 --- /dev/null +++ b/src/mapgen/mapgen_v7.cpp @@ -0,0 +1,743 @@ +/* +Minetest +Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2014-2017 paramat + +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 "voxel.h" +#include "noise.h" +#include "mapblock.h" +#include "mapnode.h" +#include "map.h" +#include "content_sao.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_v7.h" + + +FlagDesc flagdesc_mapgen_v7[] = { + {"mountains", MGV7_MOUNTAINS}, + {"ridges", MGV7_RIDGES}, + {"floatlands", MGV7_FLOATLANDS}, + {"caverns", MGV7_CAVERNS}, + {NULL, 0} +}; + + +/////////////////////////////////////////////////////////////////////////////// + + +MapgenV7::MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge) + : MapgenBasic(mapgenid, params, emerge) +{ + spflags = params->spflags; + mount_zero_level = params->mount_zero_level; + cave_width = params->cave_width; + large_cave_depth = params->large_cave_depth; + lava_depth = params->lava_depth; + float_mount_density = params->float_mount_density; + floatland_level = params->floatland_level; + shadow_limit = params->shadow_limit; + cavern_limit = params->cavern_limit; + cavern_taper = params->cavern_taper; + cavern_threshold = params->cavern_threshold; + + // This is to avoid a divide-by-zero. + // Parameter will be saved to map_meta.txt in limited form. + params->float_mount_height = MYMAX(params->float_mount_height, 1.0f); + float_mount_height = params->float_mount_height; + + // 2D noise + noise_terrain_base = new Noise(¶ms->np_terrain_base, seed, csize.X, csize.Z); + noise_terrain_alt = new Noise(¶ms->np_terrain_alt, seed, csize.X, csize.Z); + noise_terrain_persist = new Noise(¶ms->np_terrain_persist, seed, csize.X, csize.Z); + noise_height_select = new Noise(¶ms->np_height_select, seed, csize.X, csize.Z); + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + + if (spflags & MGV7_MOUNTAINS) + noise_mount_height = new Noise(¶ms->np_mount_height, seed, csize.X, csize.Z); + + if (spflags & MGV7_FLOATLANDS) { + noise_floatland_base = new Noise(¶ms->np_floatland_base, seed, csize.X, csize.Z); + noise_float_base_height = new Noise(¶ms->np_float_base_height, seed, csize.X, csize.Z); + } + + if (spflags & MGV7_RIDGES) { + noise_ridge_uwater = new Noise(¶ms->np_ridge_uwater, seed, csize.X, csize.Z); + // 3D noise, 1-up 1-down overgeneration + noise_ridge = new Noise(¶ms->np_ridge, seed, csize.X, csize.Y + 2, csize.Z); + } + // 3D noise, 1 up, 1 down overgeneration + if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS)) + noise_mountain = new Noise(¶ms->np_mountain, seed, csize.X, csize.Y + 2, csize.Z); + // 3D noise, 1 down overgeneration + MapgenBasic::np_cave1 = params->np_cave1; + MapgenBasic::np_cave2 = params->np_cave2; + MapgenBasic::np_cavern = params->np_cavern; +} + + +MapgenV7::~MapgenV7() +{ + delete noise_terrain_base; + delete noise_terrain_alt; + delete noise_terrain_persist; + delete noise_height_select; + delete noise_filler_depth; + + if (spflags & MGV7_MOUNTAINS) + delete noise_mount_height; + + if (spflags & MGV7_FLOATLANDS) { + delete noise_floatland_base; + delete noise_float_base_height; + } + + if (spflags & MGV7_RIDGES) { + delete noise_ridge_uwater; + delete noise_ridge; + } + + if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS)) + delete noise_mountain; +} + + +MapgenV7Params::MapgenV7Params(): + np_terrain_base (4, 70, v3f(600, 600, 600), 82341, 5, 0.6, 2.0), + np_terrain_alt (4, 25, v3f(600, 600, 600), 5934, 5, 0.6, 2.0), + np_terrain_persist (0.6, 0.1, v3f(2000, 2000, 2000), 539, 3, 0.6, 2.0), + np_height_select (-8, 16, v3f(500, 500, 500), 4213, 6, 0.7, 2.0), + np_filler_depth (0, 1.2, v3f(150, 150, 150), 261, 3, 0.7, 2.0), + np_mount_height (256, 112, v3f(1000, 1000, 1000), 72449, 3, 0.6, 2.0), + np_ridge_uwater (0, 1, v3f(1000, 1000, 1000), 85039, 5, 0.6, 2.0), + np_floatland_base (-0.6, 1.5, v3f(600, 600, 600), 114, 5, 0.6, 2.0), + np_float_base_height (48, 24, v3f(300, 300, 300), 907, 4, 0.7, 2.0), + np_mountain (-0.6, 1, v3f(250, 350, 250), 5333, 5, 0.63, 2.0), + np_ridge (0, 1, v3f(100, 100, 100), 6467, 4, 0.75, 2.0), + np_cavern (0, 1, v3f(384, 128, 384), 723, 5, 0.63, 2.0), + np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), + np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0) +{ +} + + +void MapgenV7Params::readParams(const Settings *settings) +{ + settings->getFlagStrNoEx("mgv7_spflags", spflags, flagdesc_mapgen_v7); + settings->getS16NoEx("mgv7_mount_zero_level", mount_zero_level); + settings->getFloatNoEx("mgv7_cave_width", cave_width); + settings->getS16NoEx("mgv7_large_cave_depth", large_cave_depth); + settings->getS16NoEx("mgv7_lava_depth", lava_depth); + settings->getFloatNoEx("mgv7_float_mount_density", float_mount_density); + settings->getFloatNoEx("mgv7_float_mount_height", float_mount_height); + settings->getS16NoEx("mgv7_floatland_level", floatland_level); + settings->getS16NoEx("mgv7_shadow_limit", shadow_limit); + settings->getS16NoEx("mgv7_cavern_limit", cavern_limit); + settings->getS16NoEx("mgv7_cavern_taper", cavern_taper); + settings->getFloatNoEx("mgv7_cavern_threshold", cavern_threshold); + + settings->getNoiseParams("mgv7_np_terrain_base", np_terrain_base); + settings->getNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); + settings->getNoiseParams("mgv7_np_terrain_persist", np_terrain_persist); + settings->getNoiseParams("mgv7_np_height_select", np_height_select); + settings->getNoiseParams("mgv7_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgv7_np_mount_height", np_mount_height); + settings->getNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); + settings->getNoiseParams("mgv7_np_floatland_base", np_floatland_base); + settings->getNoiseParams("mgv7_np_float_base_height", np_float_base_height); + settings->getNoiseParams("mgv7_np_mountain", np_mountain); + settings->getNoiseParams("mgv7_np_ridge", np_ridge); + settings->getNoiseParams("mgv7_np_cavern", np_cavern); + settings->getNoiseParams("mgv7_np_cave1", np_cave1); + settings->getNoiseParams("mgv7_np_cave2", np_cave2); +} + + +void MapgenV7Params::writeParams(Settings *settings) const +{ + settings->setFlagStr("mgv7_spflags", spflags, flagdesc_mapgen_v7, U32_MAX); + settings->setS16("mgv7_mount_zero_level", mount_zero_level); + settings->setFloat("mgv7_cave_width", cave_width); + settings->setS16("mgv7_large_cave_depth", large_cave_depth); + settings->setS16("mgv7_lava_depth", lava_depth); + settings->setFloat("mgv7_float_mount_density", float_mount_density); + settings->setFloat("mgv7_float_mount_height", float_mount_height); + settings->setS16("mgv7_floatland_level", floatland_level); + settings->setS16("mgv7_shadow_limit", shadow_limit); + settings->setS16("mgv7_cavern_limit", cavern_limit); + settings->setS16("mgv7_cavern_taper", cavern_taper); + settings->setFloat("mgv7_cavern_threshold", cavern_threshold); + + settings->setNoiseParams("mgv7_np_terrain_base", np_terrain_base); + settings->setNoiseParams("mgv7_np_terrain_alt", np_terrain_alt); + settings->setNoiseParams("mgv7_np_terrain_persist", np_terrain_persist); + settings->setNoiseParams("mgv7_np_height_select", np_height_select); + settings->setNoiseParams("mgv7_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgv7_np_mount_height", np_mount_height); + settings->setNoiseParams("mgv7_np_ridge_uwater", np_ridge_uwater); + settings->setNoiseParams("mgv7_np_floatland_base", np_floatland_base); + settings->setNoiseParams("mgv7_np_float_base_height", np_float_base_height); + settings->setNoiseParams("mgv7_np_mountain", np_mountain); + settings->setNoiseParams("mgv7_np_ridge", np_ridge); + settings->setNoiseParams("mgv7_np_cavern", np_cavern); + settings->setNoiseParams("mgv7_np_cave1", np_cave1); + settings->setNoiseParams("mgv7_np_cave2", np_cave2); +} + + +/////////////////////////////////////////////////////////////////////////////// + + +int MapgenV7::getSpawnLevelAtPoint(v2s16 p) +{ + // If rivers are enabled, first check if in a river + if (spflags & MGV7_RIDGES) { + float width = 0.2; + float uwatern = NoisePerlin2D(&noise_ridge_uwater->np, p.X, p.Y, seed) * 2; + if (fabs(uwatern) <= width) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + } + + // Terrain noise 'offset' is the average level of that terrain. + // At least 50% of terrain will be below the higher of base and alt terrain + // 'offset's. + // Raising the maximum spawn level above 'water_level + 16' is necessary + // for when terrain 'offset's are set much higher than water_level. + s16 max_spawn_y = MYMAX(MYMAX(noise_terrain_alt->np.offset, + noise_terrain_base->np.offset), + water_level + 16); + // Base terrain calculation + s16 y = baseTerrainLevelAtPoint(p.X, p.Y); + + // If mountains are disabled, terrain level is base terrain level. + // Avoids mid-air spawn where mountain terrain would have been. + if (!(spflags & MGV7_MOUNTAINS)) { + if (y < water_level || y > max_spawn_y) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + + // y + 2 because y is surface level and due to biome 'dust' + return y + 2; + } + + // Search upwards for first node without mountain terrain + int iters = 256; + while (iters > 0 && y <= max_spawn_y) { + if (!getMountainTerrainAtPoint(p.X, y + 1, p.Y)) { + if (y <= water_level || y > max_spawn_y) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + + // y + 1 due to biome 'dust' + return y + 1; + } + y++; + iters--; + } + + // Unsuitable spawn point + return MAX_MAP_GENERATION_LIMIT; +} + + +void MapgenV7::makeChunk(BlockMakeData *data) +{ + // Pre-conditions + assert(data->vmanip); + assert(data->nodedef); + assert(data->blockpos_requested.X >= data->blockpos_min.X && + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); + assert(data->blockpos_requested.X <= data->blockpos_max.X && + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); + + this->generating = true; + this->vm = data->vmanip; + this->ndef = data->nodedef; + //TimeTaker t("makeChunk"); + + 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); + + blockseed = getBlockSeed2(full_node_min, seed); + + // Generate base and mountain terrain + // An initial heightmap is no longer created here for use in generateRidgeTerrain() + s16 stone_surface_max_y = generateTerrain(); + + // Generate rivers + if (spflags & MGV7_RIDGES) + generateRidgeTerrain(); + + // Create heightmap + updateHeightmap(node_min, node_max); + + // Init biome generator, place biome-specific nodes, and build biomemap + biomegen->calcBiomeNoise(node_min); + + MgStoneType mgstone_type; + content_t biome_stone; + generateBiomes(&mgstone_type, &biome_stone); + + // Generate caverns, tunnels and classic caves + if (flags & MG_CAVES) { + bool near_cavern = false; + // Generate caverns + if (spflags & MGV7_CAVERNS) + near_cavern = generateCaverns(stone_surface_max_y); + // Generate tunnels and classic caves + if (near_cavern) + // Disable classic caves in this mapchunk by setting + // 'large cave depth' to world base. Avoids excessive liquid in + // large caverns and floating blobs of overgenerated liquid. + generateCaves(stone_surface_max_y, -MAX_MAP_GENERATION_LIMIT); + else + generateCaves(stone_surface_max_y, large_cave_depth); + } + + // Generate dungeons + if (flags & MG_DUNGEONS) + generateDungeons(stone_surface_max_y, mgstone_type, biome_stone); + + // Generate the registered decorations + if (flags & MG_DECORATIONS) + m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max); + + // Generate the registered ores + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + + // Sprinkle some dust on top after everything else was generated + dustTopNodes(); + + //printf("makeChunk: %dms\n", t.stop()); + + // Update liquids + updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); + + // Calculate lighting + // Limit floatland shadow + bool propagate_shadow = !((spflags & MGV7_FLOATLANDS) && + node_min.Y <= shadow_limit && node_max.Y >= shadow_limit); + + if (flags & MG_LIGHT) + calcLighting(node_min - v3s16(0, 1, 0), node_max + v3s16(0, 1, 0), + full_node_min, full_node_max, propagate_shadow); + + //setLighting(node_min - v3s16(1, 0, 1) * MAP_BLOCKSIZE, + // node_max + v3s16(1, 0, 1) * MAP_BLOCKSIZE, 0xFF); + + this->generating = false; +} + + +float MapgenV7::baseTerrainLevelAtPoint(s16 x, s16 z) +{ + float hselect = NoisePerlin2D(&noise_height_select->np, x, z, seed); + hselect = rangelim(hselect, 0.0, 1.0); + + float persist = NoisePerlin2D(&noise_terrain_persist->np, x, z, seed); + + noise_terrain_base->np.persist = persist; + float height_base = NoisePerlin2D(&noise_terrain_base->np, x, z, seed); + + noise_terrain_alt->np.persist = persist; + float height_alt = NoisePerlin2D(&noise_terrain_alt->np, x, z, seed); + + if (height_alt > height_base) + return height_alt; + + return (height_base * hselect) + (height_alt * (1.0 - hselect)); +} + + +float MapgenV7::baseTerrainLevelFromMap(int index) +{ + float hselect = rangelim(noise_height_select->result[index], 0.0, 1.0); + float height_base = noise_terrain_base->result[index]; + float height_alt = noise_terrain_alt->result[index]; + + if (height_alt > height_base) + return height_alt; + + return (height_base * hselect) + (height_alt * (1.0 - hselect)); +} + + +bool MapgenV7::getMountainTerrainAtPoint(s16 x, s16 y, s16 z) +{ + float mnt_h_n = + MYMAX(NoisePerlin2D(&noise_mount_height->np, x, z, seed), 1.0f); + float density_gradient = -((float)(y - mount_zero_level) / mnt_h_n); + float mnt_n = NoisePerlin3D(&noise_mountain->np, x, y, z, seed); + + return mnt_n + density_gradient >= 0.0; +} + + +bool MapgenV7::getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y) +{ + float mounthn = MYMAX(noise_mount_height->result[idx_xz], 1.0f); + float density_gradient = -((float)(y - mount_zero_level) / mounthn); + float mountn = noise_mountain->result[idx_xyz]; + + return mountn + density_gradient >= 0.0; +} + + +bool MapgenV7::getFloatlandMountainFromMap(int idx_xyz, int idx_xz, s16 y) +{ + // Make rim 2 nodes thick to match floatland base terrain + float density_gradient = (y >= floatland_level) ? + -pow((float)(y - floatland_level) / float_mount_height, 0.75f) : + -pow((float)(floatland_level - 1 - y) / float_mount_height, 0.75f); + + float floatn = noise_mountain->result[idx_xyz] + float_mount_density; + + return floatn + density_gradient >= 0.0f; +} + + +void MapgenV7::floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, int idx_xz) +{ + // '+1' to avoid a layer of stone at y = MAX_MAP_GENERATION_LIMIT + s16 base_min = MAX_MAP_GENERATION_LIMIT + 1; + s16 base_max = MAX_MAP_GENERATION_LIMIT; + + float n_base = noise_floatland_base->result[idx_xz]; + if (n_base > 0.0f) { + float n_base_height = + MYMAX(noise_float_base_height->result[idx_xz], 1.0f); + float amp = n_base * n_base_height; + float ridge = n_base_height / 3.0f; + base_min = floatland_level - amp / 1.5f; + + if (amp > ridge * 2.0f) { + // Lake bed + base_max = floatland_level - (amp - ridge * 2.0f) / 2.0f; + } else { + // Hills and ridges + float diff = fabs(amp - ridge) / ridge; + // Smooth ridges using the 'smoothstep function' + float smooth_diff = diff * diff * (3.0f - 2.0f * diff); + base_max = floatland_level + ridge - smooth_diff * ridge; + } + } + + *float_base_min = base_min; + *float_base_max = base_max; +} + + +int MapgenV7::generateTerrain() +{ + MapNode n_air(CONTENT_AIR); + MapNode n_stone(c_stone); + MapNode n_water(c_water_source); + + //// Calculate noise for terrain generation + noise_terrain_persist->perlinMap2D(node_min.X, node_min.Z); + float *persistmap = noise_terrain_persist->result; + + noise_terrain_base->perlinMap2D(node_min.X, node_min.Z, persistmap); + noise_terrain_alt->perlinMap2D(node_min.X, node_min.Z, persistmap); + noise_height_select->perlinMap2D(node_min.X, node_min.Z); + + if ((spflags & MGV7_MOUNTAINS) || (spflags & MGV7_FLOATLANDS)) { + noise_mountain->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + } + + if (spflags & MGV7_MOUNTAINS) { + noise_mount_height->perlinMap2D(node_min.X, node_min.Z); + } + + if (spflags & MGV7_FLOATLANDS) { + noise_floatland_base->perlinMap2D(node_min.X, node_min.Z); + noise_float_base_height->perlinMap2D(node_min.X, node_min.Z); + } + + //// Place nodes + const v3s16 &em = vm->m_area.getExtent(); + s16 stone_surface_max_y = -MAX_MAP_GENERATION_LIMIT; + 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++) { + s16 surface_y = baseTerrainLevelFromMap(index2d); + if (surface_y > stone_surface_max_y) + stone_surface_max_y = surface_y; + + // Get extent of floatland base terrain + // '+1' to avoid a layer of stone at y = MAX_MAP_GENERATION_LIMIT + s16 float_base_min = MAX_MAP_GENERATION_LIMIT + 1; + s16 float_base_max = MAX_MAP_GENERATION_LIMIT; + if (spflags & MGV7_FLOATLANDS) + floatBaseExtentFromMap(&float_base_min, &float_base_max, index2d); + + u32 vi = vm->m_area.index(x, node_min.Y - 1, z); + u32 index3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X); + + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { + if (vm->m_data[vi].getContent() == CONTENT_IGNORE) { + if (y <= surface_y) { + vm->m_data[vi] = n_stone; // Base terrain + } else if ((spflags & MGV7_MOUNTAINS) && + getMountainTerrainFromMap(index3d, index2d, y)) { + vm->m_data[vi] = n_stone; // Mountain terrain + if (y > stone_surface_max_y) + stone_surface_max_y = y; + } else if ((spflags & MGV7_FLOATLANDS) && + ((y >= float_base_min && y <= float_base_max) || + getFloatlandMountainFromMap(index3d, index2d, y))) { + vm->m_data[vi] = n_stone; // Floatland terrain + stone_surface_max_y = node_max.Y; + } else if (y <= water_level) { + vm->m_data[vi] = n_water; // Ground level water + } else if ((spflags & MGV7_FLOATLANDS) && + (y >= float_base_max && y <= floatland_level)) { + vm->m_data[vi] = n_water; // Floatland water + } else { + vm->m_data[vi] = n_air; + } + } + vm->m_area.add_y(em, vi, 1); + index3d += ystride; + } + } + + return stone_surface_max_y; +} + + +void MapgenV7::generateRidgeTerrain() +{ + if (node_max.Y < water_level - 16 || + ((spflags & MGV7_FLOATLANDS) && node_max.Y > shadow_limit)) + return; + + noise_ridge->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + noise_ridge_uwater->perlinMap2D(node_min.X, node_min.Z); + + MapNode n_water(c_water_source); + MapNode n_air(CONTENT_AIR); + u32 index = 0; + float width = 0.2; + + for (s16 z = node_min.Z; z <= node_max.Z; z++) + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { + u32 vi = vm->m_area.index(node_min.X, y, z); + for (s16 x = node_min.X; x <= node_max.X; x++, index++, vi++) { + int j = (z - node_min.Z) * csize.X + (x - node_min.X); + + float uwatern = noise_ridge_uwater->result[j] * 2; + if (fabs(uwatern) > width) + continue; + + float altitude = y - water_level; + float height_mod = (altitude + 17) / 2.5; + float width_mod = width - fabs(uwatern); + float nridge = noise_ridge->result[index] * MYMAX(altitude, 0) / 7.0; + + if (nridge + width_mod * height_mod < 0.6) + continue; + + vm->m_data[vi] = (y > water_level) ? n_air : n_water; + } + } +} + + +//////////////////////////////////////////////////////////////////////////////// +//// Code Boneyard +//// +//// Much of the stuff here has potential to become useful again at some point +//// in the future, but we don't want it to get lost or forgotten in version +//// control. +//// + +#if 0 +int MapgenV7::generateMountainTerrain(s16 ymax) +{ + MapNode n_stone(c_stone); + u32 j = 0; + + for (s16 z = node_min.Z; z <= node_max.Z; z++) + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { + u32 vi = vm->m_area.index(node_min.X, y, z); + for (s16 x = node_min.X; x <= node_max.X; x++) { + int index = (z - node_min.Z) * csize.X + (x - node_min.X); + content_t c = vm->m_data[vi].getContent(); + + if (getMountainTerrainFromMap(j, index, y) + && (c == CONTENT_AIR || c == c_water_source)) { + vm->m_data[vi] = n_stone; + if (y > ymax) + ymax = y; + } + + vi++; + j++; + } + } + + return ymax; +} +#endif + + +#if 0 +void MapgenV7::carveRivers() { + MapNode n_air(CONTENT_AIR), n_water_source(c_water_source); + MapNode n_stone(c_stone); + u32 index = 0; + + int river_depth = 4; + + for (s16 z = node_min.Z; z <= node_max.Z; z++) + for (s16 x = node_min.X; x <= node_max.X; x++, index++) { + float terrain_mod = noise_terrain_mod->result[index]; + NoiseParams *np = noise_terrain_river->np; + np.persist = noise_terrain_persist->result[index]; + float terrain_river = NoisePerlin2DNoTxfm(np, x, z, seed); + float height = terrain_river * (1 - abs(terrain_mod)) * + noise_terrain_river->np.scale; + height = log(height * height); //log(h^3) is pretty interesting for terrain + + s16 y = heightmap[index]; + if (height < 1.0 && y > river_depth && + y - river_depth >= node_min.Y && y <= node_max.Y) { + + for (s16 ry = y; ry != y - river_depth; ry--) { + u32 vi = vm->m_area.index(x, ry, z); + vm->m_data[vi] = n_air; + } + + u32 vi = vm->m_area.index(x, y - river_depth, z); + vm->m_data[vi] = n_water_source; + } + } +} +#endif + + +#if 0 +void MapgenV7::addTopNodes() +{ + v3s16 em = vm->m_area.getExtent(); + s16 ntopnodes; + 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 = bmgr->biomes[biomemap[index]]; + + //////////////////// First, add top nodes below the ridge + s16 y = ridge_heightmap[index]; + + // This cutoff is good enough, but not perfect. + // It will cut off potentially placed top nodes at chunk boundaries + if (y < node_min.Y) + continue; + if (y > node_max.Y) { + y = node_max.Y; // Let's see if we can still go downward anyway + u32 vi = vm->m_area.index(x, y, z); + content_t c = vm->m_data[vi].getContent(); + if (ndef->get(c).walkable) + continue; + } + + // N.B. It is necessary to search downward since ridge_heightmap[i] + // might not be the actual height, just the lowest part in the chunk + // where a ridge had been carved + u32 i = vm->m_area.index(x, y, z); + for (; y >= node_min.Y; y--) { + content_t c = vm->m_data[i].getContent(); + if (ndef->get(c).walkable) + break; + vm->m_area.add_y(em, i, -1); + } + + if (y != node_min.Y - 1 && y >= water_level) { + ridge_heightmap[index] = y; //update ridgeheight + ntopnodes = biome->top_depth; + for (; y <= node_max.Y && ntopnodes; y++) { + ntopnodes--; + vm->m_data[i] = MapNode(biome->c_top); + vm->m_area.add_y(em, i, 1); + } + // If dirt, grow grass on it. + if (y > water_level - 10 && + vm->m_data[i].getContent() == CONTENT_AIR) { + vm->m_area.add_y(em, i, -1); + if (vm->m_data[i].getContent() == c_dirt) + vm->m_data[i] = MapNode(c_dirt_with_grass); + } + } + + //////////////////// Now, add top nodes on top of the ridge + y = heightmap[index]; + if (y > node_max.Y) { + y = node_max.Y; // Let's see if we can still go downward anyway + u32 vi = vm->m_area.index(x, y, z); + content_t c = vm->m_data[vi].getContent(); + if (ndef->get(c).walkable) + continue; + } + + i = vm->m_area.index(x, y, z); + for (; y >= node_min.Y; y--) { + content_t c = vm->m_data[i].getContent(); + if (ndef->get(c).walkable) + break; + vm->m_area.add_y(em, i, -1); + } + + if (y != node_min.Y - 1) { + ntopnodes = biome->top_depth; + // Let's see if we've already added it... + if (y == ridge_heightmap[index] + ntopnodes - 1) + continue; + + for (; y <= node_max.Y && ntopnodes; y++) { + ntopnodes--; + vm->m_data[i] = MapNode(biome->c_top); + vm->m_area.add_y(em, i, 1); + } + // If dirt, grow grass on it. + if (y > water_level - 10 && + vm->m_data[i].getContent() == CONTENT_AIR) { + vm->m_area.add_y(em, i, -1); + if (vm->m_data[i].getContent() == c_dirt) + vm->m_data[i] = MapNode(c_dirt_with_grass); + } + } + } +} +#endif diff --git a/src/mapgen/mapgen_v7.h b/src/mapgen/mapgen_v7.h new file mode 100644 index 000000000..6fb7dc4bb --- /dev/null +++ b/src/mapgen/mapgen_v7.h @@ -0,0 +1,111 @@ +/* +Minetest +Copyright (C) 2013-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2014-2017 paramat + +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. +*/ + +#pragma once + +#include "mapgen.h" + +///////////// Mapgen V7 flags +#define MGV7_MOUNTAINS 0x01 +#define MGV7_RIDGES 0x02 +#define MGV7_FLOATLANDS 0x04 +#define MGV7_CAVERNS 0x08 +#define MGV7_BIOMEREPEAT 0x10 // Now unused + +class BiomeManager; + +extern FlagDesc flagdesc_mapgen_v7[]; + + +struct MapgenV7Params : public MapgenParams { + u32 spflags = MGV7_MOUNTAINS | MGV7_RIDGES | MGV7_CAVERNS; + s16 mount_zero_level = 0; + float cave_width = 0.09f; + s16 large_cave_depth = -33; + s16 lava_depth = -256; + float float_mount_density = 0.6f; + float float_mount_height = 128.0f; + s16 floatland_level = 1280; + s16 shadow_limit = 1024; + s16 cavern_limit = -256; + s16 cavern_taper = 256; + float cavern_threshold = 0.7f; + + NoiseParams np_terrain_base; + NoiseParams np_terrain_alt; + NoiseParams np_terrain_persist; + NoiseParams np_height_select; + NoiseParams np_filler_depth; + NoiseParams np_mount_height; + NoiseParams np_ridge_uwater; + NoiseParams np_floatland_base; + NoiseParams np_float_base_height; + NoiseParams np_mountain; + NoiseParams np_ridge; + NoiseParams np_cavern; + NoiseParams np_cave1; + NoiseParams np_cave2; + + MapgenV7Params(); + ~MapgenV7Params() = default; + + void readParams(const Settings *settings); + void writeParams(Settings *settings) const; +}; + +class MapgenV7 : public MapgenBasic { +public: + MapgenV7(int mapgenid, MapgenV7Params *params, EmergeManager *emerge); + ~MapgenV7(); + + virtual MapgenType getType() const { return MAPGEN_V7; } + + virtual void makeChunk(BlockMakeData *data); + int getSpawnLevelAtPoint(v2s16 p); + + float baseTerrainLevelAtPoint(s16 x, s16 z); + float baseTerrainLevelFromMap(int index); + bool getMountainTerrainAtPoint(s16 x, s16 y, s16 z); + bool getMountainTerrainFromMap(int idx_xyz, int idx_xz, s16 y); + bool getFloatlandMountainFromMap(int idx_xyz, int idx_xz, s16 y); + void floatBaseExtentFromMap(s16 *float_base_min, s16 *float_base_max, int idx_xz); + + int generateTerrain(); + void generateRidgeTerrain(); + +private: + s16 mount_zero_level; + s16 large_cave_depth; + float float_mount_density; + float float_mount_height; + s16 floatland_level; + s16 shadow_limit; + + Noise *noise_terrain_base; + Noise *noise_terrain_alt; + Noise *noise_terrain_persist; + Noise *noise_height_select; + Noise *noise_mount_height; + Noise *noise_ridge_uwater; + Noise *noise_floatland_base; + Noise *noise_float_base_height; + Noise *noise_mountain; + Noise *noise_ridge; +}; diff --git a/src/mapgen/mapgen_valleys.cpp b/src/mapgen/mapgen_valleys.cpp new file mode 100644 index 000000000..a13bb4582 --- /dev/null +++ b/src/mapgen/mapgen_valleys.cpp @@ -0,0 +1,743 @@ +/* +Minetest Valleys C +Copyright (C) 2016-2017 Duane Robertson <duane@duanerobertson.com> +Copyright (C) 2016-2017 paramat + +Based on Valleys Mapgen by Gael de Sailly + (https://forum.minetest.net/viewtopic.php?f=9&t=11430) +and mapgen_v7, mapgen_flat by kwolekr and paramat. + +Licensing changed by permission of Gael de Sailly. + +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 "voxel.h" +#include "noise.h" +#include "mapblock.h" +#include "mapnode.h" +#include "map.h" +#include "nodedef.h" +#include "voxelalgorithms.h" +#include "settings.h" // For g_settings +#include "emerge.h" +#include "dungeongen.h" +#include "mg_biome.h" +#include "mg_ore.h" +#include "mg_decoration.h" +#include "mapgen_valleys.h" +#include "cavegen.h" + + +//#undef NDEBUG +//#include "assert.h" + +//#include "util/timetaker.h" +//#include "profiler.h" + + +//static Profiler mapgen_prof; +//Profiler *mapgen_profiler = &mapgen_prof; + +static FlagDesc flagdesc_mapgen_valleys[] = { + {"altitude_chill", MGVALLEYS_ALT_CHILL}, + {"humid_rivers", MGVALLEYS_HUMID_RIVERS}, + {NULL, 0} +}; + +/////////////////////////////////////////////////////////////////////////////// + + +MapgenValleys::MapgenValleys(int mapgenid, MapgenValleysParams *params, EmergeManager *emerge) + : MapgenBasic(mapgenid, params, emerge) +{ + // NOTE: MapgenValleys has a hard dependency on BiomeGenOriginal + m_bgen = (BiomeGenOriginal *)biomegen; + + BiomeParamsOriginal *bp = (BiomeParamsOriginal *)params->bparams; + + spflags = params->spflags; + altitude_chill = params->altitude_chill; + large_cave_depth = params->large_cave_depth; + lava_features_lim = rangelim(params->lava_features, 0, 10); + massive_cave_depth = params->massive_cave_depth; + river_depth_bed = params->river_depth + 1.f; + river_size_factor = params->river_size / 100.f; + water_features_lim = rangelim(params->water_features, 0, 10); + cave_width = params->cave_width; + + //// 2D Terrain noise + noise_filler_depth = new Noise(¶ms->np_filler_depth, seed, csize.X, csize.Z); + noise_inter_valley_slope = new Noise(¶ms->np_inter_valley_slope, seed, csize.X, csize.Z); + noise_rivers = new Noise(¶ms->np_rivers, seed, csize.X, csize.Z); + noise_terrain_height = new Noise(¶ms->np_terrain_height, seed, csize.X, csize.Z); + noise_valley_depth = new Noise(¶ms->np_valley_depth, seed, csize.X, csize.Z); + noise_valley_profile = new Noise(¶ms->np_valley_profile, seed, csize.X, csize.Z); + + //// 3D Terrain noise + // 1-up 1-down overgeneration + noise_inter_valley_fill = new Noise(¶ms->np_inter_valley_fill, seed, csize.X, csize.Y + 2, csize.Z); + // 1-down overgeneraion + 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_massive_caves = new Noise(¶ms->np_massive_caves, seed, csize.X, csize.Y + 1, csize.Z); + + humid_rivers = (spflags & MGVALLEYS_HUMID_RIVERS); + use_altitude_chill = (spflags & MGVALLEYS_ALT_CHILL); + humidity_adjust = bp->np_humidity.offset - 50.f; + + // a small chance of overflows if the settings are very high + cave_water_max_height = water_level + MYMAX(0, water_features_lim - 4) * 50; + lava_max_height = water_level + MYMAX(0, lava_features_lim - 4) * 50; + + tcave_cache = new float[csize.Y + 2]; +} + + +MapgenValleys::~MapgenValleys() +{ + delete noise_cave1; + delete noise_cave2; + delete noise_filler_depth; + delete noise_inter_valley_fill; + delete noise_inter_valley_slope; + delete noise_rivers; + delete noise_massive_caves; + delete noise_terrain_height; + delete noise_valley_depth; + delete noise_valley_profile; + + delete[] tcave_cache; +} + + +MapgenValleysParams::MapgenValleysParams(): + np_cave1 (0, 12, v3f(61, 61, 61), 52534, 3, 0.5, 2.0), + np_cave2 (0, 12, v3f(67, 67, 67), 10325, 3, 0.5, 2.0), + np_filler_depth (0.f, 1.2f, v3f(256, 256, 256), 1605, 3, 0.5f, 2.f), + np_inter_valley_fill (0.f, 1.f, v3f(256, 512, 256), 1993, 6, 0.8f, 2.f), + np_inter_valley_slope (0.5f, 0.5f, v3f(128, 128, 128), 746, 1, 1.f, 2.f), + np_rivers (0.f, 1.f, v3f(256, 256, 256), -6050, 5, 0.6f, 2.f), + np_massive_caves (0.f, 1.f, v3f(768, 256, 768), 59033, 6, 0.63f, 2.f), + np_terrain_height (-10.f, 50.f, v3f(1024, 1024, 1024), 5202, 6, 0.4f, 2.f), + np_valley_depth (5.f, 4.f, v3f(512, 512, 512), -1914, 1, 1.f, 2.f), + np_valley_profile (0.6f, 0.5f, v3f(512, 512, 512), 777, 1, 1.f, 2.f) +{ +} + + +void MapgenValleysParams::readParams(const Settings *settings) +{ + settings->getFlagStrNoEx("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys); + settings->getU16NoEx("mgvalleys_altitude_chill", altitude_chill); + settings->getS16NoEx("mgvalleys_large_cave_depth", large_cave_depth); + settings->getU16NoEx("mgvalleys_lava_features", lava_features); + settings->getS16NoEx("mgvalleys_massive_cave_depth", massive_cave_depth); + settings->getU16NoEx("mgvalleys_river_depth", river_depth); + settings->getU16NoEx("mgvalleys_river_size", river_size); + settings->getU16NoEx("mgvalleys_water_features", water_features); + settings->getFloatNoEx("mgvalleys_cave_width", cave_width); + + settings->getNoiseParams("mgvalleys_np_cave1", np_cave1); + settings->getNoiseParams("mgvalleys_np_cave2", np_cave2); + settings->getNoiseParams("mgvalleys_np_filler_depth", np_filler_depth); + settings->getNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill); + settings->getNoiseParams("mgvalleys_np_inter_valley_slope", np_inter_valley_slope); + settings->getNoiseParams("mgvalleys_np_rivers", np_rivers); + settings->getNoiseParams("mgvalleys_np_massive_caves", np_massive_caves); + settings->getNoiseParams("mgvalleys_np_terrain_height", np_terrain_height); + settings->getNoiseParams("mgvalleys_np_valley_depth", np_valley_depth); + settings->getNoiseParams("mgvalleys_np_valley_profile", np_valley_profile); +} + + +void MapgenValleysParams::writeParams(Settings *settings) const +{ + settings->setFlagStr("mgvalleys_spflags", spflags, flagdesc_mapgen_valleys, U32_MAX); + settings->setU16("mgvalleys_altitude_chill", altitude_chill); + settings->setS16("mgvalleys_large_cave_depth", large_cave_depth); + settings->setU16("mgvalleys_lava_features", lava_features); + settings->setS16("mgvalleys_massive_cave_depth", massive_cave_depth); + settings->setU16("mgvalleys_river_depth", river_depth); + settings->setU16("mgvalleys_river_size", river_size); + settings->setU16("mgvalleys_water_features", water_features); + settings->setFloat("mgvalleys_cave_width", cave_width); + + settings->setNoiseParams("mgvalleys_np_cave1", np_cave1); + settings->setNoiseParams("mgvalleys_np_cave2", np_cave2); + settings->setNoiseParams("mgvalleys_np_filler_depth", np_filler_depth); + settings->setNoiseParams("mgvalleys_np_inter_valley_fill", np_inter_valley_fill); + settings->setNoiseParams("mgvalleys_np_inter_valley_slope", np_inter_valley_slope); + settings->setNoiseParams("mgvalleys_np_rivers", np_rivers); + settings->setNoiseParams("mgvalleys_np_massive_caves", np_massive_caves); + settings->setNoiseParams("mgvalleys_np_terrain_height", np_terrain_height); + settings->setNoiseParams("mgvalleys_np_valley_depth", np_valley_depth); + settings->setNoiseParams("mgvalleys_np_valley_profile", np_valley_profile); +} + + +/////////////////////////////////////// + + +void MapgenValleys::makeChunk(BlockMakeData *data) +{ + // Pre-conditions + assert(data->vmanip); + assert(data->nodedef); + assert(data->blockpos_requested.X >= data->blockpos_min.X && + data->blockpos_requested.Y >= data->blockpos_min.Y && + data->blockpos_requested.Z >= data->blockpos_min.Z); + assert(data->blockpos_requested.X <= data->blockpos_max.X && + data->blockpos_requested.Y <= data->blockpos_max.Y && + data->blockpos_requested.Z <= data->blockpos_max.Z); + + this->generating = true; + this->vm = data->vmanip; + this->ndef = data->nodedef; + + //TimeTaker t("makeChunk"); + + 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); + + blockseed = getBlockSeed2(full_node_min, seed); + + // Generate biome noises. Note this must be executed strictly before + // generateTerrain, because generateTerrain depends on intermediate + // biome-related noises. + m_bgen->calcBiomeNoise(node_min); + + // Generate noise maps and base terrain height. + // Modify heat and humidity maps. + calculateNoise(); + + // Generate base terrain with initial heightmaps + s16 stone_surface_max_y = generateTerrain(); + + // Recalculate heightmap + updateHeightmap(node_min, node_max); + + // Place biome-specific nodes and build biomemap + MgStoneType mgstone_type; + content_t biome_stone; + generateBiomes(&mgstone_type, &biome_stone); + + // Cave creation. + if (flags & MG_CAVES) + generateCaves(stone_surface_max_y, large_cave_depth); + + // Dungeon creation + if ((flags & MG_DUNGEONS) && node_max.Y < 50) + generateDungeons(stone_surface_max_y, mgstone_type, biome_stone); + + // Generate the registered decorations + if (flags & MG_DECORATIONS) + m_emerge->decomgr->placeAllDecos(this, blockseed, node_min, node_max); + + // Generate the registered ores + m_emerge->oremgr->placeAllOres(this, blockseed, node_min, node_max); + + // Sprinkle some dust on top after everything else was generated + dustTopNodes(); + + //TimeTaker tll("liquid_lighting"); + + updateLiquid(&data->transforming_liquid, full_node_min, full_node_max); + + if (flags & MG_LIGHT) + calcLighting( + node_min - v3s16(0, 1, 0), + node_max + v3s16(0, 1, 0), + full_node_min, + full_node_max); + + //mapgen_profiler->avg("liquid_lighting", tll.stop() / 1000.f); + //mapgen_profiler->avg("makeChunk", t.stop() / 1000.f); + + this->generating = false; +} + + +// Populate the noise tables and do most of the +// calculation necessary to determine terrain height. +void MapgenValleys::calculateNoise() +{ + //TimeTaker t("calculateNoise", NULL, PRECISION_MICRO); + + int x = node_min.X; + int y = node_min.Y - 1; + int z = node_min.Z; + + //TimeTaker tcn("actualNoise"); + + noise_inter_valley_slope->perlinMap2D(x, z); + noise_rivers->perlinMap2D(x, z); + noise_terrain_height->perlinMap2D(x, z); + noise_valley_depth->perlinMap2D(x, z); + noise_valley_profile->perlinMap2D(x, z); + + noise_inter_valley_fill->perlinMap3D(x, y, z); + + //mapgen_profiler->avg("noisemaps", tcn.stop() / 1000.f); + + float heat_offset = 0.f; + float humidity_scale = 1.f; + + // Altitude chill tends to reduce the average heat. + if (use_altitude_chill) + heat_offset = 5.f; + + // River humidity tends to increase the humidity range. + if (humid_rivers) { + humidity_scale = 0.8f; + } + + for (s32 index = 0; index < csize.X * csize.Z; index++) { + m_bgen->heatmap[index] += heat_offset; + m_bgen->humidmap[index] *= humidity_scale; + } + + TerrainNoise tn; + + u32 index = 0; + for (tn.z = node_min.Z; tn.z <= node_max.Z; tn.z++) + for (tn.x = node_min.X; tn.x <= node_max.X; tn.x++, index++) { + // The parameters that we actually need to generate terrain + // are passed by address (and the return value). + tn.terrain_height = noise_terrain_height->result[index]; + // River noise is replaced with base terrain, which + // is basically the height of the water table. + tn.rivers = &noise_rivers->result[index]; + // Valley depth noise is replaced with the valley + // number that represents the height of terrain + // over rivers and is used to determine about + // how close a river is for humidity calculation. + tn.valley = &noise_valley_depth->result[index]; + tn.valley_profile = noise_valley_profile->result[index]; + // Slope noise is replaced by the calculated slope + // which is used to get terrain height in the slow + // method, to create sharper mountains. + tn.slope = &noise_inter_valley_slope->result[index]; + tn.inter_valley_fill = noise_inter_valley_fill->result[index]; + + // This is the actual terrain height. + float mount = terrainLevelFromNoise(&tn); + noise_terrain_height->result[index] = mount; + } +} + + +// This keeps us from having to maintain two similar sets of +// complicated code to determine ground level. +float MapgenValleys::terrainLevelFromNoise(TerrainNoise *tn) +{ + // The square function changes the behaviour of this noise: + // very often small, and sometimes very high. + float valley_d = MYSQUARE(*tn->valley); + + // valley_d is here because terrain is generally higher where valleys + // are deep (mountains). base represents the height of the + // rivers, most of the surface is above. + float base = tn->terrain_height + valley_d; + + // "river" represents the distance from the river, in arbitrary units. + float river = fabs(*tn->rivers) - river_size_factor; + + // Use the curve of the function 1-exp(-(x/a)^2) to model valleys. + // Making "a" vary (0 < a <= 1) changes the shape of the valleys. + // Try it with a geometry software ! + // (here x = "river" and a = valley_profile). + // "valley" represents the height of the terrain, from the rivers. + { + float t = river / tn->valley_profile; + *tn->valley = valley_d * (1.f - exp(- MYSQUARE(t))); + } + + // approximate height of the terrain at this point + float mount = base + *tn->valley; + + *tn->slope *= *tn->valley; + + // Rivers are placed where "river" is negative, so where the original + // noise value is close to zero. + // Base ground is returned as rivers since it's basically the water table. + *tn->rivers = base; + if (river < 0.f) { + // Use the the function -sqrt(1-x^2) which models a circle. + float depth; + { + float t = river / river_size_factor + 1; + depth = (river_depth_bed * sqrt(MYMAX(0, 1.f - MYSQUARE(t)))); + } + + // base - depth : height of the bottom of the river + // water_level - 3 : don't make rivers below 3 nodes under the surface + // We use three because that's as low as the swamp biomes go. + // There is no logical equivalent to this using rangelim. + mount = MYMIN(MYMAX(base - depth, (float)(water_level - 3)), mount); + + // Slope has no influence on rivers. + *tn->slope = 0.f; + } + + return mount; +} + + +// This avoids duplicating the code in terrainLevelFromNoise, adding +// only the final step of terrain generation without a noise map. +float MapgenValleys::adjustedTerrainLevelFromNoise(TerrainNoise *tn) +{ + float mount = terrainLevelFromNoise(tn); + s16 y_start = myround(mount); + + for (s16 y = y_start; y <= y_start + 1000; y++) { + float fill = NoisePerlin3D(&noise_inter_valley_fill->np, tn->x, y, tn->z, seed); + + if (fill * *tn->slope < y - mount) { + mount = MYMAX(y - 1, mount); + break; + } + } + + return mount; +} + + +int MapgenValleys::getSpawnLevelAtPoint(v2s16 p) +{ + // Check to make sure this isn't a request for a location in a river. + float rivers = NoisePerlin2D(&noise_rivers->np, p.X, p.Y, seed); + if (fabs(rivers) < river_size_factor) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + + s16 level_at_point = terrainLevelAtPoint(p.X, p.Y); + if (level_at_point <= water_level || + level_at_point > water_level + 32) + return MAX_MAP_GENERATION_LIMIT; // Unsuitable spawn point + + return level_at_point; +} + + +float MapgenValleys::terrainLevelAtPoint(s16 x, s16 z) +{ + TerrainNoise tn; + + float rivers = NoisePerlin2D(&noise_rivers->np, x, z, seed); + float valley = NoisePerlin2D(&noise_valley_depth->np, x, z, seed); + float inter_valley_slope = NoisePerlin2D(&noise_inter_valley_slope->np, x, z, seed); + + tn.x = x; + tn.z = z; + tn.terrain_height = NoisePerlin2D(&noise_terrain_height->np, x, z, seed); + tn.rivers = &rivers; + tn.valley = &valley; + tn.valley_profile = NoisePerlin2D(&noise_valley_profile->np, x, z, seed); + tn.slope = &inter_valley_slope; + tn.inter_valley_fill = 0.f; + + return adjustedTerrainLevelFromNoise(&tn); +} + + +int MapgenValleys::generateTerrain() +{ + // Raising this reduces the rate of evaporation. + static const float evaporation = 300.f; + // from the lua + static const float humidity_dropoff = 4.f; + // constant to convert altitude chill (compatible with lua) to heat + static const float alt_to_heat = 20.f; + // humidity reduction by altitude + static const float alt_to_humid = 10.f; + + MapNode n_air(CONTENT_AIR); + MapNode n_river_water(c_river_water_source); + MapNode n_stone(c_stone); + MapNode n_water(c_water_source); + + const v3s16 &em = vm->m_area.getExtent(); + s16 surface_max_y = -MAX_MAP_GENERATION_LIMIT; + u32 index_2d = 0; + + for (s16 z = node_min.Z; z <= node_max.Z; z++) + for (s16 x = node_min.X; x <= node_max.X; x++, index_2d++) { + float river_y = noise_rivers->result[index_2d]; + float surface_y = noise_terrain_height->result[index_2d]; + float slope = noise_inter_valley_slope->result[index_2d]; + float t_heat = m_bgen->heatmap[index_2d]; + + heightmap[index_2d] = -MAX_MAP_GENERATION_LIMIT; + + if (surface_y > surface_max_y) + surface_max_y = ceil(surface_y); + + if (humid_rivers) { + // Derive heat from (base) altitude. This will be most correct + // at rivers, since other surface heights may vary below. + if (use_altitude_chill && (surface_y > 0.f || river_y > 0.f)) + t_heat -= alt_to_heat * MYMAX(surface_y, river_y) / altitude_chill; + + // If humidity is low or heat is high, lower the water table. + float delta = m_bgen->humidmap[index_2d] - 50.f; + if (delta < 0.f) { + float t_evap = (t_heat - 32.f) / evaporation; + river_y += delta * MYMAX(t_evap, 0.08f); + } + } + + u32 index_3d = (z - node_min.Z) * zstride_1u1d + (x - node_min.X); + u32 index_data = vm->m_area.index(x, node_min.Y - 1, z); + + // Mapgens concern themselves with stone and water. + for (s16 y = node_min.Y - 1; y <= node_max.Y + 1; y++) { + if (vm->m_data[index_data].getContent() == CONTENT_IGNORE) { + float fill = noise_inter_valley_fill->result[index_3d]; + float surface_delta = (float)y - surface_y; + bool river = y + 1 < river_y; + + if (slope * fill > surface_delta) { + // ground + vm->m_data[index_data] = n_stone; + if (y > heightmap[index_2d]) + heightmap[index_2d] = y; + if (y > surface_max_y) + surface_max_y = y; + } else if (y <= water_level) { + // sea + vm->m_data[index_data] = n_water; + } else if (river) { + // river + vm->m_data[index_data] = n_river_water; + } else { // air + vm->m_data[index_data] = n_air; + } + } + + vm->m_area.add_y(em, index_data, 1); + index_3d += ystride; + } + + if (heightmap[index_2d] == -MAX_MAP_GENERATION_LIMIT) { + s16 surface_y_int = myround(surface_y); + if (surface_y_int > node_max.Y + 1 || surface_y_int < node_min.Y - 1) { + // If surface_y is outside the chunk, it's good enough. + heightmap[index_2d] = surface_y_int; + } else { + // If the ground is outside of this chunk, but surface_y + // is within the chunk, give a value outside. + heightmap[index_2d] = node_min.Y - 2; + } + } + + if (humid_rivers) { + // Use base ground (water table) in a riverbed, to + // avoid an unnatural rise in humidity. + float t_alt = MYMAX(noise_rivers->result[index_2d], (float)heightmap[index_2d]); + float humid = m_bgen->humidmap[index_2d]; + float water_depth = (t_alt - river_y) / humidity_dropoff; + humid *= 1.f + pow(0.5f, MYMAX(water_depth, 1.f)); + + // Reduce humidity with altitude (ignoring riverbeds). + // This is similar to the lua version's seawater adjustment, + // but doesn't increase the base humidity, which causes + // problems with the default biomes. + if (t_alt > 0.f) + humid -= alt_to_humid * t_alt / altitude_chill; + + m_bgen->humidmap[index_2d] = humid; + } + + // Assign the heat adjusted by any changed altitudes. + // The altitude will change about half the time. + if (use_altitude_chill) { + // ground height ignoring riverbeds + float t_alt = MYMAX(noise_rivers->result[index_2d], (float)heightmap[index_2d]); + if (humid_rivers && heightmap[index_2d] == (s16)myround(surface_y)) + // The altitude hasn't changed. Use the first result. + m_bgen->heatmap[index_2d] = t_heat; + else if (t_alt > 0.f) + m_bgen->heatmap[index_2d] -= alt_to_heat * t_alt / altitude_chill; + } + } + + return surface_max_y; +} + +void MapgenValleys::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); + + PseudoRandom ps(blockseed + 72202); + + MapNode n_air(CONTENT_AIR); + MapNode n_lava(c_lava_source); + MapNode n_water(c_river_water_source); + + const v3s16 &em = vm->m_area.getExtent(); + + // Cave blend distance near YMIN, YMAX + const float massive_cave_blend = 128.f; + // noise threshold for massive caves + const float massive_cave_threshold = 0.6f; + // mct: 1 = small rare caves, 0.5 1/3rd ground volume, 0 = 1/2 ground volume. + + float yblmin = -mapgen_limit + massive_cave_blend * 1.5f; + float yblmax = massive_cave_depth - massive_cave_blend * 1.5f; + bool made_a_big_one = false; + + // Cache the tcave values as they only vary by altitude. + if (node_max.Y <= massive_cave_depth) { + noise_massive_caves->perlinMap3D(node_min.X, node_min.Y - 1, node_min.Z); + + for (s16 y = node_min.Y - 1; y <= node_max.Y; y++) { + float tcave = massive_cave_threshold; + + if (y < yblmin) { + float t = (yblmin - y) / massive_cave_blend; + tcave += MYSQUARE(t); + } else if (y > yblmax) { + float t = (y - yblmax) / massive_cave_blend; + tcave += MYSQUARE(t); + } + + tcave_cache[y - node_min.Y + 1] = tcave; + } + } + + // lava_depth varies between one and ten as you approach + // the bottom of the world. + s16 lava_depth = ceil((lava_max_height - node_min.Y + 1) * 10.f / mapgen_limit); + // This allows random lava spawns to be less common at the surface. + s16 lava_chance = MYCUBE(lava_features_lim) * lava_depth; + // water_depth varies between ten and one on the way down. + s16 water_depth = ceil((mapgen_limit - abs(node_min.Y) + 1) * 10.f / mapgen_limit); + // This allows random water spawns to be more common at the surface. + s16 water_chance = MYCUBE(water_features_lim) * water_depth; + + // Reduce the odds of overflows even further. + if (node_max.Y > water_level) { + lava_chance /= 3; + water_chance /= 3; + } + + u32 index_2d = 0; + for (s16 z = node_min.Z; z <= node_max.Z; z++) + for (s16 x = node_min.X; x <= node_max.X; x++, index_2d++) { + Biome *biome = (Biome *)m_bmgr->getRaw(biomemap[index_2d]); + bool tunnel_air_above = false; + bool is_under_river = false; + bool underground = false; + u32 index_data = vm->m_area.index(x, node_max.Y, z); + u32 index_3d = (z - node_min.Z) * zstride_1d + csize.Y * ystride + (x - node_min.X); + + // Dig caves on down loop to check for air above. + // 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--, + index_3d -= ystride, + vm->m_area.add_y(em, index_data, -1)) { + + float terrain = noise_terrain_height->result[index_2d]; + + // Saves some time. + if (y > terrain + 10) + continue; + + if (y < terrain - 40) + underground = true; + + // Dig massive caves. + if (node_max.Y <= massive_cave_depth + && noise_massive_caves->result[index_3d] + > tcave_cache[y - node_min.Y + 1]) { + vm->m_data[index_data] = n_air; + made_a_big_one = true; + continue; + } + + content_t c = vm->m_data[index_data].getContent(); + // Detect river water to place riverbed nodes in tunnels + if (c == biome->c_river_water) + is_under_river = true; + + float d1 = contour(noise_cave1->result[index_3d]); + float d2 = contour(noise_cave2->result[index_3d]); + + if (d1 * d2 > cave_width && ndef->get(c).is_ground_content) { + // in a tunnel + vm->m_data[index_data] = n_air; + tunnel_air_above = true; + } else if (c == biome->c_filler || c == biome->c_stone) { + if (tunnel_air_above) { + // at the tunnel floor + s16 sr = ps.range(0, 39); + u32 j = index_data; + vm->m_area.add_y(em, j, 1); + + if (sr > terrain - y) { + // Put biome nodes in tunnels near the surface + if (is_under_river) + vm->m_data[index_data] = MapNode(biome->c_riverbed); + else if (underground) + vm->m_data[index_data] = MapNode(biome->c_filler); + else + vm->m_data[index_data] = MapNode(biome->c_top); + } else if (sr < 3 && underground) { + sr = abs(ps.next()); + if (lava_features_lim > 0 && y <= lava_max_height + && c == biome->c_stone && sr < lava_chance) + vm->m_data[j] = n_lava; + + sr -= lava_chance; + + // If sr < 0 then we should have already placed lava -- + // don't immediately dump water on it. + if (water_features_lim > 0 && y <= cave_water_max_height + && sr >= 0 && sr < water_chance) + vm->m_data[j] = n_water; + } + } + + tunnel_air_above = false; + underground = true; + } else { + tunnel_air_above = false; + } + } + } + + if (node_max.Y <= large_cave_depth && !made_a_big_one) { + u32 bruises_count = ps.range(0, 2); + for (u32 i = 0; i < bruises_count; i++) { + CavesRandomWalk cave(ndef, &gennotify, seed, water_level, + c_water_source, c_lava_source, lava_max_height); + + cave.makeCave(vm, node_min, node_max, &ps, true, max_stone_y, heightmap); + } + } +} diff --git a/src/mapgen/mapgen_valleys.h b/src/mapgen/mapgen_valleys.h new file mode 100644 index 000000000..7b5eb187d --- /dev/null +++ b/src/mapgen/mapgen_valleys.h @@ -0,0 +1,134 @@ +/* +Minetest Valleys C +Copyright (C) 2016-2017 Duane Robertson <duane@duanerobertson.com> +Copyright (C) 2016-2017 paramat + +Based on Valleys Mapgen by Gael de Sailly + (https://forum.minetest.net/viewtopic.php?f=9&t=11430) +and mapgen_v7 by kwolekr and paramat. + +Licensing changed by permission of Gael de Sailly. + +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. +*/ + +#pragma once + +#include "mapgen.h" + +////////////// Mapgen Valleys flags +#define MGVALLEYS_ALT_CHILL 0x01 +#define MGVALLEYS_HUMID_RIVERS 0x02 + +// Feed only one variable into these. +#define MYSQUARE(x) (x) * (x) +#define MYCUBE(x) (x) * (x) * (x) + +class BiomeManager; +class BiomeGenOriginal; + +// Global profiler +//class Profiler; +//extern Profiler *mapgen_profiler; + + +struct MapgenValleysParams : public MapgenParams { + u32 spflags = MGVALLEYS_HUMID_RIVERS | MGVALLEYS_ALT_CHILL; + s16 large_cave_depth = -33; + s16 massive_cave_depth = -256; // highest altitude of massive caves + u16 altitude_chill = 90; // The altitude at which temperature drops by 20C. + u16 lava_features = 0; // How often water will occur in caves. + u16 river_depth = 4; // How deep to carve river channels. + u16 river_size = 5; // How wide to make rivers. + u16 water_features = 0; // How often water will occur in caves. + float cave_width = 0.09f; + NoiseParams np_cave1; + NoiseParams np_cave2; + NoiseParams np_filler_depth; + NoiseParams np_inter_valley_fill; + NoiseParams np_inter_valley_slope; + NoiseParams np_rivers; + NoiseParams np_massive_caves; + NoiseParams np_terrain_height; + NoiseParams np_valley_depth; + NoiseParams np_valley_profile; + + MapgenValleysParams(); + ~MapgenValleysParams() = default; + + void readParams(const Settings *settings); + void writeParams(Settings *settings) const; +}; + +struct TerrainNoise { + s16 x; + s16 z; + float terrain_height; + float *rivers; + float *valley; + float valley_profile; + float *slope; + float inter_valley_fill; +}; + +class MapgenValleys : public MapgenBasic { +public: + + MapgenValleys(int mapgenid, MapgenValleysParams *params, EmergeManager *emerge); + ~MapgenValleys(); + + virtual MapgenType getType() const { return MAPGEN_VALLEYS; } + + virtual void makeChunk(BlockMakeData *data); + int getSpawnLevelAtPoint(v2s16 p); + + s16 large_cave_depth; + +private: + BiomeGenOriginal *m_bgen; + + bool humid_rivers; + bool use_altitude_chill; + float humidity_adjust; + s16 cave_water_max_height; + s16 lava_max_height; + + float altitude_chill; + s16 lava_features_lim; + s16 massive_cave_depth; + float river_depth_bed; + float river_size_factor; + float *tcave_cache; + s16 water_features_lim; + Noise *noise_inter_valley_fill; + Noise *noise_inter_valley_slope; + Noise *noise_rivers; + Noise *noise_cave1; + Noise *noise_cave2; + Noise *noise_massive_caves; + Noise *noise_terrain_height; + Noise *noise_valley_depth; + Noise *noise_valley_profile; + + float terrainLevelAtPoint(s16 x, s16 z); + + void calculateNoise(); + + virtual int generateTerrain(); + float terrainLevelFromNoise(TerrainNoise *tn); + float adjustedTerrainLevelFromNoise(TerrainNoise *tn); + + virtual void generateCaves(s16 max_stone_y, s16 large_cave_depth); +}; diff --git a/src/mapgen/mg_biome.cpp b/src/mapgen/mg_biome.cpp new file mode 100644 index 000000000..8dbb78e59 --- /dev/null +++ b/src/mapgen/mg_biome.cpp @@ -0,0 +1,238 @@ +/* +Minetest +Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2014-2017 paramat + +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 "mg_biome.h" +#include "mg_decoration.h" +#include "emerge.h" +#include "server.h" +#include "nodedef.h" +#include "map.h" //for MMVManip +#include "util/numeric.h" +#include "porting.h" +#include "settings.h" + + +/////////////////////////////////////////////////////////////////////////////// + + +BiomeManager::BiomeManager(Server *server) : + ObjDefManager(server, OBJDEF_BIOME) +{ + m_server = server; + + // Create default biome to be used in case none exist + Biome *b = new Biome; + + b->name = "Default"; + b->flags = 0; + b->depth_top = 0; + b->depth_filler = -MAX_MAP_GENERATION_LIMIT; + b->depth_water_top = 0; + b->depth_riverbed = 0; + b->y_min = -MAX_MAP_GENERATION_LIMIT; + b->y_max = MAX_MAP_GENERATION_LIMIT; + b->heat_point = 0.0; + b->humidity_point = 0.0; + + b->m_nodenames.emplace_back("mapgen_stone"); + b->m_nodenames.emplace_back("mapgen_stone"); + b->m_nodenames.emplace_back("mapgen_stone"); + b->m_nodenames.emplace_back("mapgen_water_source"); + b->m_nodenames.emplace_back("mapgen_water_source"); + b->m_nodenames.emplace_back("mapgen_river_water_source"); + b->m_nodenames.emplace_back("mapgen_stone"); + b->m_nodenames.emplace_back("ignore"); + m_ndef->pendNodeResolve(b); + + add(b); +} + + +void BiomeManager::clear() +{ + EmergeManager *emerge = m_server->getEmergeManager(); + + // Remove all dangling references in Decorations + DecorationManager *decomgr = emerge->decomgr; + for (size_t i = 0; i != decomgr->getNumObjects(); i++) { + Decoration *deco = (Decoration *)decomgr->getRaw(i); + deco->biomes.clear(); + } + + // Don't delete the first biome + for (size_t i = 1; i < m_objects.size(); i++) + delete (Biome *)m_objects[i]; + + m_objects.resize(1); +} + +//////////////////////////////////////////////////////////////////////////////// + + +void BiomeParamsOriginal::readParams(const Settings *settings) +{ + settings->getNoiseParams("mg_biome_np_heat", np_heat); + settings->getNoiseParams("mg_biome_np_heat_blend", np_heat_blend); + settings->getNoiseParams("mg_biome_np_humidity", np_humidity); + settings->getNoiseParams("mg_biome_np_humidity_blend", np_humidity_blend); +} + + +void BiomeParamsOriginal::writeParams(Settings *settings) const +{ + settings->setNoiseParams("mg_biome_np_heat", np_heat); + settings->setNoiseParams("mg_biome_np_heat_blend", np_heat_blend); + settings->setNoiseParams("mg_biome_np_humidity", np_humidity); + settings->setNoiseParams("mg_biome_np_humidity_blend", np_humidity_blend); +} + + +//////////////////////////////////////////////////////////////////////////////// + +BiomeGenOriginal::BiomeGenOriginal(BiomeManager *biomemgr, + BiomeParamsOriginal *params, v3s16 chunksize) +{ + m_bmgr = biomemgr; + m_params = params; + m_csize = chunksize; + + noise_heat = new Noise(¶ms->np_heat, + params->seed, m_csize.X, m_csize.Z); + noise_humidity = new Noise(¶ms->np_humidity, + params->seed, m_csize.X, m_csize.Z); + noise_heat_blend = new Noise(¶ms->np_heat_blend, + params->seed, m_csize.X, m_csize.Z); + noise_humidity_blend = new Noise(¶ms->np_humidity_blend, + params->seed, m_csize.X, m_csize.Z); + + heatmap = noise_heat->result; + humidmap = noise_humidity->result; + biomemap = new biome_t[m_csize.X * m_csize.Z]; +} + +BiomeGenOriginal::~BiomeGenOriginal() +{ + delete []biomemap; + + delete noise_heat; + delete noise_humidity; + delete noise_heat_blend; + delete noise_humidity_blend; +} + + +Biome *BiomeGenOriginal::calcBiomeAtPoint(v3s16 pos) const +{ + float heat = + NoisePerlin2D(&m_params->np_heat, pos.X, pos.Z, m_params->seed) + + NoisePerlin2D(&m_params->np_heat_blend, pos.X, pos.Z, m_params->seed); + float humidity = + NoisePerlin2D(&m_params->np_humidity, pos.X, pos.Z, m_params->seed) + + NoisePerlin2D(&m_params->np_humidity_blend, pos.X, pos.Z, m_params->seed); + + return calcBiomeFromNoise(heat, humidity, pos.Y); +} + + +void BiomeGenOriginal::calcBiomeNoise(v3s16 pmin) +{ + m_pmin = pmin; + + noise_heat->perlinMap2D(pmin.X, pmin.Z); + noise_humidity->perlinMap2D(pmin.X, pmin.Z); + noise_heat_blend->perlinMap2D(pmin.X, pmin.Z); + noise_humidity_blend->perlinMap2D(pmin.X, pmin.Z); + + for (s32 i = 0; i < m_csize.X * m_csize.Z; i++) { + noise_heat->result[i] += noise_heat_blend->result[i]; + noise_humidity->result[i] += noise_humidity_blend->result[i]; + } +} + + +biome_t *BiomeGenOriginal::getBiomes(s16 *heightmap) +{ + for (s32 i = 0; i != m_csize.X * m_csize.Z; i++) { + Biome *biome = calcBiomeFromNoise( + noise_heat->result[i], + noise_humidity->result[i], + heightmap[i]); + + biomemap[i] = biome->index; + } + + return biomemap; +} + + +Biome *BiomeGenOriginal::getBiomeAtPoint(v3s16 pos) const +{ + return getBiomeAtIndex( + (pos.Z - m_pmin.Z) * m_csize.X + (pos.X - m_pmin.X), + pos.Y); +} + + +Biome *BiomeGenOriginal::getBiomeAtIndex(size_t index, s16 y) const +{ + return calcBiomeFromNoise( + noise_heat->result[index], + noise_humidity->result[index], + y); +} + + +Biome *BiomeGenOriginal::calcBiomeFromNoise(float heat, float humidity, s16 y) const +{ + Biome *b, *biome_closest = NULL; + float dist_min = FLT_MAX; + + for (size_t i = 1; i < m_bmgr->getNumObjects(); i++) { + b = (Biome *)m_bmgr->getRaw(i); + if (!b || y > b->y_max || y < b->y_min) + continue; + + float d_heat = heat - b->heat_point; + float d_humidity = humidity - b->humidity_point; + float dist = (d_heat * d_heat) + + (d_humidity * d_humidity); + if (dist < dist_min) { + dist_min = dist; + biome_closest = b; + } + } + + return biome_closest ? biome_closest : (Biome *)m_bmgr->getRaw(BIOME_NONE); +} + + +//////////////////////////////////////////////////////////////////////////////// + +void Biome::resolveNodeNames() +{ + getIdFromNrBacklog(&c_top, "mapgen_stone", CONTENT_AIR); + getIdFromNrBacklog(&c_filler, "mapgen_stone", CONTENT_AIR); + getIdFromNrBacklog(&c_stone, "mapgen_stone", CONTENT_AIR); + getIdFromNrBacklog(&c_water_top, "mapgen_water_source", CONTENT_AIR); + getIdFromNrBacklog(&c_water, "mapgen_water_source", CONTENT_AIR); + getIdFromNrBacklog(&c_river_water, "mapgen_river_water_source", CONTENT_AIR); + getIdFromNrBacklog(&c_riverbed, "mapgen_stone", CONTENT_AIR); + getIdFromNrBacklog(&c_dust, "ignore", CONTENT_IGNORE); +} diff --git a/src/mapgen/mg_biome.h b/src/mapgen/mg_biome.h new file mode 100644 index 000000000..f45238f28 --- /dev/null +++ b/src/mapgen/mg_biome.h @@ -0,0 +1,230 @@ +/* +Minetest +Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2014-2017 paramat + +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. +*/ + +#pragma once + +#include "objdef.h" +#include "nodedef.h" +#include "noise.h" + +class Server; +class Settings; +class BiomeManager; + +//// +//// Biome +//// + +typedef u8 biome_t; + +#define BIOME_NONE ((biome_t)0) + +// TODO(hmmmm): Decide whether this is obsolete or will be used in the future +enum BiomeType { + BIOMETYPE_NORMAL, + BIOMETYPE_LIQUID, + BIOMETYPE_NETHER, + BIOMETYPE_AETHER, + BIOMETYPE_FLAT, +}; + +class Biome : public ObjDef, public NodeResolver { +public: + u32 flags; + + content_t c_top; + content_t c_filler; + content_t c_stone; + content_t c_water_top; + content_t c_water; + content_t c_river_water; + content_t c_riverbed; + content_t c_dust; + + s16 depth_top; + s16 depth_filler; + s16 depth_water_top; + s16 depth_riverbed; + + s16 y_min; + s16 y_max; + float heat_point; + float humidity_point; + + virtual void resolveNodeNames(); +}; + + +//// +//// BiomeGen +//// + +enum BiomeGenType { + BIOMEGEN_ORIGINAL, +}; + +struct BiomeParams { + virtual void readParams(const Settings *settings) = 0; + virtual void writeParams(Settings *settings) const = 0; + virtual ~BiomeParams() = default; + + s32 seed; +}; + +class BiomeGen { +public: + virtual ~BiomeGen() = default; + + virtual BiomeGenType getType() const = 0; + + // Calculates the biome at the exact position provided. This function can + // be called at any time, but may be less efficient than the latter methods, + // depending on implementation. + virtual Biome *calcBiomeAtPoint(v3s16 pos) const = 0; + + // Computes any intermediate results needed for biome generation. Must be + // called before using any of: getBiomes, getBiomeAtPoint, or getBiomeAtIndex. + // Calling this invalidates the previous results stored in biomemap. + virtual void calcBiomeNoise(v3s16 pmin) = 0; + + // Gets all biomes in current chunk using each corresponding element of + // heightmap as the y position, then stores the results by biome index in + // biomemap (also returned) + virtual biome_t *getBiomes(s16 *heightmap) = 0; + + // Gets a single biome at the specified position, which must be contained + // in the region formed by m_pmin and (m_pmin + m_csize - 1). + virtual Biome *getBiomeAtPoint(v3s16 pos) const = 0; + + // Same as above, but uses a raw numeric index correlating to the (x,z) position. + virtual Biome *getBiomeAtIndex(size_t index, s16 y) const = 0; + + // Result of calcBiomes bulk computation. + biome_t *biomemap = nullptr; + +protected: + BiomeManager *m_bmgr = nullptr; + v3s16 m_pmin; + v3s16 m_csize; +}; + + +//// +//// BiomeGen implementations +//// + +// +// Original biome algorithm (Whittaker's classification + surface height) +// + +struct BiomeParamsOriginal : public BiomeParams { + BiomeParamsOriginal() : + np_heat(50, 50, v3f(1000.0, 1000.0, 1000.0), 5349, 3, 0.5, 2.0), + np_humidity(50, 50, v3f(1000.0, 1000.0, 1000.0), 842, 3, 0.5, 2.0), + np_heat_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 13, 2, 1.0, 2.0), + np_humidity_blend(0, 1.5, v3f(8.0, 8.0, 8.0), 90003, 2, 1.0, 2.0) + { + } + + virtual void readParams(const Settings *settings); + virtual void writeParams(Settings *settings) const; + + NoiseParams np_heat; + NoiseParams np_humidity; + NoiseParams np_heat_blend; + NoiseParams np_humidity_blend; +}; + +class BiomeGenOriginal : public BiomeGen { +public: + BiomeGenOriginal(BiomeManager *biomemgr, + BiomeParamsOriginal *params, v3s16 chunksize); + virtual ~BiomeGenOriginal(); + + BiomeGenType getType() const { return BIOMEGEN_ORIGINAL; } + + Biome *calcBiomeAtPoint(v3s16 pos) const; + void calcBiomeNoise(v3s16 pmin); + + biome_t *getBiomes(s16 *heightmap); + Biome *getBiomeAtPoint(v3s16 pos) const; + Biome *getBiomeAtIndex(size_t index, s16 y) const; + + Biome *calcBiomeFromNoise(float heat, float humidity, s16 y) const; + + float *heatmap; + float *humidmap; + +private: + BiomeParamsOriginal *m_params; + + Noise *noise_heat; + Noise *noise_humidity; + Noise *noise_heat_blend; + Noise *noise_humidity_blend; +}; + + +//// +//// BiomeManager +//// + +class BiomeManager : public ObjDefManager { +public: + BiomeManager(Server *server); + virtual ~BiomeManager() = default; + + const char *getObjectTitle() const + { + return "biome"; + } + + static Biome *create(BiomeType type) + { + return new Biome; + } + + BiomeGen *createBiomeGen(BiomeGenType type, BiomeParams *params, v3s16 chunksize) + { + switch (type) { + case BIOMEGEN_ORIGINAL: + return new BiomeGenOriginal(this, + (BiomeParamsOriginal *)params, chunksize); + default: + return NULL; + } + } + + static BiomeParams *createBiomeParams(BiomeGenType type) + { + switch (type) { + case BIOMEGEN_ORIGINAL: + return new BiomeParamsOriginal; + default: + return NULL; + } + } + + virtual void clear(); + +private: + Server *m_server; + +}; diff --git a/src/mapgen/mg_decoration.cpp b/src/mapgen/mg_decoration.cpp new file mode 100644 index 000000000..2c2fbc647 --- /dev/null +++ b/src/mapgen/mg_decoration.cpp @@ -0,0 +1,377 @@ +/* +Minetest +Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2015-2017 paramat + +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 "mg_decoration.h" +#include "mg_schematic.h" +#include "mapgen.h" +#include "noise.h" +#include "map.h" +#include "log.h" +#include "util/numeric.h" +#include <algorithm> + + +FlagDesc flagdesc_deco[] = { + {"place_center_x", DECO_PLACE_CENTER_X}, + {"place_center_y", DECO_PLACE_CENTER_Y}, + {"place_center_z", DECO_PLACE_CENTER_Z}, + {"force_placement", DECO_FORCE_PLACEMENT}, + {"liquid_surface", DECO_LIQUID_SURFACE}, + {"all_floors", DECO_ALL_FLOORS}, + {"all_ceilings", DECO_ALL_CEILINGS}, + {NULL, 0} +}; + + +/////////////////////////////////////////////////////////////////////////////// + + +DecorationManager::DecorationManager(IGameDef *gamedef) : + ObjDefManager(gamedef, OBJDEF_DECORATION) +{ +} + + +size_t DecorationManager::placeAllDecos(Mapgen *mg, u32 blockseed, + v3s16 nmin, v3s16 nmax) +{ + size_t nplaced = 0; + + for (size_t i = 0; i != m_objects.size(); i++) { + Decoration *deco = (Decoration *)m_objects[i]; + if (!deco) + continue; + + nplaced += deco->placeDeco(mg, blockseed, nmin, nmax); + blockseed++; + } + + return nplaced; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +void Decoration::resolveNodeNames() +{ + getIdsFromNrBacklog(&c_place_on); + getIdsFromNrBacklog(&c_spawnby); +} + + +bool Decoration::canPlaceDecoration(MMVManip *vm, v3s16 p) +{ + // Check if the decoration can be placed on this node + u32 vi = vm->m_area.index(p); + if (!CONTAINS(c_place_on, vm->m_data[vi].getContent())) + return false; + + // Don't continue if there are no spawnby constraints + if (nspawnby == -1) + return true; + + int nneighs = 0; + static const v3s16 dirs[16] = { + v3s16( 0, 0, 1), + v3s16( 0, 0, -1), + v3s16( 1, 0, 0), + v3s16(-1, 0, 0), + v3s16( 1, 0, 1), + v3s16(-1, 0, 1), + v3s16(-1, 0, -1), + v3s16( 1, 0, -1), + + v3s16( 0, 1, 1), + v3s16( 0, 1, -1), + v3s16( 1, 1, 0), + v3s16(-1, 1, 0), + v3s16( 1, 1, 1), + v3s16(-1, 1, 1), + v3s16(-1, 1, -1), + v3s16( 1, 1, -1) + }; + + // Check these 16 neighbouring nodes for enough spawnby nodes + for (size_t i = 0; i != ARRLEN(dirs); i++) { + u32 index = vm->m_area.index(p + dirs[i]); + if (!vm->m_area.contains(index)) + continue; + + if (CONTAINS(c_spawnby, vm->m_data[index].getContent())) + nneighs++; + } + + if (nneighs < nspawnby) + return false; + + return true; +} + + +size_t Decoration::placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) +{ + PcgRandom ps(blockseed + 53); + int carea_size = nmax.X - nmin.X + 1; + + // Divide area into parts + // If chunksize is changed it may no longer be divisable by sidelen + if (carea_size % sidelen) + sidelen = carea_size; + + s16 divlen = carea_size / sidelen; + int area = sidelen * sidelen; + + for (s16 z0 = 0; z0 < divlen; z0++) + for (s16 x0 = 0; x0 < divlen; x0++) { + v2s16 p2d_center( // Center position of part of division + nmin.X + sidelen / 2 + sidelen * x0, + nmin.Z + sidelen / 2 + sidelen * z0 + ); + v2s16 p2d_min( // Minimum edge of part of division + nmin.X + sidelen * x0, + nmin.Z + sidelen * z0 + ); + v2s16 p2d_max( // Maximum edge of part of division + nmin.X + sidelen + sidelen * x0 - 1, + nmin.Z + sidelen + sidelen * z0 - 1 + ); + + // Amount of decorations + float nval = (flags & DECO_USE_NOISE) ? + NoisePerlin2D(&np, p2d_center.X, p2d_center.Y, mapseed) : + fill_ratio; + u32 deco_count = 0; + float deco_count_f = (float)area * nval; + if (deco_count_f >= 1.f) { + deco_count = deco_count_f; + } else if (deco_count_f > 0.f) { + // For low density decorations calculate a chance for 1 decoration + if (ps.range(1000) <= deco_count_f * 1000.f) + deco_count = 1; + } + + for (u32 i = 0; i < deco_count; i++) { + s16 x = ps.range(p2d_min.X, p2d_max.X); + s16 z = ps.range(p2d_min.Y, p2d_max.Y); + int mapindex = carea_size * (z - nmin.Z) + (x - nmin.X); + + if ((flags & DECO_ALL_FLOORS) || + (flags & DECO_ALL_CEILINGS)) { + // All-surfaces decorations + // Check biome of column + if (mg->biomemap && !biomes.empty()) { + std::unordered_set<u8>::const_iterator iter = + biomes.find(mg->biomemap[mapindex]); + if (iter == biomes.end()) + continue; + } + + // Get all floors and ceilings in node column + u16 size = (nmax.Y - nmin.Y + 1) / 2; + s16 floors[size]; + s16 ceilings[size]; + u16 num_floors = 0; + u16 num_ceilings = 0; + + mg->getSurfaces(v2s16(x, z), nmin.Y, nmax.Y, + floors, ceilings, &num_floors, &num_ceilings); + + if ((flags & DECO_ALL_FLOORS) && num_floors > 0) { + // Floor decorations + for (u16 fi = 0; fi < num_floors; fi++) { + s16 y = floors[fi]; + if (y < y_min || y > y_max) + continue; + + v3s16 pos(x, y, z); + if (generate(mg->vm, &ps, pos, false)) + mg->gennotify.addEvent( + GENNOTIFY_DECORATION, pos, index); + } + } + + if ((flags & DECO_ALL_CEILINGS) && num_ceilings > 0) { + // Ceiling decorations + for (u16 ci = 0; ci < num_ceilings; ci++) { + s16 y = ceilings[ci]; + if (y < y_min || y > y_max) + continue; + + v3s16 pos(x, y, z); + if (generate(mg->vm, &ps, pos, true)) + mg->gennotify.addEvent( + GENNOTIFY_DECORATION, pos, index); + } + } + } else { // Heightmap decorations + s16 y = -MAX_MAP_GENERATION_LIMIT; + if (flags & DECO_LIQUID_SURFACE) + y = mg->findLiquidSurface(v2s16(x, z), nmin.Y, nmax.Y); + else if (mg->heightmap) + y = mg->heightmap[mapindex]; + else + y = mg->findGroundLevel(v2s16(x, z), nmin.Y, nmax.Y); + + if (y < y_min || y > y_max || y < nmin.Y || y > nmax.Y) + continue; + + if (mg->biomemap && !biomes.empty()) { + std::unordered_set<u8>::const_iterator iter = + biomes.find(mg->biomemap[mapindex]); + if (iter == biomes.end()) + continue; + } + + v3s16 pos(x, y, z); + if (generate(mg->vm, &ps, pos, false)) + mg->gennotify.addEvent(GENNOTIFY_DECORATION, pos, index); + } + } + } + + return 0; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +void DecoSimple::resolveNodeNames() +{ + Decoration::resolveNodeNames(); + getIdsFromNrBacklog(&c_decos); +} + + +size_t DecoSimple::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling) +{ + // Don't bother if there aren't any decorations to place + if (c_decos.empty()) + return 0; + + if (!canPlaceDecoration(vm, p)) + return 0; + + // Check for placement outside the voxelmanip volume + if (ceiling) { + // Ceiling decorations + // 'place offset y' is inverted + if (p.Y - place_offset_y - std::max(deco_height, deco_height_max) < + vm->m_area.MinEdge.Y) + return 0; + + if (p.Y - 1 - place_offset_y > vm->m_area.MaxEdge.Y) + return 0; + + } else { // Heightmap and floor decorations + if (p.Y + place_offset_y + std::max(deco_height, deco_height_max) > + vm->m_area.MaxEdge.Y) + return 0; + + if (p.Y + 1 + place_offset_y < vm->m_area.MinEdge.Y) + return 0; + } + + content_t c_place = c_decos[pr->range(0, c_decos.size() - 1)]; + s16 height = (deco_height_max > 0) ? + pr->range(deco_height, deco_height_max) : deco_height; + u8 param2 = (deco_param2_max > 0) ? + pr->range(deco_param2, deco_param2_max) : deco_param2; + bool force_placement = (flags & DECO_FORCE_PLACEMENT); + + const v3s16 &em = vm->m_area.getExtent(); + u32 vi = vm->m_area.index(p); + + if (ceiling) { + // Ceiling decorations + // 'place offset y' is inverted + vm->m_area.add_y(em, vi, -place_offset_y); + + for (int i = 0; i < height; i++) { + vm->m_area.add_y(em, vi, -1); + content_t c = vm->m_data[vi].getContent(); + if (c != CONTENT_AIR && c != CONTENT_IGNORE && !force_placement) + break; + + vm->m_data[vi] = MapNode(c_place, 0, param2); + } + } else { // Heightmap and floor decorations + vm->m_area.add_y(em, vi, place_offset_y); + + for (int i = 0; i < height; i++) { + vm->m_area.add_y(em, vi, 1); + content_t c = vm->m_data[vi].getContent(); + if (c != CONTENT_AIR && c != CONTENT_IGNORE && !force_placement) + break; + + vm->m_data[vi] = MapNode(c_place, 0, param2); + } + } + + return 1; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +size_t DecoSchematic::generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling) +{ + // Schematic could have been unloaded but not the decoration + // In this case generate() does nothing (but doesn't *fail*) + if (schematic == NULL) + return 0; + + if (!canPlaceDecoration(vm, p)) + return 0; + + if (flags & DECO_PLACE_CENTER_Y) { + p.Y -= (schematic->size.Y - 1) / 2; + } else { + // Only apply 'place offset y' if not 'deco place center y' + if (ceiling) + // Shift down so that schematic top layer is level with ceiling + // 'place offset y' is inverted + p.Y -= (place_offset_y + schematic->size.Y - 1); + else + p.Y += place_offset_y; + } + + // Check schematic top and base are in voxelmanip + if (p.Y + schematic->size.Y - 1 > vm->m_area.MaxEdge.Y) + return 0; + + if (p.Y < vm->m_area.MinEdge.Y) + return 0; + + if (flags & DECO_PLACE_CENTER_X) + p.X -= (schematic->size.X - 1) / 2; + if (flags & DECO_PLACE_CENTER_Z) + p.Z -= (schematic->size.Z - 1) / 2; + + Rotation rot = (rotation == ROTATE_RAND) ? + (Rotation)pr->range(ROTATE_0, ROTATE_270) : rotation; + bool force_placement = (flags & DECO_FORCE_PLACEMENT); + + schematic->blitToVManip(vm, p, rot, force_placement); + + return 1; +} diff --git a/src/mapgen/mg_decoration.h b/src/mapgen/mg_decoration.h new file mode 100644 index 000000000..1ca632f25 --- /dev/null +++ b/src/mapgen/mg_decoration.h @@ -0,0 +1,136 @@ +/* +Minetest +Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2015-2017 paramat + +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. +*/ + +#pragma once + +#include <unordered_set> +#include "objdef.h" +#include "noise.h" +#include "nodedef.h" + +class Mapgen; +class MMVManip; +class PcgRandom; +class Schematic; + +enum DecorationType { + DECO_SIMPLE, + DECO_SCHEMATIC, + DECO_LSYSTEM +}; + +#define DECO_PLACE_CENTER_X 0x01 +#define DECO_PLACE_CENTER_Y 0x02 +#define DECO_PLACE_CENTER_Z 0x04 +#define DECO_USE_NOISE 0x08 +#define DECO_FORCE_PLACEMENT 0x10 +#define DECO_LIQUID_SURFACE 0x20 +#define DECO_ALL_FLOORS 0x40 +#define DECO_ALL_CEILINGS 0x80 + +extern FlagDesc flagdesc_deco[]; + + +class Decoration : public ObjDef, public NodeResolver { +public: + Decoration() = default; + virtual ~Decoration() = default; + + virtual void resolveNodeNames(); + + bool canPlaceDecoration(MMVManip *vm, v3s16 p); + size_t placeDeco(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); + + virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling) = 0; + + u32 flags = 0; + int mapseed = 0; + std::vector<content_t> c_place_on; + s16 sidelen = 1; + s16 y_min; + s16 y_max; + float fill_ratio = 0.0f; + NoiseParams np; + std::vector<content_t> c_spawnby; + s16 nspawnby; + s16 place_offset_y = 0; + + std::unordered_set<u8> biomes; +}; + + +class DecoSimple : public Decoration { +public: + virtual void resolveNodeNames(); + virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling); + + std::vector<content_t> c_decos; + s16 deco_height; + s16 deco_height_max; + u8 deco_param2; + u8 deco_param2_max; +}; + + +class DecoSchematic : public Decoration { +public: + DecoSchematic() = default; + + virtual size_t generate(MMVManip *vm, PcgRandom *pr, v3s16 p, bool ceiling); + + Rotation rotation; + Schematic *schematic = nullptr; +}; + + +/* +class DecoLSystem : public Decoration { +public: + virtual void generate(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); +}; +*/ + + +class DecorationManager : public ObjDefManager { +public: + DecorationManager(IGameDef *gamedef); + virtual ~DecorationManager() = default; + + const char *getObjectTitle() const + { + return "decoration"; + } + + static Decoration *create(DecorationType type) + { + switch (type) { + case DECO_SIMPLE: + return new DecoSimple; + case DECO_SCHEMATIC: + return new DecoSchematic; + //case DECO_LSYSTEM: + // return new DecoLSystem; + default: + return NULL; + } + } + + size_t placeAllDecos(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); +}; diff --git a/src/mapgen/mg_ore.cpp b/src/mapgen/mg_ore.cpp new file mode 100644 index 000000000..979135ed4 --- /dev/null +++ b/src/mapgen/mg_ore.cpp @@ -0,0 +1,483 @@ +/* +Minetest +Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2015-2017 paramat + +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 "mg_ore.h" +#include "mapgen.h" +#include "noise.h" +#include "map.h" +#include "log.h" +#include "util/numeric.h" +#include <algorithm> + + +FlagDesc flagdesc_ore[] = { + {"absheight", OREFLAG_ABSHEIGHT}, // Non-functional + {"puff_cliffs", OREFLAG_PUFF_CLIFFS}, + {"puff_additive_composition", OREFLAG_PUFF_ADDITIVE}, + {NULL, 0} +}; + + +/////////////////////////////////////////////////////////////////////////////// + + +OreManager::OreManager(IGameDef *gamedef) : + ObjDefManager(gamedef, OBJDEF_ORE) +{ +} + + +size_t OreManager::placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) +{ + size_t nplaced = 0; + + for (size_t i = 0; i != m_objects.size(); i++) { + Ore *ore = (Ore *)m_objects[i]; + if (!ore) + continue; + + nplaced += ore->placeOre(mg, blockseed, nmin, nmax); + blockseed++; + } + + return nplaced; +} + + +void OreManager::clear() +{ + for (ObjDef *object : m_objects) { + Ore *ore = (Ore *) object; + delete ore; + } + m_objects.clear(); +} + + +/////////////////////////////////////////////////////////////////////////////// + + +Ore::~Ore() +{ + delete noise; +} + + +void Ore::resolveNodeNames() +{ + getIdFromNrBacklog(&c_ore, "", CONTENT_AIR); + getIdsFromNrBacklog(&c_wherein); +} + + +size_t Ore::placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax) +{ + if (nmin.Y > y_max || nmax.Y < y_min) + return 0; + + int actual_ymin = MYMAX(nmin.Y, y_min); + int actual_ymax = MYMIN(nmax.Y, y_max); + if (clust_size >= actual_ymax - actual_ymin + 1) + return 0; + + nmin.Y = actual_ymin; + nmax.Y = actual_ymax; + generate(mg->vm, mg->seed, blockseed, nmin, nmax, mg->biomemap); + + return 1; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +void OreScatter::generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap) +{ + PcgRandom pr(blockseed); + MapNode n_ore(c_ore, 0, ore_param2); + + u32 sizex = (nmax.X - nmin.X + 1); + u32 volume = (nmax.X - nmin.X + 1) * + (nmax.Y - nmin.Y + 1) * + (nmax.Z - nmin.Z + 1); + u32 csize = clust_size; + u32 cvolume = csize * csize * csize; + u32 nclusters = volume / clust_scarcity; + + for (u32 i = 0; i != nclusters; i++) { + int x0 = pr.range(nmin.X, nmax.X - csize + 1); + int y0 = pr.range(nmin.Y, nmax.Y - csize + 1); + int z0 = pr.range(nmin.Z, nmax.Z - csize + 1); + + if ((flags & OREFLAG_USE_NOISE) && + (NoisePerlin3D(&np, x0, y0, z0, mapseed) < nthresh)) + continue; + + if (biomemap && !biomes.empty()) { + u32 index = sizex * (z0 - nmin.Z) + (x0 - nmin.X); + std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]); + if (it == biomes.end()) + continue; + } + + for (u32 z1 = 0; z1 != csize; z1++) + for (u32 y1 = 0; y1 != csize; y1++) + for (u32 x1 = 0; x1 != csize; x1++) { + if (pr.range(1, cvolume) > clust_num_ores) + continue; + + u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1); + if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) + continue; + + vm->m_data[i] = n_ore; + } + } +} + + +/////////////////////////////////////////////////////////////////////////////// + + +void OreSheet::generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap) +{ + PcgRandom pr(blockseed + 4234); + MapNode n_ore(c_ore, 0, ore_param2); + + u16 max_height = column_height_max; + int y_start_min = nmin.Y + max_height; + int y_start_max = nmax.Y - max_height; + + int y_start = y_start_min < y_start_max ? + pr.range(y_start_min, y_start_max) : + (y_start_min + y_start_max) / 2; + + if (!noise) { + int sx = nmax.X - nmin.X + 1; + int sz = nmax.Z - nmin.Z + 1; + noise = new Noise(&np, 0, sx, sz); + } + noise->seed = mapseed + y_start; + noise->perlinMap2D(nmin.X, nmin.Z); + + size_t index = 0; + for (int z = nmin.Z; z <= nmax.Z; z++) + for (int x = nmin.X; x <= nmax.X; x++, index++) { + float noiseval = noise->result[index]; + if (noiseval < nthresh) + continue; + + if (biomemap && !biomes.empty()) { + std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]); + if (it == biomes.end()) + continue; + } + + u16 height = pr.range(column_height_min, column_height_max); + int ymidpoint = y_start + noiseval; + int y0 = MYMAX(nmin.Y, ymidpoint - height * (1 - column_midpoint_factor)); + int y1 = MYMIN(nmax.Y, y0 + height - 1); + + for (int y = y0; y <= y1; y++) { + u32 i = vm->m_area.index(x, y, z); + if (!vm->m_area.contains(i)) + continue; + if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) + continue; + + vm->m_data[i] = n_ore; + } + } +} + + +/////////////////////////////////////////////////////////////////////////////// + + +OrePuff::~OrePuff() +{ + delete noise_puff_top; + delete noise_puff_bottom; +} + + +void OrePuff::generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap) +{ + PcgRandom pr(blockseed + 4234); + MapNode n_ore(c_ore, 0, ore_param2); + + int y_start = pr.range(nmin.Y, nmax.Y); + + if (!noise) { + int sx = nmax.X - nmin.X + 1; + int sz = nmax.Z - nmin.Z + 1; + noise = new Noise(&np, 0, sx, sz); + noise_puff_top = new Noise(&np_puff_top, 0, sx, sz); + noise_puff_bottom = new Noise(&np_puff_bottom, 0, sx, sz); + } + + noise->seed = mapseed + y_start; + noise->perlinMap2D(nmin.X, nmin.Z); + bool noise_generated = false; + + size_t index = 0; + for (int z = nmin.Z; z <= nmax.Z; z++) + for (int x = nmin.X; x <= nmax.X; x++, index++) { + float noiseval = noise->result[index]; + if (noiseval < nthresh) + continue; + + if (biomemap && !biomes.empty()) { + std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]); + if (it == biomes.end()) + continue; + } + + if (!noise_generated) { + noise_generated = true; + noise_puff_top->perlinMap2D(nmin.X, nmin.Z); + noise_puff_bottom->perlinMap2D(nmin.X, nmin.Z); + } + + float ntop = noise_puff_top->result[index]; + float nbottom = noise_puff_bottom->result[index]; + + if (!(flags & OREFLAG_PUFF_CLIFFS)) { + float ndiff = noiseval - nthresh; + if (ndiff < 1.0f) { + ntop *= ndiff; + nbottom *= ndiff; + } + } + + int ymid = y_start; + int y0 = ymid - nbottom; + int y1 = ymid + ntop; + + if ((flags & OREFLAG_PUFF_ADDITIVE) && (y0 > y1)) + SWAP(int, y0, y1); + + for (int y = y0; y <= y1; y++) { + u32 i = vm->m_area.index(x, y, z); + if (!vm->m_area.contains(i)) + continue; + if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) + continue; + + vm->m_data[i] = n_ore; + } + } +} + + +/////////////////////////////////////////////////////////////////////////////// + + +void OreBlob::generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap) +{ + PcgRandom pr(blockseed + 2404); + MapNode n_ore(c_ore, 0, ore_param2); + + u32 sizex = (nmax.X - nmin.X + 1); + u32 volume = (nmax.X - nmin.X + 1) * + (nmax.Y - nmin.Y + 1) * + (nmax.Z - nmin.Z + 1); + u32 csize = clust_size; + u32 nblobs = volume / clust_scarcity; + + if (!noise) + noise = new Noise(&np, mapseed, csize, csize, csize); + + for (u32 i = 0; i != nblobs; i++) { + int x0 = pr.range(nmin.X, nmax.X - csize + 1); + int y0 = pr.range(nmin.Y, nmax.Y - csize + 1); + int z0 = pr.range(nmin.Z, nmax.Z - csize + 1); + + if (biomemap && !biomes.empty()) { + u32 bmapidx = sizex * (z0 - nmin.Z) + (x0 - nmin.X); + std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[bmapidx]); + if (it == biomes.end()) + continue; + } + + bool noise_generated = false; + noise->seed = blockseed + i; + + size_t index = 0; + for (u32 z1 = 0; z1 != csize; z1++) + for (u32 y1 = 0; y1 != csize; y1++) + for (u32 x1 = 0; x1 != csize; x1++, index++) { + u32 i = vm->m_area.index(x0 + x1, y0 + y1, z0 + z1); + if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) + continue; + + // Lazily generate noise only if there's a chance of ore being placed + // This simple optimization makes calls 6x faster on average + if (!noise_generated) { + noise_generated = true; + noise->perlinMap3D(x0, y0, z0); + } + + float noiseval = noise->result[index]; + + float xdist = (s32)x1 - (s32)csize / 2; + float ydist = (s32)y1 - (s32)csize / 2; + float zdist = (s32)z1 - (s32)csize / 2; + + noiseval -= (sqrt(xdist * xdist + ydist * ydist + zdist * zdist) / csize); + + if (noiseval < nthresh) + continue; + + vm->m_data[i] = n_ore; + } + } +} + + +/////////////////////////////////////////////////////////////////////////////// + + +OreVein::~OreVein() +{ + delete noise2; +} + + +void OreVein::generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap) +{ + PcgRandom pr(blockseed + 520); + MapNode n_ore(c_ore, 0, ore_param2); + + u32 sizex = (nmax.X - nmin.X + 1); + + if (!noise) { + int sx = nmax.X - nmin.X + 1; + int sy = nmax.Y - nmin.Y + 1; + int sz = nmax.Z - nmin.Z + 1; + noise = new Noise(&np, mapseed, sx, sy, sz); + noise2 = new Noise(&np, mapseed + 436, sx, sy, sz); + } + bool noise_generated = false; + + size_t index = 0; + for (int z = nmin.Z; z <= nmax.Z; z++) + for (int y = nmin.Y; y <= nmax.Y; y++) + for (int x = nmin.X; x <= nmax.X; x++, index++) { + u32 i = vm->m_area.index(x, y, z); + if (!vm->m_area.contains(i)) + continue; + if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) + continue; + + if (biomemap && !biomes.empty()) { + u32 bmapidx = sizex * (z - nmin.Z) + (x - nmin.X); + std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[bmapidx]); + if (it == biomes.end()) + continue; + } + + // Same lazy generation optimization as in OreBlob + if (!noise_generated) { + noise_generated = true; + noise->perlinMap3D(nmin.X, nmin.Y, nmin.Z); + noise2->perlinMap3D(nmin.X, nmin.Y, nmin.Z); + } + + // randval ranges from -1..1 + float randval = (float)pr.next() / (pr.RANDOM_RANGE / 2) - 1.f; + float noiseval = contour(noise->result[index]); + float noiseval2 = contour(noise2->result[index]); + if (noiseval * noiseval2 + randval * random_factor < nthresh) + continue; + + vm->m_data[i] = n_ore; + } +} + + +/////////////////////////////////////////////////////////////////////////////// + + +OreStratum::~OreStratum() +{ + delete noise_stratum_thickness; +} + + +void OreStratum::generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap) +{ + PcgRandom pr(blockseed + 4234); + MapNode n_ore(c_ore, 0, ore_param2); + + if (flags & OREFLAG_USE_NOISE) { + if (!(noise && noise_stratum_thickness)) { + int sx = nmax.X - nmin.X + 1; + int sz = nmax.Z - nmin.Z + 1; + noise = new Noise(&np, 0, sx, sz); + noise_stratum_thickness = new Noise(&np_stratum_thickness, 0, sx, sz); + } + noise->perlinMap2D(nmin.X, nmin.Z); + noise_stratum_thickness->perlinMap2D(nmin.X, nmin.Z); + } + + size_t index = 0; + + for (int z = nmin.Z; z <= nmax.Z; z++) + for (int x = nmin.X; x <= nmax.X; x++, index++) { + if (biomemap && !biomes.empty()) { + std::unordered_set<u8>::const_iterator it = biomes.find(biomemap[index]); + if (it == biomes.end()) + continue; + } + + int y0; + int y1; + + if (flags & OREFLAG_USE_NOISE) { + float nmid = noise->result[index]; + float nhalfthick = noise_stratum_thickness->result[index] / 2.0f; + y0 = MYMAX(nmin.Y, nmid - nhalfthick); + y1 = MYMIN(nmax.Y, nmid + nhalfthick); + } else { + y0 = nmin.Y; + y1 = nmax.Y; + } + + for (int y = y0; y <= y1; y++) { + if (pr.range(1, clust_scarcity) != 1) + continue; + + u32 i = vm->m_area.index(x, y, z); + if (!vm->m_area.contains(i)) + continue; + if (!CONTAINS(c_wherein, vm->m_data[i].getContent())) + continue; + + vm->m_data[i] = n_ore; + } + } +} diff --git a/src/mapgen/mg_ore.h b/src/mapgen/mg_ore.h new file mode 100644 index 000000000..e715f348b --- /dev/null +++ b/src/mapgen/mg_ore.h @@ -0,0 +1,183 @@ +/* +Minetest +Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2015-2017 paramat + +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. +*/ + +#pragma once + +#include <unordered_set> +#include "objdef.h" +#include "noise.h" +#include "nodedef.h" + +class Noise; +class Mapgen; +class MMVManip; + +/////////////////// Ore generation flags + +#define OREFLAG_ABSHEIGHT 0x01 // Non-functional but kept to not break flags +#define OREFLAG_PUFF_CLIFFS 0x02 +#define OREFLAG_PUFF_ADDITIVE 0x04 +#define OREFLAG_USE_NOISE 0x08 + +enum OreType { + ORE_SCATTER, + ORE_SHEET, + ORE_PUFF, + ORE_BLOB, + ORE_VEIN, + ORE_STRATUM, +}; + +extern FlagDesc flagdesc_ore[]; + +class Ore : public ObjDef, public NodeResolver { +public: + static const bool NEEDS_NOISE = false; + + content_t c_ore; // the node to place + std::vector<content_t> c_wherein; // the nodes to be placed in + u32 clust_scarcity; // ore cluster has a 1-in-clust_scarcity chance of appearing at a node + s16 clust_num_ores; // how many ore nodes are in a chunk + s16 clust_size; // how large (in nodes) a chunk of ore is + s16 y_min; + s16 y_max; + u8 ore_param2; // to set node-specific attributes + u32 flags = 0; // attributes for this ore + float nthresh; // threshold for noise at which an ore is placed + NoiseParams np; // noise for distribution of clusters (NULL for uniform scattering) + Noise *noise = nullptr; + std::unordered_set<u8> biomes; + + Ore() = default;; + virtual ~Ore(); + + virtual void resolveNodeNames(); + + size_t placeOre(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap) = 0; +}; + +class OreScatter : public Ore { +public: + static const bool NEEDS_NOISE = false; + + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap); +}; + +class OreSheet : public Ore { +public: + static const bool NEEDS_NOISE = true; + + u16 column_height_min; + u16 column_height_max; + float column_midpoint_factor; + + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap); +}; + +class OrePuff : public Ore { +public: + static const bool NEEDS_NOISE = true; + + NoiseParams np_puff_top; + NoiseParams np_puff_bottom; + Noise *noise_puff_top = nullptr; + Noise *noise_puff_bottom = nullptr; + + OrePuff() = default; + virtual ~OrePuff(); + + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap); +}; + +class OreBlob : public Ore { +public: + static const bool NEEDS_NOISE = true; + + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap); +}; + +class OreVein : public Ore { +public: + static const bool NEEDS_NOISE = true; + + float random_factor; + Noise *noise2 = nullptr; + + OreVein() = default; + virtual ~OreVein(); + + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap); +}; + +class OreStratum : public Ore { +public: + static const bool NEEDS_NOISE = false; + + NoiseParams np_stratum_thickness; + Noise *noise_stratum_thickness = nullptr; + + OreStratum() = default; + virtual ~OreStratum(); + + virtual void generate(MMVManip *vm, int mapseed, u32 blockseed, + v3s16 nmin, v3s16 nmax, u8 *biomemap); +}; + +class OreManager : public ObjDefManager { +public: + OreManager(IGameDef *gamedef); + virtual ~OreManager() = default; + + const char *getObjectTitle() const + { + return "ore"; + } + + static Ore *create(OreType type) + { + switch (type) { + case ORE_SCATTER: + return new OreScatter; + case ORE_SHEET: + return new OreSheet; + case ORE_PUFF: + return new OrePuff; + case ORE_BLOB: + return new OreBlob; + case ORE_VEIN: + return new OreVein; + case ORE_STRATUM: + return new OreStratum; + default: + return nullptr; + } + } + + void clear(); + + size_t placeAllOres(Mapgen *mg, u32 blockseed, v3s16 nmin, v3s16 nmax); +}; diff --git a/src/mapgen/mg_schematic.cpp b/src/mapgen/mg_schematic.cpp new file mode 100644 index 000000000..8874abd42 --- /dev/null +++ b/src/mapgen/mg_schematic.cpp @@ -0,0 +1,578 @@ +/* +Minetest +Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2015-2017 paramat + +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 <fstream> +#include <typeinfo> +#include "mg_schematic.h" +#include "server.h" +#include "mapgen.h" +#include "emerge.h" +#include "map.h" +#include "mapblock.h" +#include "log.h" +#include "util/numeric.h" +#include "util/serialize.h" +#include "serialization.h" +#include "filesys.h" +#include "voxelalgorithms.h" + +/////////////////////////////////////////////////////////////////////////////// + + +SchematicManager::SchematicManager(Server *server) : + ObjDefManager(server, OBJDEF_SCHEMATIC), + m_server(server) +{ +} + + +void SchematicManager::clear() +{ + EmergeManager *emerge = m_server->getEmergeManager(); + + // Remove all dangling references in Decorations + DecorationManager *decomgr = emerge->decomgr; + for (size_t i = 0; i != decomgr->getNumObjects(); i++) { + Decoration *deco = (Decoration *)decomgr->getRaw(i); + + try { + DecoSchematic *dschem = dynamic_cast<DecoSchematic *>(deco); + if (dschem) + dschem->schematic = NULL; + } catch (const std::bad_cast &) { + } + } + + ObjDefManager::clear(); +} + + +/////////////////////////////////////////////////////////////////////////////// + + +Schematic::Schematic() += default; + + +Schematic::~Schematic() +{ + delete []schemdata; + delete []slice_probs; +} + + +void Schematic::resolveNodeNames() +{ + getIdsFromNrBacklog(&c_nodes, true, CONTENT_AIR); + + size_t bufsize = size.X * size.Y * size.Z; + for (size_t i = 0; i != bufsize; i++) { + content_t c_original = schemdata[i].getContent(); + content_t c_new = c_nodes[c_original]; + schemdata[i].setContent(c_new); + } +} + + +void Schematic::blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place) +{ + sanity_check(m_ndef != NULL); + + int xstride = 1; + int ystride = size.X; + int zstride = size.X * size.Y; + + s16 sx = size.X; + s16 sy = size.Y; + s16 sz = size.Z; + + int i_start, i_step_x, i_step_z; + switch (rot) { + case ROTATE_90: + i_start = sx - 1; + i_step_x = zstride; + i_step_z = -xstride; + SWAP(s16, sx, sz); + break; + case ROTATE_180: + i_start = zstride * (sz - 1) + sx - 1; + i_step_x = -xstride; + i_step_z = -zstride; + break; + case ROTATE_270: + i_start = zstride * (sz - 1); + i_step_x = -zstride; + i_step_z = xstride; + SWAP(s16, sx, sz); + break; + default: + i_start = 0; + i_step_x = xstride; + i_step_z = zstride; + } + + s16 y_map = p.Y; + for (s16 y = 0; y != sy; y++) { + if ((slice_probs[y] != MTSCHEM_PROB_ALWAYS) && + (slice_probs[y] <= myrand_range(1, MTSCHEM_PROB_ALWAYS))) + continue; + + for (s16 z = 0; z != sz; z++) { + u32 i = z * i_step_z + y * ystride + i_start; + for (s16 x = 0; x != sx; x++, i += i_step_x) { + u32 vi = vm->m_area.index(p.X + x, y_map, p.Z + z); + if (!vm->m_area.contains(vi)) + continue; + + if (schemdata[i].getContent() == CONTENT_IGNORE) + continue; + + u8 placement_prob = schemdata[i].param1 & MTSCHEM_PROB_MASK; + bool force_place_node = schemdata[i].param1 & MTSCHEM_FORCE_PLACE; + + if (placement_prob == MTSCHEM_PROB_NEVER) + continue; + + if (!force_place && !force_place_node) { + content_t c = vm->m_data[vi].getContent(); + if (c != CONTENT_AIR && c != CONTENT_IGNORE) + continue; + } + + if ((placement_prob != MTSCHEM_PROB_ALWAYS) && + (placement_prob <= myrand_range(1, MTSCHEM_PROB_ALWAYS))) + continue; + + vm->m_data[vi] = schemdata[i]; + vm->m_data[vi].param1 = 0; + + if (rot) + vm->m_data[vi].rotateAlongYAxis(m_ndef, rot); + } + } + y_map++; + } +} + + +bool Schematic::placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, + Rotation rot, bool force_place) +{ + assert(vm != NULL); + assert(schemdata != NULL); + sanity_check(m_ndef != NULL); + + //// Determine effective rotation and effective schematic dimensions + if (rot == ROTATE_RAND) + rot = (Rotation)myrand_range(ROTATE_0, ROTATE_270); + + v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ? + v3s16(size.Z, size.Y, size.X) : size; + + //// Adjust placement position if necessary + if (flags & DECO_PLACE_CENTER_X) + p.X -= (s.X + 1) / 2; + if (flags & DECO_PLACE_CENTER_Y) + p.Y -= (s.Y + 1) / 2; + if (flags & DECO_PLACE_CENTER_Z) + p.Z -= (s.Z + 1) / 2; + + blitToVManip(vm, p, rot, force_place); + + return vm->m_area.contains(VoxelArea(p, p + s - v3s16(1,1,1))); +} + +void Schematic::placeOnMap(ServerMap *map, v3s16 p, u32 flags, + Rotation rot, bool force_place) +{ + std::map<v3s16, MapBlock *> lighting_modified_blocks; + std::map<v3s16, MapBlock *> modified_blocks; + std::map<v3s16, MapBlock *>::iterator it; + + assert(map != NULL); + assert(schemdata != NULL); + sanity_check(m_ndef != NULL); + + //// Determine effective rotation and effective schematic dimensions + if (rot == ROTATE_RAND) + rot = (Rotation)myrand_range(ROTATE_0, ROTATE_270); + + v3s16 s = (rot == ROTATE_90 || rot == ROTATE_270) ? + v3s16(size.Z, size.Y, size.X) : size; + + //// Adjust placement position if necessary + if (flags & DECO_PLACE_CENTER_X) + p.X -= (s.X + 1) / 2; + if (flags & DECO_PLACE_CENTER_Y) + p.Y -= (s.Y + 1) / 2; + if (flags & DECO_PLACE_CENTER_Z) + p.Z -= (s.Z + 1) / 2; + + //// Create VManip for effected area, emerge our area, modify area + //// inside VManip, then blit back. + v3s16 bp1 = getNodeBlockPos(p); + v3s16 bp2 = getNodeBlockPos(p + s - v3s16(1,1,1)); + + MMVManip vm(map); + vm.initialEmerge(bp1, bp2); + + blitToVManip(&vm, p, rot, force_place); + + voxalgo::blit_back_with_light(map, &vm, &modified_blocks); + + //// Carry out post-map-modification actions + + //// Create & dispatch map modification events to observers + MapEditEvent event; + event.type = MEET_OTHER; + for (it = modified_blocks.begin(); it != modified_blocks.end(); ++it) + event.modified_blocks.insert(it->first); + + map->dispatchEvent(&event); +} + + +bool Schematic::deserializeFromMts(std::istream *is, + std::vector<std::string> *names) +{ + std::istream &ss = *is; + content_t cignore = CONTENT_IGNORE; + bool have_cignore = false; + + //// Read signature + u32 signature = readU32(ss); + if (signature != MTSCHEM_FILE_SIGNATURE) { + errorstream << __FUNCTION__ << ": invalid schematic " + "file" << std::endl; + return false; + } + + //// Read version + u16 version = readU16(ss); + if (version > MTSCHEM_FILE_VER_HIGHEST_READ) { + errorstream << __FUNCTION__ << ": unsupported schematic " + "file version" << std::endl; + return false; + } + + //// Read size + size = readV3S16(ss); + + //// Read Y-slice probability values + delete []slice_probs; + slice_probs = new u8[size.Y]; + for (int y = 0; y != size.Y; y++) + slice_probs[y] = (version >= 3) ? readU8(ss) : MTSCHEM_PROB_ALWAYS_OLD; + + //// Read node names + u16 nidmapcount = readU16(ss); + for (int i = 0; i != nidmapcount; i++) { + std::string name = deSerializeString(ss); + + // Instances of "ignore" from v1 are converted to air (and instances + // are fixed to have MTSCHEM_PROB_NEVER later on). + if (name == "ignore") { + name = "air"; + cignore = i; + have_cignore = true; + } + + names->push_back(name); + } + + //// Read node data + size_t nodecount = size.X * size.Y * size.Z; + + delete []schemdata; + schemdata = new MapNode[nodecount]; + + MapNode::deSerializeBulk(ss, SER_FMT_VER_HIGHEST_READ, schemdata, + nodecount, 2, 2, true); + + // Fix probability values for nodes that were ignore; removed in v2 + if (version < 2) { + for (size_t i = 0; i != nodecount; i++) { + if (schemdata[i].param1 == 0) + schemdata[i].param1 = MTSCHEM_PROB_ALWAYS_OLD; + if (have_cignore && schemdata[i].getContent() == cignore) + schemdata[i].param1 = MTSCHEM_PROB_NEVER; + } + } + + // Fix probability values for probability range truncation introduced in v4 + if (version < 4) { + for (s16 y = 0; y != size.Y; y++) + slice_probs[y] >>= 1; + for (size_t i = 0; i != nodecount; i++) + schemdata[i].param1 >>= 1; + } + + return true; +} + + +bool Schematic::serializeToMts(std::ostream *os, + const std::vector<std::string> &names) +{ + std::ostream &ss = *os; + + writeU32(ss, MTSCHEM_FILE_SIGNATURE); // signature + writeU16(ss, MTSCHEM_FILE_VER_HIGHEST_WRITE); // version + writeV3S16(ss, size); // schematic size + + for (int y = 0; y != size.Y; y++) // Y slice probabilities + writeU8(ss, slice_probs[y]); + + writeU16(ss, names.size()); // name count + for (size_t i = 0; i != names.size(); i++) + ss << serializeString(names[i]); // node names + + // compressed bulk node data + MapNode::serializeBulk(ss, SER_FMT_VER_HIGHEST_WRITE, + schemdata, size.X * size.Y * size.Z, 2, 2, true); + + return true; +} + + +bool Schematic::serializeToLua(std::ostream *os, + const std::vector<std::string> &names, bool use_comments, u32 indent_spaces) +{ + std::ostream &ss = *os; + + std::string indent("\t"); + if (indent_spaces > 0) + indent.assign(indent_spaces, ' '); + + //// Write header + { + ss << "schematic = {" << std::endl; + ss << indent << "size = " + << "{x=" << size.X + << ", y=" << size.Y + << ", z=" << size.Z + << "}," << std::endl; + } + + //// Write y-slice probabilities + { + ss << indent << "yslice_prob = {" << std::endl; + + for (u16 y = 0; y != size.Y; y++) { + u8 probability = slice_probs[y] & MTSCHEM_PROB_MASK; + + ss << indent << indent << "{" + << "ypos=" << y + << ", prob=" << (u16)probability * 2 + << "}," << std::endl; + } + + ss << indent << "}," << std::endl; + } + + //// Write node data + { + ss << indent << "data = {" << std::endl; + + u32 i = 0; + for (u16 z = 0; z != size.Z; z++) + for (u16 y = 0; y != size.Y; y++) { + if (use_comments) { + ss << std::endl + << indent << indent + << "-- z=" << z + << ", y=" << y << std::endl; + } + + for (u16 x = 0; x != size.X; x++, i++) { + u8 probability = schemdata[i].param1 & MTSCHEM_PROB_MASK; + bool force_place = schemdata[i].param1 & MTSCHEM_FORCE_PLACE; + + ss << indent << indent << "{" + << "name=\"" << names[schemdata[i].getContent()] + << "\", prob=" << (u16)probability * 2 + << ", param2=" << (u16)schemdata[i].param2; + + if (force_place) + ss << ", force_place=true"; + + ss << "}," << std::endl; + } + } + + ss << indent << "}," << std::endl; + } + + ss << "}" << std::endl; + + return true; +} + + +bool Schematic::loadSchematicFromFile(const std::string &filename, + INodeDefManager *ndef, StringMap *replace_names) +{ + std::ifstream is(filename.c_str(), std::ios_base::binary); + if (!is.good()) { + errorstream << __FUNCTION__ << ": unable to open file '" + << filename << "'" << std::endl; + return false; + } + + size_t origsize = m_nodenames.size(); + if (!deserializeFromMts(&is, &m_nodenames)) + return false; + + m_nnlistsizes.push_back(m_nodenames.size() - origsize); + + name = filename; + + if (replace_names) { + for (size_t i = origsize; i < m_nodenames.size(); i++) { + std::string &node_name = m_nodenames[i]; + StringMap::iterator it = replace_names->find(node_name); + if (it != replace_names->end()) + node_name = it->second; + } + } + + if (ndef) + ndef->pendNodeResolve(this); + + return true; +} + + +bool Schematic::saveSchematicToFile(const std::string &filename, + INodeDefManager *ndef) +{ + MapNode *orig_schemdata = schemdata; + std::vector<std::string> ndef_nodenames; + std::vector<std::string> *names; + + if (m_resolve_done && ndef == NULL) + ndef = m_ndef; + + if (ndef) { + names = &ndef_nodenames; + + u32 volume = size.X * size.Y * size.Z; + schemdata = new MapNode[volume]; + for (u32 i = 0; i != volume; i++) + schemdata[i] = orig_schemdata[i]; + + generate_nodelist_and_update_ids(schemdata, volume, names, ndef); + } else { // otherwise, use the names we have on hand in the list + names = &m_nodenames; + } + + std::ostringstream os(std::ios_base::binary); + bool status = serializeToMts(&os, *names); + + if (ndef) { + delete []schemdata; + schemdata = orig_schemdata; + } + + if (!status) + return false; + + return fs::safeWriteToFile(filename, os.str()); +} + + +bool Schematic::getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2) +{ + MMVManip *vm = new MMVManip(map); + + v3s16 bp1 = getNodeBlockPos(p1); + v3s16 bp2 = getNodeBlockPos(p2); + vm->initialEmerge(bp1, bp2); + + size = p2 - p1 + 1; + + slice_probs = new u8[size.Y]; + for (s16 y = 0; y != size.Y; y++) + slice_probs[y] = MTSCHEM_PROB_ALWAYS; + + schemdata = new MapNode[size.X * size.Y * size.Z]; + + u32 i = 0; + for (s16 z = p1.Z; z <= p2.Z; z++) + for (s16 y = p1.Y; y <= p2.Y; y++) { + u32 vi = vm->m_area.index(p1.X, y, z); + for (s16 x = p1.X; x <= p2.X; x++, i++, vi++) { + schemdata[i] = vm->m_data[vi]; + schemdata[i].param1 = MTSCHEM_PROB_ALWAYS; + } + } + + delete vm; + return true; +} + + +void Schematic::applyProbabilities(v3s16 p0, + std::vector<std::pair<v3s16, u8> > *plist, + std::vector<std::pair<s16, u8> > *splist) +{ + for (size_t i = 0; i != plist->size(); i++) { + v3s16 p = (*plist)[i].first - p0; + int index = p.Z * (size.Y * size.X) + p.Y * size.X + p.X; + if (index < size.Z * size.Y * size.X) { + u8 prob = (*plist)[i].second; + schemdata[index].param1 = prob; + + // trim unnecessary node names from schematic + if (prob == MTSCHEM_PROB_NEVER) + schemdata[index].setContent(CONTENT_AIR); + } + } + + for (size_t i = 0; i != splist->size(); i++) { + s16 y = (*splist)[i].first - p0.Y; + slice_probs[y] = (*splist)[i].second; + } +} + + +void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount, + std::vector<std::string> *usednodes, INodeDefManager *ndef) +{ + std::unordered_map<content_t, content_t> nodeidmap; + content_t numids = 0; + + for (size_t i = 0; i != nodecount; i++) { + content_t id; + content_t c = nodes[i].getContent(); + + std::unordered_map<content_t, content_t>::const_iterator it = nodeidmap.find(c); + if (it == nodeidmap.end()) { + id = numids; + numids++; + + usednodes->push_back(ndef->get(c).name); + nodeidmap.insert(std::make_pair(c, id)); + } else { + id = it->second; + } + nodes[i].setContent(id); + } +} diff --git a/src/mapgen/mg_schematic.h b/src/mapgen/mg_schematic.h new file mode 100644 index 000000000..069b594f1 --- /dev/null +++ b/src/mapgen/mg_schematic.h @@ -0,0 +1,147 @@ +/* +Minetest +Copyright (C) 2014-2016 kwolekr, Ryan Kwolek <kwolekr@minetest.net> +Copyright (C) 2015-2017 paramat + +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. +*/ + +#pragma once + +#include <map> +#include "mg_decoration.h" +#include "util/string.h" + +class Map; +class ServerMap; +class Mapgen; +class MMVManip; +class PseudoRandom; +class NodeResolver; +class Server; + +/* + Minetest Schematic File Format + + All values are stored in big-endian byte order. + [u32] signature: 'MTSM' + [u16] version: 4 + [u16] size X + [u16] size Y + [u16] size Z + For each Y: + [u8] slice probability value + [Name-ID table] Name ID Mapping Table + [u16] name-id count + For each name-id mapping: + [u16] name length + [u8[]] name + ZLib deflated { + For each node in schematic: (for z, y, x) + [u16] content + For each node in schematic: + [u8] param1 + bit 0-6: probability + bit 7: specific node force placement + For each node in schematic: + [u8] param2 + } + + Version changes: + 1 - Initial version + 2 - Fixed messy never/always place; 0 probability is now never, 0xFF is always + 3 - Added y-slice probabilities; this allows for variable height structures + 4 - Compressed range of node occurence prob., added per-node force placement bit +*/ + +//// Schematic constants +#define MTSCHEM_FILE_SIGNATURE 0x4d54534d // 'MTSM' +#define MTSCHEM_FILE_VER_HIGHEST_READ 4 +#define MTSCHEM_FILE_VER_HIGHEST_WRITE 4 + +#define MTSCHEM_PROB_MASK 0x7F + +#define MTSCHEM_PROB_NEVER 0x00 +#define MTSCHEM_PROB_ALWAYS 0x7F +#define MTSCHEM_PROB_ALWAYS_OLD 0xFF + +#define MTSCHEM_FORCE_PLACE 0x80 + +enum SchematicType +{ + SCHEMATIC_NORMAL, +}; + +enum SchematicFormatType { + SCHEM_FMT_HANDLE, + SCHEM_FMT_MTS, + SCHEM_FMT_LUA, +}; + +class Schematic : public ObjDef, public NodeResolver { +public: + Schematic(); + virtual ~Schematic(); + + virtual void resolveNodeNames(); + + bool loadSchematicFromFile(const std::string &filename, INodeDefManager *ndef, + StringMap *replace_names=NULL); + bool saveSchematicToFile(const std::string &filename, INodeDefManager *ndef); + bool getSchematicFromMap(Map *map, v3s16 p1, v3s16 p2); + + bool deserializeFromMts(std::istream *is, std::vector<std::string> *names); + bool serializeToMts(std::ostream *os, const std::vector<std::string> &names); + bool serializeToLua(std::ostream *os, const std::vector<std::string> &names, + bool use_comments, u32 indent_spaces); + + void blitToVManip(MMVManip *vm, v3s16 p, Rotation rot, bool force_place); + bool placeOnVManip(MMVManip *vm, v3s16 p, u32 flags, Rotation rot, bool force_place); + void placeOnMap(ServerMap *map, v3s16 p, u32 flags, Rotation rot, bool force_place); + + void applyProbabilities(v3s16 p0, + std::vector<std::pair<v3s16, u8> > *plist, + std::vector<std::pair<s16, u8> > *splist); + + std::vector<content_t> c_nodes; + u32 flags = 0; + v3s16 size; + MapNode *schemdata = nullptr; + u8 *slice_probs = nullptr; +}; + +class SchematicManager : public ObjDefManager { +public: + SchematicManager(Server *server); + virtual ~SchematicManager() = default; + + virtual void clear(); + + const char *getObjectTitle() const + { + return "schematic"; + } + + static Schematic *create(SchematicType type) + { + return new Schematic; + } + +private: + Server *m_server; +}; + +void generate_nodelist_and_update_ids(MapNode *nodes, size_t nodecount, + std::vector<std::string> *usednodes, INodeDefManager *ndef); diff --git a/src/mapgen/treegen.cpp b/src/mapgen/treegen.cpp new file mode 100644 index 000000000..9e11b1a06 --- /dev/null +++ b/src/mapgen/treegen.cpp @@ -0,0 +1,872 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>, + 2012-2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl> +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 "irr_v3d.h" +#include <stack> +#include "util/pointer.h" +#include "util/numeric.h" +#include "map.h" +#include "mapblock.h" +#include "serverenvironment.h" +#include "nodedef.h" +#include "treegen.h" +#include "voxelalgorithms.h" + +namespace treegen +{ + +void make_tree(MMVManip &vmanip, v3s16 p0, + bool is_apple_tree, INodeDefManager *ndef, s32 seed) +{ + /* + NOTE: Tree-placing code is currently duplicated in the engine + and in games that have saplings; both are deprecated but not + replaced yet + */ + MapNode treenode(ndef->getId("mapgen_tree")); + MapNode leavesnode(ndef->getId("mapgen_leaves")); + MapNode applenode(ndef->getId("mapgen_apple")); + + PseudoRandom pr(seed); + s16 trunk_h = pr.range(4, 5); + v3s16 p1 = p0; + for (s16 ii = 0; ii < trunk_h; ii++) { + if (vmanip.m_area.contains(p1)) { + u32 vi = vmanip.m_area.index(p1); + vmanip.m_data[vi] = treenode; + } + p1.Y++; + } + + // p1 is now the last piece of the trunk + p1.Y -= 1; + + VoxelArea leaves_a(v3s16(-2, -1, -2), v3s16(2, 2, 2)); + Buffer<u8> leaves_d(leaves_a.getVolume()); + for (s32 i = 0; i < leaves_a.getVolume(); i++) + leaves_d[i] = 0; + + // Force leaves at near the end of the trunk + s16 d = 1; + for (s16 z = -d; z <= d; z++) + for (s16 y = -d; y <= d; y++) + for (s16 x = -d; x <= d; x++) { + leaves_d[leaves_a.index(v3s16(x, y, z))] = 1; + } + + // Add leaves randomly + for (u32 iii = 0; iii < 7; iii++) { + v3s16 p( + pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d), + pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d), + pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d) + ); + + for (s16 z = 0; z <= d; z++) + for (s16 y = 0; y <= d; y++) + for (s16 x = 0; x <= d; x++) { + leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1; + } + } + + // Blit leaves to vmanip + for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++) + for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { + v3s16 pmin(leaves_a.MinEdge.X, y, z); + u32 i = leaves_a.index(pmin); + u32 vi = vmanip.m_area.index(pmin + p1); + for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { + v3s16 p(x, y, z); + if (vmanip.m_area.contains(p + p1) && + (vmanip.m_data[vi].getContent() == CONTENT_AIR || + vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) { + if (leaves_d[i] == 1) { + bool is_apple = pr.range(0, 99) < 10; + if (is_apple_tree && is_apple) + vmanip.m_data[vi] = applenode; + else + vmanip.m_data[vi] = leavesnode; + } + } + vi++; + i++; + } + } +} + + +// L-System tree LUA spawner +treegen::error spawn_ltree(ServerEnvironment *env, v3s16 p0, + INodeDefManager *ndef, const TreeDef &tree_definition) +{ + ServerMap *map = &env->getServerMap(); + std::map<v3s16, MapBlock*> modified_blocks; + MMVManip vmanip(map); + v3s16 tree_blockp = getNodeBlockPos(p0); + treegen::error e; + + vmanip.initialEmerge(tree_blockp - v3s16(1, 1, 1), tree_blockp + v3s16(1, 3, 1)); + e = make_ltree(vmanip, p0, ndef, tree_definition); + if (e != SUCCESS) + return e; + + voxalgo::blit_back_with_light(map, &vmanip, &modified_blocks); + + // Send a MEET_OTHER event + MapEditEvent event; + event.type = MEET_OTHER; + for (auto &modified_block : modified_blocks) + event.modified_blocks.insert(modified_block.first); + map->dispatchEvent(&event); + return SUCCESS; +} + + +//L-System tree generator +treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, + INodeDefManager *ndef, TreeDef tree_definition) +{ + MapNode dirtnode(ndef->getId("mapgen_dirt")); + s32 seed; + if (tree_definition.explicit_seed) + seed = tree_definition.seed + 14002; + else + seed = p0.X * 2 + p0.Y * 4 + p0.Z; // use the tree position to seed PRNG + PseudoRandom ps(seed); + + // chance of inserting abcd rules + double prop_a = 9; + double prop_b = 8; + double prop_c = 7; + double prop_d = 6; + + //randomize tree growth level, minimum=2 + s16 iterations = tree_definition.iterations; + if (tree_definition.iterations_random_level > 0) + iterations -= ps.range(0, tree_definition.iterations_random_level); + if (iterations < 2) + iterations = 2; + + s16 MAX_ANGLE_OFFSET = 5; + double angle_in_radians = (double)tree_definition.angle * M_PI / 180; + double angleOffset_in_radians = (s16)(ps.range(0, 1) % MAX_ANGLE_OFFSET) * M_PI / 180; + + //initialize rotation matrix, position and stacks for branches + core::matrix4 rotation; + rotation = setRotationAxisRadians(rotation, M_PI / 2, v3f(0, 0, 1)); + v3f position; + position.X = p0.X; + position.Y = p0.Y; + position.Z = p0.Z; + std::stack <core::matrix4> stack_orientation; + std::stack <v3f> stack_position; + + //generate axiom + std::string axiom = tree_definition.initial_axiom; + for (s16 i = 0; i < iterations; i++) { + std::string temp; + for (s16 j = 0; j < (s16)axiom.size(); j++) { + char axiom_char = axiom.at(j); + switch (axiom_char) { + case 'A': + temp += tree_definition.rules_a; + break; + case 'B': + temp += tree_definition.rules_b; + break; + case 'C': + temp += tree_definition.rules_c; + break; + case 'D': + temp += tree_definition.rules_d; + break; + case 'a': + if (prop_a >= ps.range(1, 10)) + temp += tree_definition.rules_a; + break; + case 'b': + if (prop_b >= ps.range(1, 10)) + temp += tree_definition.rules_b; + break; + case 'c': + if (prop_c >= ps.range(1, 10)) + temp += tree_definition.rules_c; + break; + case 'd': + if (prop_d >= ps.range(1, 10)) + temp += tree_definition.rules_d; + break; + default: + temp += axiom_char; + break; + } + } + axiom = temp; + } + + //make sure tree is not floating in the air + if (tree_definition.trunk_type == "double") { + tree_node_placement( + vmanip, + v3f(position.X + 1, position.Y - 1, position.Z), + dirtnode + ); + tree_node_placement( + vmanip, + v3f(position.X, position.Y - 1, position.Z + 1), + dirtnode + ); + tree_node_placement( + vmanip, + v3f(position.X + 1, position.Y - 1, position.Z + 1), + dirtnode + ); + } else if (tree_definition.trunk_type == "crossed") { + tree_node_placement( + vmanip, + v3f(position.X + 1, position.Y - 1, position.Z), + dirtnode + ); + tree_node_placement( + vmanip, + v3f(position.X - 1, position.Y - 1, position.Z), + dirtnode + ); + tree_node_placement( + vmanip, + v3f(position.X, position.Y - 1, position.Z + 1), + dirtnode + ); + tree_node_placement( + vmanip, + v3f(position.X, position.Y - 1, position.Z - 1), + dirtnode + ); + } + + /* build tree out of generated axiom + + Key for Special L-System Symbols used in Axioms + + G - move forward one unit with the pen up + F - move forward one unit with the pen down drawing trunks and branches + f - move forward one unit with the pen down drawing leaves (100% chance) + T - move forward one unit with the pen down drawing trunks only + R - move forward one unit with the pen down placing fruit + A - replace with rules set A + B - replace with rules set B + C - replace with rules set C + D - replace with rules set D + a - replace with rules set A, chance 90% + b - replace with rules set B, chance 80% + c - replace with rules set C, chance 70% + d - replace with rules set D, chance 60% + + - yaw the turtle right by angle degrees + - - yaw the turtle left by angle degrees + & - pitch the turtle down by angle degrees + ^ - pitch the turtle up by angle degrees + / - roll the turtle to the right by angle degrees + * - roll the turtle to the left by angle degrees + [ - save in stack current state info + ] - recover from stack state info + + */ + + s16 x,y,z; + for (s16 i = 0; i < (s16)axiom.size(); i++) { + char axiom_char = axiom.at(i); + core::matrix4 temp_rotation; + temp_rotation.makeIdentity(); + v3f dir; + switch (axiom_char) { + case 'G': + dir = v3f(1, 0, 0); + dir = transposeMatrix(rotation, dir); + position += dir; + break; + case 'T': + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z), + tree_definition + ); + if (tree_definition.trunk_type == "double" && + !tree_definition.thin_branches) { + tree_trunk_placement( + vmanip, + v3f(position.X + 1, position.Y, position.Z), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z + 1), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X + 1, position.Y, position.Z + 1), + tree_definition + ); + } else if (tree_definition.trunk_type == "crossed" && + !tree_definition.thin_branches) { + tree_trunk_placement( + vmanip, + v3f(position.X + 1, position.Y, position.Z), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X - 1, position.Y, position.Z), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z + 1), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z - 1), + tree_definition + ); + } + dir = v3f(1, 0, 0); + dir = transposeMatrix(rotation, dir); + position += dir; + break; + case 'F': + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z), + tree_definition + ); + if ((stack_orientation.empty() && + tree_definition.trunk_type == "double") || + (!stack_orientation.empty() && + tree_definition.trunk_type == "double" && + !tree_definition.thin_branches)) { + tree_trunk_placement( + vmanip, + v3f(position.X +1 , position.Y, position.Z), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z + 1), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X + 1, position.Y, position.Z + 1), + tree_definition + ); + } else if ((stack_orientation.empty() && + tree_definition.trunk_type == "crossed") || + (!stack_orientation.empty() && + tree_definition.trunk_type == "crossed" && + !tree_definition.thin_branches)) { + tree_trunk_placement( + vmanip, + v3f(position.X + 1, position.Y, position.Z), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X - 1, position.Y, position.Z), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z + 1), + tree_definition + ); + tree_trunk_placement( + vmanip, + v3f(position.X, position.Y, position.Z - 1), + tree_definition + ); + } if (!stack_orientation.empty()) { + s16 size = 1; + for (x = -size; x <= size; x++) + for (y = -size; y <= size; y++) + for (z = -size; z <= size; z++) { + if (abs(x) == size && + abs(y) == size && + abs(z) == size) { + tree_leaves_placement( + vmanip, + v3f(position.X + x + 1, position.Y + y, + position.Z + z), + ps.next(), + tree_definition + ); + tree_leaves_placement( + vmanip, + v3f(position.X + x - 1, position.Y + y, + position.Z + z), + ps.next(), + tree_definition + ); + tree_leaves_placement( + vmanip,v3f(position.X + x, position.Y + y, + position.Z + z + 1), + ps.next(), + tree_definition + ); + tree_leaves_placement( + vmanip,v3f(position.X + x, position.Y + y, + position.Z + z - 1), + ps.next(), + tree_definition + ); + } + } + } + dir = v3f(1, 0, 0); + dir = transposeMatrix(rotation, dir); + position += dir; + break; + case 'f': + tree_single_leaves_placement( + vmanip, + v3f(position.X, position.Y, position.Z), + ps.next(), + tree_definition + ); + dir = v3f(1, 0, 0); + dir = transposeMatrix(rotation, dir); + position += dir; + break; + case 'R': + tree_fruit_placement( + vmanip, + v3f(position.X, position.Y, position.Z), + tree_definition + ); + dir = v3f(1, 0, 0); + dir = transposeMatrix(rotation, dir); + position += dir; + break; + + // turtle orientation commands + case '[': + stack_orientation.push(rotation); + stack_position.push(position); + break; + case ']': + if (stack_orientation.empty()) + return UNBALANCED_BRACKETS; + rotation = stack_orientation.top(); + stack_orientation.pop(); + position = stack_position.top(); + stack_position.pop(); + break; + case '+': + temp_rotation.makeIdentity(); + temp_rotation = setRotationAxisRadians(temp_rotation, + angle_in_radians + angleOffset_in_radians, v3f(0, 0, 1)); + rotation *= temp_rotation; + break; + case '-': + temp_rotation.makeIdentity(); + temp_rotation = setRotationAxisRadians(temp_rotation, + angle_in_radians + angleOffset_in_radians, v3f(0, 0, -1)); + rotation *= temp_rotation; + break; + case '&': + temp_rotation.makeIdentity(); + temp_rotation = setRotationAxisRadians(temp_rotation, + angle_in_radians + angleOffset_in_radians, v3f(0, 1, 0)); + rotation *= temp_rotation; + break; + case '^': + temp_rotation.makeIdentity(); + temp_rotation = setRotationAxisRadians(temp_rotation, + angle_in_radians + angleOffset_in_radians, v3f(0, -1, 0)); + rotation *= temp_rotation; + break; + case '*': + temp_rotation.makeIdentity(); + temp_rotation = setRotationAxisRadians(temp_rotation, + angle_in_radians, v3f(1, 0, 0)); + rotation *= temp_rotation; + break; + case '/': + temp_rotation.makeIdentity(); + temp_rotation = setRotationAxisRadians(temp_rotation, + angle_in_radians, v3f(-1, 0, 0)); + rotation *= temp_rotation; + break; + default: + break; + } + } + + return SUCCESS; +} + + +void tree_node_placement(MMVManip &vmanip, v3f p0, MapNode node) +{ + v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); + if (!vmanip.m_area.contains(p1)) + return; + u32 vi = vmanip.m_area.index(p1); + if (vmanip.m_data[vi].getContent() != CONTENT_AIR + && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) + return; + vmanip.m_data[vmanip.m_area.index(p1)] = node; +} + + +void tree_trunk_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition) +{ + v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); + if (!vmanip.m_area.contains(p1)) + return; + u32 vi = vmanip.m_area.index(p1); + content_t current_node = vmanip.m_data[vi].getContent(); + if (current_node != CONTENT_AIR && current_node != CONTENT_IGNORE + && current_node != tree_definition.leavesnode.getContent() + && current_node != tree_definition.leaves2node.getContent() + && current_node != tree_definition.fruitnode.getContent()) + return; + vmanip.m_data[vi] = tree_definition.trunknode; +} + + +void tree_leaves_placement(MMVManip &vmanip, v3f p0, + PseudoRandom ps, TreeDef &tree_definition) +{ + MapNode leavesnode = tree_definition.leavesnode; + if (ps.range(1, 100) > 100 - tree_definition.leaves2_chance) + leavesnode = tree_definition.leaves2node; + v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); + if (!vmanip.m_area.contains(p1)) + return; + u32 vi = vmanip.m_area.index(p1); + if (vmanip.m_data[vi].getContent() != CONTENT_AIR + && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) + return; + if (tree_definition.fruit_chance > 0) { + if (ps.range(1, 100) > 100 - tree_definition.fruit_chance) + vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode; + else + vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode; + } else if (ps.range(1, 100) > 20) { + vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode; + } +} + + +void tree_single_leaves_placement(MMVManip &vmanip, v3f p0, + PseudoRandom ps, TreeDef &tree_definition) +{ + MapNode leavesnode = tree_definition.leavesnode; + if (ps.range(1, 100) > 100 - tree_definition.leaves2_chance) + leavesnode = tree_definition.leaves2node; + v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); + if (!vmanip.m_area.contains(p1)) + return; + u32 vi = vmanip.m_area.index(p1); + if (vmanip.m_data[vi].getContent() != CONTENT_AIR + && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) + return; + vmanip.m_data[vmanip.m_area.index(p1)] = leavesnode; +} + + +void tree_fruit_placement(MMVManip &vmanip, v3f p0, TreeDef &tree_definition) +{ + v3s16 p1 = v3s16(myround(p0.X), myround(p0.Y), myround(p0.Z)); + if (!vmanip.m_area.contains(p1)) + return; + u32 vi = vmanip.m_area.index(p1); + if (vmanip.m_data[vi].getContent() != CONTENT_AIR + && vmanip.m_data[vi].getContent() != CONTENT_IGNORE) + return; + vmanip.m_data[vmanip.m_area.index(p1)] = tree_definition.fruitnode; +} + + +irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3f axis) +{ + double c = cos(angle); + double s = sin(angle); + double t = 1.0 - c; + + double tx = t * axis.X; + double ty = t * axis.Y; + double tz = t * axis.Z; + double sx = s * axis.X; + double sy = s * axis.Y; + double sz = s * axis.Z; + + M[0] = tx * axis.X + c; + M[1] = tx * axis.Y + sz; + M[2] = tx * axis.Z - sy; + + M[4] = ty * axis.X - sz; + M[5] = ty * axis.Y + c; + M[6] = ty * axis.Z + sx; + + M[8] = tz * axis.X + sy; + M[9] = tz * axis.Y - sx; + M[10] = tz * axis.Z + c; + return M; +} + + +v3f transposeMatrix(irr::core::matrix4 M, v3f v) +{ + v3f translated; + double x = M[0] * v.X + M[4] * v.Y + M[8] * v.Z +M[12]; + double y = M[1] * v.X + M[5] * v.Y + M[9] * v.Z +M[13]; + double z = M[2] * v.X + M[6] * v.Y + M[10] * v.Z +M[14]; + translated.X = x; + translated.Y = y; + translated.Z = z; + return translated; +} + + +void make_jungletree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, s32 seed) +{ + /* + NOTE: Tree-placing code is currently duplicated in the engine + and in games that have saplings; both are deprecated but not + replaced yet + */ + content_t c_tree = ndef->getId("mapgen_jungletree"); + content_t c_leaves = ndef->getId("mapgen_jungleleaves"); + if (c_tree == CONTENT_IGNORE) + c_tree = ndef->getId("mapgen_tree"); + if (c_leaves == CONTENT_IGNORE) + c_leaves = ndef->getId("mapgen_leaves"); + + MapNode treenode(c_tree); + MapNode leavesnode(c_leaves); + + PseudoRandom pr(seed); + for (s16 x= -1; x <= 1; x++) + for (s16 z= -1; z <= 1; z++) { + if (pr.range(0, 2) == 0) + continue; + v3s16 p1 = p0 + v3s16(x, 0, z); + v3s16 p2 = p0 + v3s16(x, -1, z); + u32 vi1 = vmanip.m_area.index(p1); + u32 vi2 = vmanip.m_area.index(p2); + + if (vmanip.m_area.contains(p2) && + vmanip.m_data[vi2].getContent() == CONTENT_AIR) + vmanip.m_data[vi2] = treenode; + else if (vmanip.m_area.contains(p1) && + vmanip.m_data[vi1].getContent() == CONTENT_AIR) + vmanip.m_data[vi1] = treenode; + } + vmanip.m_data[vmanip.m_area.index(p0)] = treenode; + + s16 trunk_h = pr.range(8, 12); + v3s16 p1 = p0; + for (s16 ii = 0; ii < trunk_h; ii++) { + if (vmanip.m_area.contains(p1)) { + u32 vi = vmanip.m_area.index(p1); + vmanip.m_data[vi] = treenode; + } + p1.Y++; + } + + // p1 is now the last piece of the trunk + p1.Y -= 1; + + VoxelArea leaves_a(v3s16(-3, -2, -3), v3s16(3, 2, 3)); + //SharedPtr<u8> leaves_d(new u8[leaves_a.getVolume()]); + Buffer<u8> leaves_d(leaves_a.getVolume()); + for (s32 i = 0; i < leaves_a.getVolume(); i++) + leaves_d[i] = 0; + + // Force leaves at near the end of the trunk + s16 d = 1; + for (s16 z = -d; z <= d; z++) + for (s16 y = -d; y <= d; y++) + for (s16 x = -d; x <= d; x++) { + leaves_d[leaves_a.index(v3s16(x,y,z))] = 1; + } + + // Add leaves randomly + for (u32 iii = 0; iii < 30; iii++) { + v3s16 p( + pr.range(leaves_a.MinEdge.X, leaves_a.MaxEdge.X - d), + pr.range(leaves_a.MinEdge.Y, leaves_a.MaxEdge.Y - d), + pr.range(leaves_a.MinEdge.Z, leaves_a.MaxEdge.Z - d) + ); + + for (s16 z = 0; z <= d; z++) + for (s16 y = 0; y <= d; y++) + for (s16 x = 0; x <= d; x++) { + leaves_d[leaves_a.index(p + v3s16(x, y, z))] = 1; + } + } + + // Blit leaves to vmanip + for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++) + for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { + v3s16 pmin(leaves_a.MinEdge.X, y, z); + u32 i = leaves_a.index(pmin); + u32 vi = vmanip.m_area.index(pmin + p1); + for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { + v3s16 p(x, y, z); + if (vmanip.m_area.contains(p + p1) && + (vmanip.m_data[vi].getContent() == CONTENT_AIR || + vmanip.m_data[vi].getContent() == CONTENT_IGNORE)) { + if (leaves_d[i] == 1) + vmanip.m_data[vi] = leavesnode; + } + vi++; + i++; + } + } +} + + +void make_pine_tree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, s32 seed) +{ + /* + NOTE: Tree-placing code is currently duplicated in the engine + and in games that have saplings; both are deprecated but not + replaced yet + */ + content_t c_tree = ndef->getId("mapgen_pine_tree"); + content_t c_leaves = ndef->getId("mapgen_pine_needles"); + content_t c_snow = ndef->getId("mapgen_snow"); + if (c_tree == CONTENT_IGNORE) + c_tree = ndef->getId("mapgen_tree"); + if (c_leaves == CONTENT_IGNORE) + c_leaves = ndef->getId("mapgen_leaves"); + if (c_snow == CONTENT_IGNORE) + c_snow = CONTENT_AIR; + + MapNode treenode(c_tree); + MapNode leavesnode(c_leaves); + MapNode snownode(c_snow); + + PseudoRandom pr(seed); + u16 trunk_h = pr.range(9, 13); + v3s16 p1 = p0; + for (u16 ii = 0; ii < trunk_h; ii++) { + if (vmanip.m_area.contains(p1)) { + u32 vi = vmanip.m_area.index(p1); + vmanip.m_data[vi] = treenode; + } + p1.Y++; + } + + // Make p1 the top node of the trunk + p1.Y -= 1; + + VoxelArea leaves_a(v3s16(-3, -6, -3), v3s16(3, 3, 3)); + Buffer<u8> leaves_d(leaves_a.getVolume()); + for (s32 i = 0; i < leaves_a.getVolume(); i++) + leaves_d[i] = 0; + + // Upper branches + u16 dev = 3; + for (s16 yy = -1; yy <= 1; yy++) { + for (s16 zz = -dev; zz <= dev; zz++) { + u32 i = leaves_a.index(v3s16(-dev, yy, zz)); + u32 ia = leaves_a.index(v3s16(-dev, yy+1, zz)); + for (s16 xx = -dev; xx <= dev; xx++) { + if (pr.range(0, 20) <= 19 - dev) { + leaves_d[i] = 1; + leaves_d[ia] = 2; + } + i++; + ia++; + } + } + dev--; + } + + // Centre top nodes + leaves_d[leaves_a.index(v3s16(0, 1, 0))] = 1; + leaves_d[leaves_a.index(v3s16(0, 2, 0))] = 1; + leaves_d[leaves_a.index(v3s16(0, 3, 0))] = 2; + + // Lower branches + s16 my = -6; + for (u32 iii = 0; iii < 20; iii++) { + s16 xi = pr.range(-3, 2); + s16 yy = pr.range(-6, -5); + s16 zi = pr.range(-3, 2); + if (yy > my) + my = yy; + for (s16 zz = zi; zz <= zi + 1; zz++) { + u32 i = leaves_a.index(v3s16(xi, yy, zz)); + u32 ia = leaves_a.index(v3s16(xi, yy + 1, zz)); + for (s32 xx = xi; xx <= xi + 1; xx++) { + leaves_d[i] = 1; + if (leaves_d[ia] == 0) + leaves_d[ia] = 2; + i++; + ia++; + } + } + } + + dev = 2; + for (s16 yy = my + 1; yy <= my + 2; yy++) { + for (s16 zz = -dev; zz <= dev; zz++) { + u32 i = leaves_a.index(v3s16(-dev, yy, zz)); + u32 ia = leaves_a.index(v3s16(-dev, yy + 1, zz)); + for (s16 xx = -dev; xx <= dev; xx++) { + if (pr.range(0, 20) <= 19 - dev) { + leaves_d[i] = 1; + leaves_d[ia] = 2; + } + i++; + ia++; + } + } + dev--; + } + + // Blit leaves to vmanip + for (s16 z = leaves_a.MinEdge.Z; z <= leaves_a.MaxEdge.Z; z++) + for (s16 y = leaves_a.MinEdge.Y; y <= leaves_a.MaxEdge.Y; y++) { + v3s16 pmin(leaves_a.MinEdge.X, y, z); + u32 i = leaves_a.index(pmin); + u32 vi = vmanip.m_area.index(pmin + p1); + for (s16 x = leaves_a.MinEdge.X; x <= leaves_a.MaxEdge.X; x++) { + v3s16 p(x, y, z); + if (vmanip.m_area.contains(p + p1) && + (vmanip.m_data[vi].getContent() == CONTENT_AIR || + vmanip.m_data[vi].getContent() == CONTENT_IGNORE || + vmanip.m_data[vi] == snownode)) { + if (leaves_d[i] == 1) + vmanip.m_data[vi] = leavesnode; + else if (leaves_d[i] == 2) + vmanip.m_data[vi] = snownode; + } + vi++; + i++; + } + } +} + +}; // namespace treegen diff --git a/src/mapgen/treegen.h b/src/mapgen/treegen.h new file mode 100644 index 000000000..8e5306560 --- /dev/null +++ b/src/mapgen/treegen.h @@ -0,0 +1,92 @@ +/* +Minetest +Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>, + 2012-2013 RealBadAngel, Maciej Kasatkin <mk@realbadangel.pl> +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. +*/ + +#pragma once + +#include <matrix4.h> +#include "noise.h" + +class MMVManip; +class INodeDefManager; +class ServerEnvironment; + + +namespace treegen { + + enum error { + SUCCESS, + UNBALANCED_BRACKETS + }; + + struct TreeDef { + std::string initial_axiom; + std::string rules_a; + std::string rules_b; + std::string rules_c; + std::string rules_d; + + MapNode trunknode; + MapNode leavesnode; + MapNode leaves2node; + + int leaves2_chance; + int angle; + int iterations; + int iterations_random_level; + std::string trunk_type; + bool thin_branches; + MapNode fruitnode; + int fruit_chance; + s32 seed; + bool explicit_seed; + }; + + // Add default tree + void make_tree(MMVManip &vmanip, v3s16 p0, + bool is_apple_tree, INodeDefManager *ndef, s32 seed); + // Add jungle tree + void make_jungletree(MMVManip &vmanip, v3s16 p0, + INodeDefManager *ndef, s32 seed); + // Add pine tree + void make_pine_tree(MMVManip &vmanip, v3s16 p0, + INodeDefManager *ndef, s32 seed); + + // Add L-Systems tree (used by engine) + treegen::error make_ltree(MMVManip &vmanip, v3s16 p0, INodeDefManager *ndef, + TreeDef tree_definition); + // Spawn L-systems tree from LUA + treegen::error spawn_ltree (ServerEnvironment *env, v3s16 p0, INodeDefManager *ndef, + const TreeDef &tree_definition); + + // L-System tree gen helper functions + void tree_node_placement(MMVManip &vmanip, v3f p0, + MapNode node); + void tree_trunk_placement(MMVManip &vmanip, v3f p0, + TreeDef &tree_definition); + void tree_leaves_placement(MMVManip &vmanip, v3f p0, + PseudoRandom ps, TreeDef &tree_definition); + void tree_single_leaves_placement(MMVManip &vmanip, v3f p0, + PseudoRandom ps, TreeDef &tree_definition); + void tree_fruit_placement(MMVManip &vmanip, v3f p0, + TreeDef &tree_definition); + irr::core::matrix4 setRotationAxisRadians(irr::core::matrix4 M, double angle, v3f axis); + + v3f transposeMatrix(irr::core::matrix4 M ,v3f v); + +}; // namespace treegen |