#include #include #include #include #include #include #include "Core.h" #include "Console.h" #include "Export.h" #include "PluginManager.h" #include "modules/MapCache.h" #include "modules/Random.h" #include "modules/World.h" #include "MiscUtils.h" #include "DataDefs.h" #include "df/world.h" #include "df/world_data.h" #include "df/world_region_details.h" #include "df/world_region_feature.h" #include "df/world_geo_biome.h" #include "df/world_geo_layer.h" #include "df/world_underground_region.h" #include "df/feature_init.h" #include "df/region_map_entry.h" #include "df/inclusion_type.h" #include "df/viewscreen_choose_start_sitest.h" #include "df/plant.h" #ifdef LINUX_BUILD #include using std::tr1::shared_ptr; #else #include using std::shared_ptr; #endif using namespace df::enums; using namespace DFHack; using namespace MapExtras; using namespace DFHack::Random; DFHACK_PLUGIN("3dveins"); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(gametype); command_result cmd_3dveins(color_ostream &out, std::vector & parameters); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand( "3dveins", "Rewrites the veins to make them extend in 3D space.", cmd_3dveins)); return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; } /* * Vein density fields */ typedef std::pair t_veinkey; struct NoiseFunction { typedef shared_ptr Ptr; typedef std::pair t_range; virtual ~NoiseFunction() {}; /* * Veins are placed by clipping the computed value * against a floating threshold, with values above * the threshold causing placement of a vein tile. */ virtual float eval(float x, float y, float z) = 0; virtual t_range range() = 0; virtual void displace(float &x, float &y, float &z) = 0; }; inline float apow(float a, float b) { return powf(fabsf(a), b); } struct Distribution : NoiseFunction { float bx, by, bz; Distribution(MersenneRNG &rng, float scale) { bx = rng.drandom() * scale; by = rng.drandom() * scale; bz = rng.drandom() * scale; } void displace(float &x, float &y, float &z) { x += bx; y += by; z += bz; } }; struct DistributionVein : Distribution { PerlinNoise3D density1, density2; PerlinNoise3D strand1a, strand1b; DistributionVein(MersenneRNG &rng) : Distribution(rng, 96) { density1.init(rng); density2.init(rng); strand1a.init(rng); strand1b.init(rng); } float eval(float x, float y, float z) { return 0.1f * density1(x/96, y/96, z/48) + 0.2f * density2(x/48, y/48, z/24) - apow( strand1a(x/24,y/24,z/12) +0.6f*strand1b(x/16,y/16,z/8), 0.6f); } t_range range() { return t_range(-0.3f-1.33f,0.3f); } }; struct DistributionCluster : Distribution { PerlinNoise3D density1, density2, shape; DistributionCluster(MersenneRNG &rng) : Distribution(rng, 96) { density1.init(rng); density2.init(rng); shape.init(rng); } float eval(float x, float y, float z) { return 0.2f * density1(x/96, y/96, z/32) + 0.6f * density2(x/48, y/48, z/16) + shape(x/24, y/24, z/8); } t_range range() { return t_range(-1.8f,1.8f); } }; struct DistributionClusterSmall : Distribution { PerlinNoise3D density1, density2, shape; DistributionClusterSmall(MersenneRNG &rng) : Distribution(rng, 96) { density1.init(rng); density2.init(rng); shape.init(rng); } float eval(float x, float y, float z) { const float scale = 1.0f/4.3f; return 0.06f * density1(x/96, y/96, z/48) + 0.12f * density2(x/24, y/24, z/12) + apow(shape(x*scale, y*scale, z*scale), 0.1f); } t_range range() { return t_range(-0.18f,1.18f); } }; struct DistributionClusterOne : Distribution { PerlinNoise3D density1, density2, shape; DistributionClusterOne(MersenneRNG &rng) : Distribution(rng, 96) { density1.init(rng); density2.init(rng); shape.init(rng); } float eval(float x, float y, float z) { return 0.05f * density1(x/96, y/96, z/48) + 0.1f * density2(x/48, y/48, z/24) + shape(x-bx, y-by, z-bz); } t_range range() { return t_range(-1.15f,1.15f); } }; static NoiseFunction *makeVeinDistribution(t_veinkey vein, MersenneRNG &rng) { using namespace df::enums::inclusion_type; switch (vein.second) { case VEIN: return new DistributionVein(rng); case CLUSTER_SMALL: return new DistributionClusterSmall(rng); case CLUSTER_ONE: return new DistributionClusterOne(rng); case CLUSTER: default: return new DistributionCluster(rng); } } /* * Data structures. */ template class BlockGrid { df::coord dim; std::vector buf; public: BlockGrid(df::coord size) : dim(size) { buf.resize(dim.x * dim.y * dim.z); } BlockGrid(df::coord2d size, int zdepth = 1) : dim(df::coord(size.x, size.y, zdepth)) { buf.resize(dim.x * dim.y * dim.z); } const df::coord &size() { return dim; } void resize_depth(int16_t zdepth) { dim.z = zdepth; buf.resize(dim.x * dim.y * dim.z); } void shift_depth(int16_t to_add) { dim.z += to_add; buf.insert(buf.begin(), dim.x*dim.y*to_add, T()); } T &operator() (int x, int y, int z = 0) { return buf[x + dim.x*(y + dim.y*z)]; } T &operator() (df::coord pos) { return buf[pos.x + dim.x*(pos.y + dim.y*pos.z)]; } T &operator() (df::coord2d pos, int z = 0) { return buf[pos.x + dim.x*(pos.y + dim.y*z)]; } }; enum SpecialMatCodes { // Tile not mapped to an actual tile SMC_NO_MAPPING = -1, // Tile not assigned to a vein yet SMC_LAYER = -2 }; struct GeoLayer; struct GeoColumn; struct GeoBiome; /* Representation of a block in geolayer coordinate space. * That is, it represents a block that would have happened * if geological layers weren't shifted in Z direction to * conform to terrain height. Computing veins in this system * of coordinates should make them nicely follow the layers. */ struct GeoBlock { GeoLayer *layer; GeoColumn *column; df::coord pos; uint16_t arena_mask, arena_unmined; int16_t arena_material; df::tile_bitmask unmined; int16_t material[16][16]; uint8_t veintype[16][16]; float weight[16][16]; GeoBlock(GeoLayer *parent, df::coord pos) : layer(parent), pos(pos) { memset(material, -1, sizeof(material)); } bool prepare_arena(int16_t env_material, NoiseFunction::Ptr fn); int measure_placement(float threshold); void place_tiles(float threshold, int16_t new_material, df::inclusion_type itype); }; /* * A set of layers that have the same vein type, distribution * and approximate location, and thus should be placed using * one binsearch pass for more uniform appearance. */ struct VeinExtent { typedef shared_ptr Ptr; typedef std::vector PVec; t_veinkey vein; int probability, num_tiles; Ptr parent; int parent_depth; bool placed; int placed_tiles; NoiseFunction::Ptr distribution; int num_unmined, num_layer, min_z, max_z; std::vector layers; VeinExtent(t_veinkey vein) : vein(vein) { probability = num_tiles = placed_tiles = 0; num_unmined = num_layer = 0; min_z = max_z = 0; parent_depth = 0; placed = false; } float density() { return float(num_tiles) / num_unmined; } void set_parent(Ptr pp) { if (parent) parent->add_tiles(-num_tiles); parent = pp; if (parent) parent->add_tiles(num_tiles); parent_depth = (pp ? pp->parent_depth+1 : 0); } int parent_mat() { return parent ? parent->vein.first : SMC_LAYER; } void add_tiles(int tiles) { num_tiles += tiles; if (parent) parent->add_tiles(tiles); } bool is_similar(Ptr ext2) { return probability == ext2->probability && parent_mat() == ext2->parent_mat(); } void link(GeoLayer *layer); void merge_into(VeinExtent::Ptr ext2); void place_tiles(); }; struct GeoColumn { // Original Z level values for each layer int16_t min_level[16][16][BiomeInfo::MAX_LAYERS]; int16_t max_level[16][16][BiomeInfo::MAX_LAYERS]; int8_t top_layer[16][16]; int8_t bottom_layer[16][16]; int8_t top_solid_z[16][16]; GeoColumn() { memset(min_level, -1, sizeof(min_level)); memset(max_level, -1, sizeof(max_level)); memset(top_layer, -1, sizeof(top_layer)); memset(bottom_layer, -1, sizeof(bottom_layer)); memset(top_solid_z, -1, sizeof(top_solid_z)); } }; struct GeoLayer { GeoBiome *biome; int index; df::world_geo_layer *info; int thickness, z_bias; int aquifer_depth; int16_t material; bool is_soil; bool is_soil_layer; // World-global origin coordinates in blocks df::coord world_pos; int min_z() { return world_pos.z - z_bias; } int max_z() { return world_pos.z + thickness - 1; } df::coord2d size; BlockGrid blocks; std::vector block_list; int tiles, unmined_tiles, mineral_tiles; std::map mineral_count; std::map veins; GeoLayer(GeoBiome *parent, int index, df::world_geo_layer *info); ~GeoLayer() { for (size_t i = 0; i < block_list.size(); i++) delete block_list[i]; } GeoBlock *blockAt(df::coord pos) { assert(pos.z >= 0); if (pos.z >= blocks.size().z) blocks.resize_depth(pos.z+1); GeoBlock *&blk = blocks(pos); if (!blk) { blk = new GeoBlock(this, pos); block_list.push_back(blk); } return blk; } GeoBlock *getBlockAt(df::coord pos) { if (pos.z < 0 || pos.z >= blocks.size().z) return NULL; return blocks(pos); } void setZBias(int bias) { if (bias <= z_bias) return; blocks.shift_depth(bias - z_bias); z_bias = bias; } void print_mineral_stats(color_ostream &out) { for (auto it = mineral_count.begin(); it != mineral_count.end(); ++it) out << " " << MaterialInfo(0,it->first.first).getToken() << " " << ENUM_KEY_STR(inclusion_type,it->first.second) << ": \t\t" << it->second << " (" << (float(it->second)/unmined_tiles) << ")" << std::endl; out.print(" Total tiles: %d (%d unmined)\n", tiles, unmined_tiles); } bool form_veins(color_ostream &out); }; struct GeoBiome { const BiomeInfo &info; df::coord2d world_pos; df::coord2d size; BlockGrid columns; std::vector layers; GeoBiome(const BiomeInfo &biome, df::coord2d base, df::coord2d mapsize) : info(biome), world_pos(base), size(mapsize), columns(mapsize) { } ~GeoBiome() { for (size_t i = 0; i < layers.size(); i++) delete layers[i]; } bool init_layers(); void print_mineral_stats(color_ostream &out) { out.print("Geological biome %d:\n", info.geo_index); for (size_t i = 0; i < layers.size(); i++) if (layers[i]) { out << " Layer " << i << std::endl; layers[i]->print_mineral_stats(out); } } }; GeoLayer::GeoLayer(GeoBiome *parent, int index, df::world_geo_layer *info) : biome(parent), index(index), info(info), world_pos(parent->world_pos.x, parent->world_pos.y, -info->top_height), size(parent->size), blocks(parent->size) { thickness = info->top_height - info->bottom_height + 1; z_bias = 0; aquifer_depth = 0; tiles = unmined_tiles = mineral_tiles = 0; material = info->mat_index; is_soil = isSoilInorganic(material); is_soil_layer = (info->type == geo_layer_type::SOIL || info->type == geo_layer_type::SOIL_SAND); } const unsigned NUM_INCLUSIONS = 1+(int)ENUM_LAST_ITEM(inclusion_type); struct VeinGenerator { color_ostream &out; MapCache map; df::coord2d size; df::coord2d base; std::map biomes; std::vector biome_by_idx; struct VMats { bool can_support_aquifer; int8_t default_type; int8_t valid_type[NUM_INCLUSIONS]; uint32_t seeds[NUM_INCLUSIONS]; NoiseFunction::Ptr funcs[NUM_INCLUSIONS]; VMats() { default_type = -2; memset(valid_type, -1, sizeof(valid_type)); } }; std::vector materials; std::map veins; VeinGenerator(color_ostream &out) : out(out) {} ~VeinGenerator() { for (auto it = biomes.begin(); it != biomes.end(); ++it) delete it->second; } GeoLayer *mapLayer(Block *pb, df::coord2d tile); bool init_biomes(); bool scan_tiles(); bool scan_layer_depth(Block *b, df::coord2d column, int z); bool adjust_layer_depth(df::coord2d column); bool scan_block_tiles(Block *b, df::coord2d column, int z); void write_tiles(); void write_block_tiles(Block *b, df::coord2d column, int z); bool form_veins(); bool place_orphan(t_veinkey vein, int size, GeoLayer *from); void init_seeds(); NoiseFunction::Ptr get_noise(t_veinkey vein); bool place_veins(bool verbose); void print_mineral_stats() { for (auto it = biomes.begin(); it != biomes.end(); ++it) it->second->print_mineral_stats(out); } }; /* * General initialization */ bool VeinGenerator::init_biomes() { auto &mats = df::inorganic_raw::get_vector(); materials.resize(world->raws.inorganics.size()); for (size_t i = 0; i < mats.size(); i++) { materials[i].can_support_aquifer = mats[i]->flags.is_set(inorganic_flags::AQUIFER); // Be silent about slade veins, which can happen below hell if (mats[i]->flags.is_set(inorganic_flags::DEEP_SURFACE)) materials[i].valid_type[0] = -2; } biome_by_idx.resize(map.getBiomeCount()); size = df::coord2d(map.maxBlockX()+1, map.maxBlockY()+1); base = df::coord2d(world->map.region_x*3, world->map.region_y*3); for (size_t i = 0; i < biome_by_idx.size(); i++) { const BiomeInfo &info = map.getBiomeByIndex(i); if (info.geo_index < 0 || !info.geobiome) { out.printerr("Biome %zd is not defined.\n", i); return false; } GeoBiome *&biome = biomes[info.geo_index]; if (!biome) { biome = new GeoBiome(info, base, size); if (!biome->init_layers()) return false; // Mark valid vein types auto &layers = info.geobiome->layers; for (size_t i = 0; i < layers.size(); i++) { auto layer = layers[i]; for (size_t j = 0; j < layer->vein_mat.size(); j++) { if (unsigned(layer->vein_mat[j]) >= materials.size() || unsigned(layer->vein_type[j]) >= NUM_INCLUSIONS) continue; auto &minfo = materials[layer->vein_mat[j]]; int type = layer->vein_type[j]; minfo.valid_type[type] = type; if (minfo.default_type < 0 || type == inclusion_type::CLUSTER) minfo.default_type = type; } } } biome_by_idx[i] = biome; } return true; } bool GeoBiome::init_layers() { auto &info_layers = info.geobiome->layers; layers.resize(info_layers.size()); for (size_t i = 0; i < layers.size(); i++) { layers[i] = new GeoLayer(this, i, info_layers[i]); } return true; } /* * Initial statistics collection scan. It has to: * * 1) Find out how the layers are shifted in Z direction for each tile column. * 2) Record which tiles are available, which are unmined, and how much minerals are there. */ GeoLayer *VeinGenerator::mapLayer(Block *pb, df::coord2d tile) { int idx = pb->biomeIndexAt(tile); GeoBiome *biome = biome_by_idx.at(idx); int lidx = pb->layerIndexAt(tile); if (unsigned(lidx) >= biome->layers.size()) return NULL; return biome->layers[lidx]; } static bool isTransientMaterial(df::tiletype tile) { using namespace df::enums::tiletype_material; switch (tileMaterial(tile)) { case AIR: case LAVA_STONE: case PLANT: case ROOT: case TREE: case MUSHROOM: return true; default: return false; } } static bool isSkyBlock(Block *b) { for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { df::coord2d tile(x,y); auto dsgn = b->DesignationAt(tile); auto ttype = b->baseTiletypeAt(tile); if (dsgn.bits.subterranean || !dsgn.bits.light || !isTransientMaterial(ttype)) return false; } } return true; } static int findTopBlock(MapCache &map, int x, int y) { for (int z = map.maxZ(); z >= 0; z--) { Block *b = map.BlockAt(df::coord(x,y,z)); if (b && b->is_valid() && !isSkyBlock(b)) return z; } return -1; } bool VeinGenerator::scan_tiles() { for (int x = 0; x < size.x; x++) { for (int y = 0; y < size.y; y++) { df::coord2d column(x,y); int top = findTopBlock(map, x, y); // First find where layers start and end for (int z = top; z >= 0; z--) { Block *b = map.BlockAt(df::coord(x,y,z)); if (!b || !b->is_valid()) continue; if (!scan_layer_depth(b, column, z)) return false; } if (!adjust_layer_depth(column)) return false; // Collect tile data for (int z = top; z >= 0; z--) { Block *b = map.BlockAt(df::coord(x,y,z)); if (!b || !b->is_valid()) continue; if (!scan_block_tiles(b, column, z)) return false; map.discardBlock(b); } // Discard this column of parsed blocks map.trash(); } } return true; } bool VeinGenerator::scan_layer_depth(Block *b, df::coord2d column, int z) { for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { df::coord2d tile(x,y); GeoLayer *layer = mapLayer(b, tile); if (!layer) continue; int idx = layer->index; auto &col_info = layer->biome->columns(column); auto &max_level = col_info.max_level[x][y]; auto &min_level = col_info.min_level[x][y]; auto &top = col_info.top_layer[x][y]; auto &top_solid = col_info.top_solid_z[x][y]; auto &bottom = col_info.bottom_layer[x][y]; auto ttype = b->baseTiletypeAt(tile); bool obsidian = isTransientMaterial(ttype); if (top_solid < 0 && !obsidian && isWallTerrain(ttype)) top_solid = z; if (max_level[idx] < 0) { // Do not start the layer stack in open air. // Those tiles can be very weird. if (bottom < 0 && (isOpenTerrain(ttype) || obsidian)) continue; max_level[idx] = min_level[idx] = z; if (top < 0 || idx < top) top = idx; bottom = std::max(idx, bottom); } else { if (z != min_level[idx]-1 && min_level[idx] <= top_solid) { out.printerr( "Discontinuous layer %d at (%d,%d,%d).\n", layer->index, x+column.x*16, y+column.y*16, z ); return false; } min_level[idx] = z; } } } return true; } bool VeinGenerator::adjust_layer_depth(df::coord2d column) { for (auto it = biomes.begin(); it != biomes.end(); ++it) { GeoBiome *biome = it->second; auto &col_info = biome->columns(column); for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { auto &max_level = col_info.max_level[x][y]; auto &min_level = col_info.min_level[x][y]; auto top_solid = col_info.top_solid_z[x][y]; int min_defined = col_info.top_layer[x][y]; int max_defined = col_info.bottom_layer[x][y]; if (max_defined < 0) continue; int last_top = min_defined; // Verify assumptions for (int i = min_defined; i < max_defined; i++) { if (max_level[i] >= top_solid) last_top = i; if (max_level[i+1] < 0 && min_level[i] > top_solid) max_level[i+1] = min_level[i+1] = min_level[i]; if (max_level[i+1] > top_solid) continue; if (max_level[i+1] != min_level[i]-1) { out.printerr( "Gap or overlap with next layer %d at (%d,%d,%d-%d).\n", i+1, x+column.x*16, y+column.y*16, max_level[i+1], min_level[i] ); return false; } } for (int i = min_defined; i < max_defined; i++) { auto layer = biome->layers[i]; int size = max_level[i]-min_level[i]+1; if (size == layer->thickness) continue; // Adjust the top layers so that the bottom of the layer maps to the correct place if (max_level[i] >= top_solid) { max_level[i] += layer->thickness - size; if (size > layer->thickness) layer->setZBias(size - layer->thickness); continue; } // If below a thick soil layer, allow thickness to pass from prev to current. // This accounts for a probable bug in worldgen soil placement code. if (i > min_defined && i-1 <= last_top) { auto prev = biome->layers[i-1]; if (size > layer->thickness && prev->is_soil_layer && prev->thickness > 1 && size <= layer->thickness+prev->thickness-1) { max_level[i] += layer->thickness - size; layer->setZBias(size - layer->thickness); continue; } } out.printerr( "Layer height change in layer %d at (%d,%d,%d): %d instead of %d.\n", i, x+column.x*16, y+column.y*16, max_level[i], size, biome->layers[i]->thickness ); return false; } } } } return true; } bool VeinGenerator::scan_block_tiles(Block *b, df::coord2d column, int z) { bool aquifer = b->getRaw()->flags.bits.has_aquifer; for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { df::coord2d tile(x,y); GeoLayer *layer = mapLayer(b, tile); if (!layer) continue; auto tt = b->baseTiletypeAt(tile); bool wall = isWallTerrain(tt); bool matches = false; switch (tileMaterial(tt)) { case tiletype_material::MINERAL: { t_veinkey key(b->veinMaterialAt(tile),b->veinTypeAt(tile)); // Check if the vein material and type is reasonable if (unsigned(key.first) >= materials.size() || unsigned(key.second) >= NUM_INCLUSIONS) { out.printerr("Invalid vein code: %d %d - aborting.\n",key.first,key.second); return false; } int8_t &status = materials[key.first].valid_type[key.second]; if (status == -1) { // Report first occurence of unreasonable vein spec out.printerr( "Unexpected vein %s %s - ", MaterialInfo(0,key.first).getToken().c_str(), ENUM_KEY_STR(inclusion_type, key.second).c_str() ); status = materials[key.first].default_type; if (status < 0) out.printerr("will be left in place.\n"); else out.printerr( "correcting to %s.\n", ENUM_KEY_STR(inclusion_type, df::inclusion_type(status)).c_str() ); } if (status >= 0) { key.second = df::inclusion_type(status); matches = true; // Count minerals if (wall) { layer->mineral_tiles++; layer->mineral_count[key]++; } } break; } case tiletype_material::SOIL: matches = layer->is_soil; break; case tiletype_material::STONE: matches = !layer->is_soil; break; default:; } // This tile should be overwritten if (matches) { auto &col_info = layer->biome->columns(column); int max_level = col_info.max_level[x][y][layer->index] + layer->z_bias; GeoBlock *block = layer->blockAt(df::coord(column, max_level-z)); block->material[x][y] = SMC_LAYER; layer->tiles++; if (wall) { layer->unmined_tiles++; block->unmined.setassignment(x,y,true); } if (aquifer && b->getFlagAt(tile, df::tile_designation::mask_water_table)) { int depth = z - col_info.min_level[x][y][layer->index] + 1; layer->aquifer_depth = std::max(layer->aquifer_depth, depth); } } } } return true; } /* * Vein writing pass. Without running generation it just deletes all veins. */ void VeinGenerator::write_tiles() { for (int x = 0; x < size.x; x++) { for (int y = 0; y < size.y; y++) { df::coord2d column(x,y); int top = findTopBlock(map, x, y); for (int z = top; z >= 0; z--) { Block *b = map.BlockAt(df::coord(x,y,z)); if (!b || !b->is_valid()) continue; write_block_tiles(b, column, z); b->Write(); map.discardBlock(b); } map.trash(); } } } void VeinGenerator::write_block_tiles(Block *b, df::coord2d column, int z) { bool aquifer = b->getRaw()->flags.bits.has_aquifer; for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { df::coord2d tile(x,y); GeoLayer *layer = mapLayer(b, tile); if (!layer) continue; auto &col_info = layer->biome->columns(column); int max_level = col_info.max_level[x][y][layer->index] + layer->z_bias; GeoBlock *block = layer->getBlockAt(df::coord(column, max_level-z)); auto tt = b->baseTiletypeAt(tile); if (block && block->material[x][y] != SMC_NO_MAPPING) { bool ok; int mat = block->material[x][y]; df::inclusion_type vein = inclusion_type::CLUSTER; bool aquifer_tile = aquifer && (z-col_info.min_level[x][y][layer->index]) < layer->aquifer_depth; if (mat < 0) { if (layer->is_soil) ok = b->setSoilAt(tile, tt, true); else ok = b->setStoneAt(tile, tt, layer->material, vein, true, true); } else { aquifer_tile = aquifer_tile && materials[mat].can_support_aquifer; vein = (df::inclusion_type)block->veintype[x][y]; ok = b->setStoneAt(tile, tt, mat, vein, true, true); } if (aquifer) b->setFlagAt(tile, df::tile_designation::mask_water_table, aquifer_tile); if (!ok) { out.printerr( "Couldn't write %d vein at (%d,%d,%d)\n", mat, x+column.x*16, y+column.y*16, z ); } } else { // Otherwise just kill latent veins if they happen to be there if (tileMaterial(tt) != tiletype_material::MINERAL) b->setVeinMaterialAt(tile, -1); } } } } /* * Vein placement code */ bool GeoBlock::prepare_arena(int16_t basemat, NoiseFunction::Ptr fn) { arena_mask = arena_unmined = 0; arena_material = basemat; df::coord origin = pos + layer->world_pos; float x0 = float(origin.x)*16 + 0.5f, y0 = float(origin.y)*16 + 0.5f; float z = origin.z - layer->z_bias + 0.5f; fn->displace(x0, y0, z); for (int x = 0; x < 16; x++) { for (int y = 0; y < 16; y++) { if (material[x][y] != arena_material) continue; weight[x][y] = fn->eval(x0+x, y0+y, z); arena_mask |= (1< &arena, float threshold) { int count = 0; for (size_t i = 0; i < arena.size(); i++) count += arena[i]->measure_placement(threshold); return count; } void VeinExtent::link(GeoLayer *layer) { layers.push_back(layer); num_unmined += layer->unmined_tiles; num_layer += layer->tiles; if (layers.size() == 1) { min_z = layer->min_z(); max_z = layer->max_z(); } else { min_z = std::min(min_z, layer->min_z()); max_z = std::max(max_z, layer->max_z()); } } void VeinExtent::merge_into(Ptr target) { assert(target->vein == vein); target->num_tiles += num_tiles; for (size_t i = 0; i < layers.size(); i++) { target->link(layers[i]); layers[i]->veins[vein] = target; } num_tiles = 0; layers.clear(); } void VeinExtent::place_tiles() { std::vector arena; int env_material = parent_mat(); for (size_t i = 0; i < layers.size(); i++) { auto layer = layers[i]; for (auto bit = layer->block_list.begin(); bit != layer->block_list.end(); ++bit) { if ((*bit)->prepare_arena(env_material, distribution)) arena.push_back(*bit); } } // Binary search to meet the required number auto range = distribution->range(); float mid; for (int i = 0; i < 32; i++) // iteration limit { mid = (range.first + range.second) / 2; int count = placed_tiles = measure(arena, mid); if (count == num_tiles) break; else if (count > num_tiles) range.first = mid; else range.second = mid; } // Write the tiles out for (size_t i = 0; i < arena.size(); i++) arena[i]->place_tiles(mid, vein.first, vein.second); placed = true; } bool GeoLayer::form_veins(color_ostream &out) { std::vector refs; // Defunct layers cannot have veins if (tiles <= 0) return true; for (size_t i = 0; i < info->vein_mat.size(); i++) { int parent_id = info->vein_nested_in[i]; if (parent_id >= (int)refs.size()) { out.printerr("Forward vein reference in biome %d.\n", biome->info.geo_index); return false; } t_veinkey key(info->vein_mat[i], info->vein_type[i]); VeinExtent::Ptr &vptr = veins[key]; if (vptr) { int tgt_pmat = parent_id < 0 ? SMC_LAYER : info->vein_mat[parent_id]; int cur_pmat = vptr->parent_mat(); if (parent_id < 0) vptr->set_parent(VeinExtent::Ptr()); if (cur_pmat != tgt_pmat) { std::string ctx = "be anywhere"; if (vptr->parent) ctx = "only be in "+MaterialInfo(0,vptr->parent_mat()).getToken(); out.printerr( "Duplicate vein %s %s in biome %d layer %d - will %s.\n", MaterialInfo(0,key.first).getToken().c_str(), ENUM_KEY_STR(inclusion_type, key.second).c_str(), biome->info.geo_index, index, ctx.c_str() ); } vptr->probability = std::max(vptr->probability, info->vein_unk_38[i]); } else { vptr = VeinExtent::Ptr(new VeinExtent(key)); vptr->probability = info->vein_unk_38[i]; if (parent_id >= 0) vptr->set_parent(refs[parent_id]); vptr->add_tiles(mineral_count[key]); mineral_count.erase(key); vptr->link(this); refs.push_back(vptr); } } return true; } bool VeinGenerator::place_orphan(t_veinkey key, int size, GeoLayer *from) { std::map best; for (auto it = biomes.begin(); it != biomes.end(); ++it) { auto &layers = it->second->layers; for (size_t i = 0; i < layers.size(); i++) { if (layers[i]->mineral_tiles >= layers[i]->unmined_tiles) continue; VeinExtent::Ptr vein = map_find(layers[i]->veins, key); if (!vein) continue; int dist = std::min( layers[i]->min_z() - from->max_z(), from->min_z() - layers[i]->max_z() ); best[std::max(0, dist)].push_back(vein); } } if (best.empty()) { out.printerr( "Could not place orphaned vein %s %s anywhere.\n", MaterialInfo(0,key.first).getToken().c_str(), ENUM_KEY_STR(inclusion_type, key.second).c_str() ); return false; } for (auto it = best.begin(); size > 0 && it != best.end(); ++it) { auto &vec = it->second; int free = 0; for (size_t i = 0; i < vec.size(); i++) { auto layer = vec[i]->layers[0]; free += layer->unmined_tiles - layer->mineral_tiles; } float coeff = float(size)/free; for (size_t i = 0; i < vec.size(); i++) { auto layer = vec[i]->layers[0]; int cfree = std::max(0, layer->unmined_tiles - layer->mineral_tiles); int cnt = std::min(size, std::min(cfree, int(ceilf(cfree*coeff)))); vec[i]->add_tiles(cnt); layer->mineral_tiles += cnt; size -= cnt; } } if (size > 0) { out.printerr( "Could not place all of orphaned vein %s %s: %d left.\n", MaterialInfo(0,key.first).getToken().c_str(), ENUM_KEY_STR(inclusion_type, key.second).c_str(), size ); } return true; } bool VeinGenerator::form_veins() { // Form veins in layers for (auto it = biomes.begin(); it != biomes.end(); ++it) { auto &layers = it->second->layers; for (size_t i = 0; i < layers.size(); i++) if (layers[i] && !layers[i]->form_veins(out)) return false; } // Place orphaned minerals for (auto it = biomes.begin(); it != biomes.end(); ++it) { auto &layers = it->second->layers; for (size_t i = 0; i < layers.size(); i++) { auto &mins = layers[i]->mineral_count; for (auto mit = mins.begin(); mit != mins.end(); ++mit) { if (mit->second <= 0) continue; if (!place_orphan(mit->first, mit->second, layers[i])) return false; } } } // Join adjacent extents with the same density for (auto it = biomes.begin(); it != biomes.end(); ++it) { auto &layers = it->second->layers; for (size_t i = 0; i < layers.size(); i++) { auto &mins = layers[i]->veins; for (auto mit = mins.begin(); mit != mins.end(); ++mit) { auto cur = mit->second; if (!cur) continue; // Merge in this biome if (i > 0) { if (auto prev = map_find(layers[i-1]->veins, mit->first)) { if (cur->is_similar(prev)) { cur->merge_into(prev); continue; } } } // Merge across biomes auto &vec = veins[cur->vein]; for (size_t j = 0; j < vec.size(); j++) { if (vec[j]->min_z <= cur->max_z && vec[j]->max_z >= cur->min_z && cur->is_similar(vec[j])) { cur->merge_into(vec[j]); cur.reset(); break; } } if (cur) vec.push_back(cur); } } } return true; } void VeinGenerator::init_seeds() { MersenneRNG rng; std::string seed = world->worldgen.worldgen_parms.seed; seed.resize((seed.size()+3)&~3); rng.init((uint32_t*)seed.data(), seed.size()/4, 10); for (size_t i = 0; i < materials.size(); i++) { for (unsigned j = 0; j < NUM_INCLUSIONS; j++) materials[i].seeds[j] = rng.random(); } } NoiseFunction::Ptr VeinGenerator::get_noise(t_veinkey vein) { auto &seed = materials[vein.first]; auto &func = seed.funcs[vein.second]; if (!func) { MersenneRNG rng; rng.init(seed.seeds[vein.second], 10); func = NoiseFunction::Ptr(makeVeinDistribution(vein, rng)); } return func; } static bool vein_cmp(const VeinExtent::Ptr &a, const VeinExtent::Ptr &b) { return (a->parent_depth < b->parent_depth) || (a->parent_depth == b->parent_depth && a->density() < b->density()); } bool VeinGenerator::place_veins(bool verbose) { VeinExtent::PVec queue; init_seeds(); // Compute the placement queue for (auto it = veins.begin(); it != veins.end(); ++it) { auto &vec = it->second; for (size_t i = 0; i < vec.size(); i++) { auto key = vec[i]->vein; if (vec[i]->num_tiles <= 0) continue; if (!isStoneInorganic(key.first)) { out.printerr( "Invalid vein material: %s\n", MaterialInfo(0, key.first).getToken().c_str() ); return false; } if (!is_valid_enum_item(key.second)) { out.printerr("Invalid vein type: %d\n", key.second); return false; } vec[i]->distribution = get_noise(key); queue.push_back(vec[i]); } } sort(queue.begin(), queue.end(), vein_cmp); // Place tiles out.print("Processing... (%zu)", queue.size()); for (size_t j = 0; j < queue.size(); j++) { if (queue[j]->parent && !queue[j]->parent->placed) { out.printerr( "\nParent vein not placed for %s %s.\n", MaterialInfo(0,queue[j]->vein.first).getToken().c_str(), ENUM_KEY_STR(inclusion_type, queue[j]->vein.second).c_str() ); return false; } if (verbose) { if (j > 0) out.print("done."); out.print( "\nVein layer %zu of %zu: %s %s (%.2f%%)... ", j+1, queue.size(), MaterialInfo(0,queue[j]->vein.first).getToken().c_str(), ENUM_KEY_STR(inclusion_type, queue[j]->vein.second).c_str(), queue[j]->density() * 100 ); } else { out.print("\rVein layer %zu of %zu... ", j+1, queue.size()); out.flush(); } queue[j]->place_tiles(); } out.print("done.\n"); return true; } command_result cmd_3dveins(color_ostream &con, std::vector & parameters) { bool verbose = false; for (size_t i = 0; i < parameters.size(); i++) { if (parameters[i] == "verbose") verbose = true; else return CR_WRONG_USAGE; } CoreSuspender suspend; if (!Maps::IsValid()) { con.printerr("Map is not available!\n"); return CR_FAILURE; } if (!World::isFortressMode()) { con.printerr("Must be used in fortress mode!\n"); return CR_FAILURE; } VeinGenerator generator(con); con.print("Collecting statistics...\n"); if (!generator.init_biomes()) return CR_FAILURE; if (!generator.scan_tiles()) return CR_FAILURE; con.print("Generating veins...\n"); if (!generator.form_veins()) return CR_FAILURE; if (!generator.place_veins(verbose)) return CR_FAILURE; con.print("Writing tiles...\n"); generator.write_tiles(); return CR_OK; }