├── .gitignore ├── BiomeImage.cpp ├── BiomeImage.h ├── BiomeVectors.cpp ├── BiomeVectors.h ├── BlockEntityJSON.cpp ├── BlockEntityJSON.h ├── BlockType.cpp ├── BlockType.h ├── CaveVectors.cpp ├── CaveVectors.h ├── ElevationVectors.cpp ├── ElevationVectors.h ├── EntityJSON.cpp ├── EntityJSON.h ├── Makefile ├── MinecraftWorld.cpp ├── MinecraftWorld.h ├── NBTObject.cpp ├── NBTObject.h ├── README.md ├── ResourceVectors.cpp ├── ResourceVectors.h ├── SubChunk.cpp ├── SubChunk.h ├── VillageJSON.cpp ├── VillageJSON.h ├── basic_test.cpp ├── biome.cpp ├── biome.h ├── compute_mst.cpp ├── compute_mst.h ├── compute_ore_stats.cpp ├── compute_ore_stats.h ├── gen_vtk_grid.py ├── nlohmann └── json.hpp ├── parse_bedrock.cpp ├── parse_bedrock.h ├── polygon.cpp ├── polygon.h ├── show_clusters.cpp ├── snappy_compressor.cc.patch ├── table_test.cc.patch ├── type_mst.cpp ├── web_stuff ├── MyGeoJSON.js ├── dist │ └── sprites │ │ ├── Bed.png │ │ ├── Beehive.png │ │ ├── Chest.png │ │ ├── Furnace.png │ │ ├── Hopper.png │ │ ├── MobSpawner.png │ │ ├── Torch.png │ │ ├── Witch.png │ │ ├── bat.png │ │ ├── bee.png │ │ ├── bee_nest.png │ │ ├── bell.png │ │ ├── blaze.png │ │ ├── cat.png │ │ ├── cave_spider.png │ │ ├── chicken.png │ │ ├── cow.png │ │ ├── creeper.png │ │ ├── diamond_ore.png │ │ ├── dolphin.png │ │ ├── drowned.png │ │ ├── emerald_ore.png │ │ ├── enderman.png │ │ ├── ghast.png │ │ ├── golem.png │ │ ├── horse.png │ │ ├── item.png │ │ ├── lapis_ore.png │ │ ├── llama.png │ │ ├── ocelot.png │ │ ├── panda.png │ │ ├── parrot.png │ │ ├── pig.png │ │ ├── pillager.png │ │ ├── rabbit.png │ │ ├── sheep.png │ │ ├── skeleton.png │ │ ├── slime.png │ │ ├── spider.png │ │ ├── squid.png │ │ ├── torch.png │ │ ├── villager_v2.png │ │ ├── wandering_trader.png │ │ ├── whither.png │ │ ├── witch.png │ │ ├── wolf.png │ │ ├── xp_orb.png │ │ ├── zombie.png │ │ └── zombie_villager_v2.png ├── index.html ├── index.js ├── package-lock.json └── package.json └── write_top.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.o 3 | *.Td 4 | *.csv 5 | *.vtk 6 | **/biomeproperties.json 7 | web_stuff/dist/web_stuff.*.js.map 8 | web_stuff/dist/web_stuff.*.js 9 | web_stuff/dist/web_stuff.*.css 10 | web_stuff/dist/web_stuff.*.css.map 11 | web_stuff/dist/index.js 12 | web_stuff/dist/index.js.map 13 | web_stuff/dist/index.html 14 | web_stuff/node_modules 15 | web_stuff/.cache 16 | **/villages.json 17 | **/world.js 18 | **/entities.json 19 | **/elevations.json 20 | **/biomes.json 21 | base_tiles 22 | basic.log 23 | **/block_entities.json 24 | **/world.json 25 | basic_test 26 | type_mst 27 | write_top -------------------------------------------------------------------------------- /BiomeImage.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | // I really don't like using namespace... I prefer to fully qualify. 8 | // but it won't compile without. 9 | using namespace Magick; 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | BiomeImage::BiomeImage() 19 | { 20 | Magick::InitializeMagick("./foo"); 21 | } 22 | 23 | void BiomeImage::add_chunk(int chunkx, int chunkz, Grid16 biomes) 24 | { 25 | chunks[chunkx][chunkz] = biomes; 26 | 27 | Magick::Image image(Magick::Geometry(16*pixels_per_block, 16*pixels_per_block), Magick::Color(0,0,0,0) ); 28 | 29 | image.strokeWidth(0); 30 | 31 | for(int x=0; x<16; x++) { 32 | for(int y=0; y<16; y++) { 33 | auto b = get_biome(biomes[x][y]); 34 | Magick::Color c(QuantumRange/256*b.color.r, 35 | QuantumRange/256*b.color.g, 36 | QuantumRange/256*b.color.b, 37 | QuantumRange); 38 | image.fillColor(c); // Fill color 39 | image.draw( Magick::DrawableRectangle(x*pixels_per_block, y*pixels_per_block, 40 | x*pixels_per_block+pixels_per_block, y*pixels_per_block+pixels_per_block) ); 41 | } 42 | } 43 | 44 | std::stringstream ss; 45 | ss << "base_tiles/biome_" << chunkx << "_" << chunkz << ".png"; 46 | image.write(ss.str()); 47 | 48 | images[chunkx][chunkz] = image; 49 | 50 | } 51 | 52 | void BiomeImage::write(std::string filename) { 53 | Magick::InitializeMagick("./foo"); 54 | 55 | int xcl, xch, zcl, zch; 56 | bool first = true; 57 | 58 | for(auto &i1: chunks) { 59 | for(auto &i2: i1.second) { 60 | if (first) { 61 | xcl = xch = i1.first; 62 | zcl = zch = i2.first; 63 | first = false; 64 | } else { 65 | xcl = std::min(xcl, i1.first); 66 | xch = std::max(xch, i1.first); 67 | zcl = std::min(zcl, i2.first); 68 | zch = std::max(zch, i2.first); 69 | } 70 | } 71 | } 72 | 73 | // in minecraft, y increases down. so -3 is above -2 and -2 is above -1. 74 | // this is the same order as the google xyz tiling scheme. 75 | // x has the usual euclidean order. 76 | 77 | // need to 78 | const int max_tile_level = std::ceil(log(std::max(xch-xcl, zch-zcl))/log(2)); 79 | std::cout << "will need " << max_tile_level << "\n"; 80 | 81 | // 16 blocks per chunk and 16 pixels per block. 82 | int width = pixels_per_block*16*(xch-xcl+1); 83 | int height = pixels_per_block*16*(zch-zcl+1); 84 | 85 | std::cout << "image size: " << width << " x " << height << "\n"; 86 | std::cout << "block(chunk) bounds of the world " << 16*(xch-xcl+1) << "x" << 16*(zch-zcl+1); 87 | 88 | std::ofstream file; 89 | file.open("world.js"); 90 | file << "const world_info = {\n"; 91 | file << " chunk_xl: " << xcl << ",\n"; 92 | file << " chunk_zl: " << zcl << ",\n"; 93 | file << " chunk_xh: " << xch << ",\n"; 94 | file << " chunk_zh: " << zch << ",\n"; 95 | file << " max_tile_level: " << max_tile_level << ",\n"; 96 | file << " tile_0_size: " << pow(2, max_tile_level)*16 << ",\n"; 97 | 98 | file << " eojson: 1\n"; 99 | file << "}\n"; 100 | file << "exports.info = world_info;\n"; 101 | 102 | file.close(); 103 | 104 | std::string tile_base = "tiling"; 105 | boost::filesystem::create_directory(tile_base); 106 | 107 | std::string tile_dir = tile_base + "/" + boost::lexical_cast(max_tile_level); 108 | boost::filesystem::create_directory(tile_dir); 109 | 110 | std::map > levelimages; 111 | for(auto &i1 : images) { 112 | int chunkx = i1.first; 113 | for(auto &i2 : i1.second) { 114 | int chunkz = i2.first; 115 | Magick::Image &image = i2.second; 116 | 117 | // map tiles begin at zero. shift the minecraft coords to starts at 0. 118 | int tilex = chunkx-xcl; 119 | int tilez = chunkz-zcl; 120 | 121 | std::stringstream ss; 122 | ss << tile_dir << "/" << "biome_" << tilex << "_" << tilez << ".png"; 123 | image.write(ss.str()); 124 | 125 | levelimages[tilex][tilez] = image; 126 | } 127 | } 128 | 129 | for(int level = max_tile_level-1; level>=0; level--) { 130 | std::string tile_dir = tile_base + "/" + boost::lexical_cast(level); 131 | boost::filesystem::create_directory(tile_dir); 132 | 133 | std::map > levelimages_new; 134 | for(auto &i1 : levelimages) { 135 | int tilex = i1.first; 136 | for(auto &i2 : i1.second) { 137 | int tilez = i2.first; 138 | Magick::Image &image = i2.second; 139 | auto sbefore = image.size(); 140 | 141 | int xnew = tilex/2; 142 | int znew = tilez/2; 143 | 144 | if (levelimages_new[xnew].find(znew) == levelimages_new[xnew].end()) { 145 | levelimages_new[xnew][znew] = Magick::Image(Magick::Geometry(tile_size*2,tile_size*2), Magick::Color(0,0,0,0) ); 146 | } 147 | Magick::Image &newimage = levelimages_new[xnew][znew]; 148 | auto safter = newimage.size(); 149 | 150 | newimage.composite(image, tile_size*(tilex%2), tile_size*(tilez%2), Magick::OverCompositeOp); 151 | } 152 | } 153 | 154 | 155 | // the & referencs in these iterations are very important because we are modifying (resize) 156 | // the images. by default, auto is a value, not a reference 157 | // https://stackoverflow.com/a/13934813/23630 158 | for(auto &i1 : levelimages_new) { 159 | int tilex = i1.first; 160 | for(auto &i2 : i1.second) { 161 | int tilez = i2.first; 162 | Magick::Image &image = i2.second; 163 | 164 | auto sbefore = image.size(); 165 | image.resize(Magick::Geometry(tile_size,tile_size)); 166 | auto safter = image.size(); 167 | 168 | std::stringstream ss; 169 | ss << tile_dir << "/" << "biome_" << tilex << "_" << tilez << ".png"; 170 | image.write(ss.str()); 171 | } 172 | } 173 | 174 | levelimages = levelimages_new; 175 | } 176 | 177 | 178 | // not doing the big image 179 | return; 180 | 181 | 182 | Magick::Image image(Magick::Geometry(width, height), Magick::Color(0,0,0,0) ); 183 | 184 | std::cout << "image size " << width << "," << height << "\n"; 185 | for(auto i1:chunks) { 186 | int chunkx = i1.first; 187 | for(auto i2:i1.second) { 188 | int chunkz = i2.first; 189 | auto biomes = i2.second; 190 | //std::cout << "for chunk: " << chunkx << ", " << chunkz << "\n"; 191 | for(int x=0; x<16; x++) { 192 | for(int z=0; z<16; z++) { 193 | auto b = get_biome(biomes[x][z]); 194 | Magick::Color c(QuantumRange/256*b.color.r, 195 | QuantumRange/256*b.color.g, 196 | QuantumRange/256*b.color.b, 197 | QuantumRange); 198 | image.fillColor(c); // Fill color 199 | //std::cout << " biome at: " << (x+chunkx*16) << ", " << (z+chunkz*16) << " is "; 200 | //std::cout << b.name << "\n"; 201 | int xl = (x+ (chunkx - xcl)*16) * pixels_per_block; 202 | int zl = (z+ (chunkz - zcl)*16) * pixels_per_block; 203 | int xh = xl+pixels_per_block; 204 | int zh = zl+pixels_per_block; 205 | //std::cout << "drawing " << xl << "," << zl << "," << xh << "," << zh; 206 | //std::cout << " in color " << QuantumRange/256*b.color.r << "," << QuantumRange/256*b.color.g << ","; 207 | //std::cout << QuantumRange/256*b.color.b << "\n"; 208 | image.draw( Magick::DrawableRectangle(xl, zl, xh, zh) ); 209 | } 210 | } 211 | } 212 | } 213 | image.write(filename ); 214 | } 215 | -------------------------------------------------------------------------------- /BiomeImage.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #pragma once 4 | 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | typedef std::array, 16> Grid16; 13 | 14 | class BiomeImage { 15 | public: 16 | BiomeImage(); 17 | 18 | void add_chunk(int chunkx, int chunkz, Grid16); 19 | 20 | void write(std::string filename); 21 | 22 | static const int pixels_per_block = 4; 23 | static const int tile_size = pixels_per_block*16; 24 | 25 | private: 26 | 27 | std::map > chunks; 28 | std::map > images; 29 | }; 30 | -------------------------------------------------------------------------------- /BiomeVectors.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | //https://github.com/nlohmann/json#json-as-first-class-data-type 9 | // this json header is copied from the above github. I don't know how to include 10 | // a single file as a submodule and the git report itself is pretty large. 11 | #include 12 | 13 | BiomeVectors::BiomeVectors() 14 | { 15 | /* empty */ 16 | } 17 | 18 | void BiomeVectors::add_chunk(int chunkx, int chunkz, Grid16 biomes) 19 | { 20 | for(int x=0; x<16; x++) { 21 | for(int z=0; z<16; z++) { 22 | auto b = get_biome(biomes[x][z]); 23 | int xl = (chunkx*16+x); 24 | int zl = (chunkz*16+z); 25 | int xh = xl + 1; 26 | int zh = zl + 1; 27 | if (b.name == "") { 28 | std::cout << "adding blank biome\n"; 29 | } 30 | add_rectangle_to_polygon_set(polysets[b.id], xl, zl, xh, zh); 31 | } 32 | } 33 | } 34 | 35 | void BiomeVectors::write(std::string filename) 36 | { 37 | nlohmann::json top; 38 | 39 | top["type"] = "FeatureCollection"; 40 | 41 | for(auto ps : polysets) { 42 | const Biome &b = get_biome(ps.first); 43 | 44 | PolygonHolesSet merged; 45 | gtl::assign(merged, ps.second); 46 | 47 | for (auto poly : merged) { 48 | 49 | nlohmann::json feature; 50 | 51 | feature["type"] = "Feature"; 52 | feature["properties"]["biome"] = b.name; 53 | 54 | feature["geometry"]["type"] = "Polygon"; 55 | 56 | feature["geometry"]["coordinates"] = polygon_to_json(poly); 57 | 58 | top["features"].push_back(feature); 59 | } 60 | } 61 | 62 | std::ofstream file; 63 | file.open(filename); 64 | file << top.dump(2); 65 | file.close(); 66 | } 67 | -------------------------------------------------------------------------------- /BiomeVectors.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | 5 | #include 6 | #include 7 | 8 | class BiomeVectors { 9 | public: 10 | BiomeVectors(); 11 | 12 | void add_chunk(int chunkx, int chunkz, Grid16); 13 | 14 | void write(std::string filename); 15 | 16 | private: 17 | std::map polysets; 18 | 19 | }; 20 | -------------------------------------------------------------------------------- /BlockEntityJSON.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #define UNUSED(x) (void)(x) 8 | 9 | void GenerateBlockEntityJSON(MinecraftWorld &world, std::string filename) 10 | { 11 | 12 | boost::property_tree::ptree tree; 13 | 14 | tree.put("type", "FeatureCollection"); 15 | 16 | boost::property_tree::ptree features; 17 | 18 | int num = 0; 19 | for (auto i1 : world.chunk_block_entities) { 20 | int chunkx = i1.first; 21 | UNUSED(chunkx); 22 | for(auto i2 : i1.second) { 23 | int chunkz = i2.first; 24 | UNUSED(chunkz); 25 | for(auto entity : i2.second) { 26 | num++; 27 | 28 | std::cout << entity.get_string() << "\n"; 29 | 30 | boost::property_tree::ptree feature_node; 31 | 32 | feature_node.put("type", "Feature"); 33 | feature_node.put("properties.type", entity.get_type()); 34 | if (entity.get_type() == "MobSpawner") { 35 | auto spawnertype = entity.get_string_property("EntityIdentifier"); 36 | assert(spawnertype); 37 | feature_node.put("properties.spawner_type", *spawnertype); 38 | } 39 | 40 | 41 | // this seems like a goofy way to have a list. 42 | boost::property_tree::ptree coords; 43 | boost::property_tree::ptree coord; 44 | 45 | // the 0 coord is x, 1 coord is y (the height), 2 coord is z. 46 | std::vector position = entity.get_position(); 47 | 48 | // this first list is the full coordinates for reporting. 49 | for (int i : {0,1,2}) { 50 | coord.put("", position[i]); 51 | coords.push_back(std::make_pair("", coord)); 52 | } 53 | feature_node.add_child("properties.coords", coords); 54 | 55 | feature_node.put("geometry.type", "Point"); 56 | 57 | // this second list of coordinates is for the geojson format. 58 | // 0(x) and 2(z) are the normal "xy" coordinates 59 | coords.clear(); 60 | for (int i : {0,2}) { 61 | coord.put("", position[i]); 62 | coords.push_back(std::make_pair("", coord)); 63 | } 64 | 65 | feature_node.add_child("geometry.coordinates", coords); 66 | 67 | features.push_back(std::make_pair("", feature_node)); 68 | } 69 | } 70 | } 71 | 72 | tree.add_child("features", features); 73 | 74 | boost::property_tree::write_json(filename, tree); 75 | 76 | std::cout << "wrote " << num << " block entities\n"; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /BlockEntityJSON.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | 9 | void GenerateBlockEntityJSON(MinecraftWorld &world, std::string filename); 10 | -------------------------------------------------------------------------------- /BlockType.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | std::set earthly_types = { 8 | "minecraft:bedrock", 9 | "minecraft:cobblestone", 10 | "minecraft:dirt", 11 | "minecraft:farmland", 12 | "minecraft:mossy_cobblestone", 13 | "minecraft:sand", 14 | "minecraft:sandstone", 15 | "minecraft:stone", 16 | "minecraft:stonebrick", 17 | "minecraft:gravel", 18 | "minecraft:clay", 19 | }; 20 | 21 | std::set liquid_types = { 22 | "minecraft:flowing_lava", 23 | "minecraft:flowing_water", 24 | "minecraft:lava", 25 | "minecraft:magma", 26 | "minecraft:water" 27 | }; 28 | 29 | void BlockType::add_string(std::string tagname, std::string value) { 30 | // this is not a generic nbt parser. this is a minecraft 31 | // parser. If the current string tag has an nbt name "name", 32 | // it's the name of the palette entry. 33 | if (tagname == "name") { 34 | name = value; 35 | 36 | if (earthly_types.find(name) != earthly_types.end()) { 37 | earthly = true; 38 | } 39 | if (liquid_types.find(name) != liquid_types.end()) { 40 | liquid = true; 41 | } 42 | if (name == "minecraft:air") { 43 | air = true; 44 | } 45 | } else if ((tagname == "stone_type") || (tagname == "dirt_type")) { 46 | stone_dirt_type = value; 47 | } else { 48 | NBTObject::add_string(tagname, value); 49 | } 50 | } 51 | 52 | 53 | 54 | // seems like there'd be something like this in stl 55 | int mycmp(const std::string &a, const std::string &b) { return a.compare(b); } 56 | 57 | template 58 | int mycmp(T a, T b) { 59 | if (a==b) { return 0; } 60 | else if (a 65 | int map_compare(const T &a, const T &b) 66 | { 67 | // for the purposes of uniqness in the map, the actual order is not important. 68 | if (a.size() != b.size()) { 69 | return mycmp(a.size(), b.size()); 70 | } 71 | 72 | // At this point all of the maps have the same size. 73 | auto itera=a.begin(); 74 | auto iterb=b.begin(); 75 | for(/* got error if I try to include the auto assigns here */; 76 | itera!=a.end(); // the check above ensures a and b are the same size. 77 | itera++, iterb++) { 78 | if ((*itera).first != (*iterb).first) { return (*itera).first.compare((*iterb).first); } 79 | if ((*itera).second != (*iterb).second) { return mycmp((*itera).second, (*iterb).second); } 80 | } 81 | return 0; 82 | } 83 | 84 | 85 | static std::map palette; 86 | static std::vector bt_by_id; 87 | 88 | int BlockType::get_block_type_id(BlockType & bt) 89 | { 90 | auto iter = palette.find(bt.lookup_string()); 91 | if (iter != palette.end()) { 92 | return (*iter).second; 93 | } 94 | return -1; 95 | } 96 | 97 | void BlockType::add_block_type(BlockType &bt) 98 | { 99 | int id = get_block_type_id(bt); 100 | if (id != -1) { return; } 101 | 102 | if (palette.empty()) { 103 | palette.insert(std::pair("nullblock", 0)); 104 | bt_by_id.push_back(BlockType()); 105 | bt_by_id[0].name = "nullblock"; 106 | } 107 | 108 | id = palette.size(); 109 | 110 | palette.insert(std::pair(bt.lookup_string(), id) ); 111 | 112 | bt.id = id; 113 | bt_by_id.push_back(bt); 114 | } 115 | 116 | BlockType& BlockType::get_block_type_by_id(unsigned int id) 117 | { 118 | //std::cout << "id " << id << " " << bt_by_id.size() << "\n"; 119 | assert(id<=bt_by_id.size()); 120 | return bt_by_id[id]; 121 | } 122 | 123 | int BlockType::get_block_type_id_by_name(std::string name) 124 | { 125 | auto iter = palette.find(name); 126 | if (iter==palette.end()) { 127 | std::cout << name << " not found\n"; 128 | return -1; 129 | } else { 130 | std::cout << "id for " << name << " is " << (*iter).second << "\n"; 131 | return (*iter).second; 132 | } 133 | 134 | } 135 | 136 | void BlockType::print_block_types() 137 | { 138 | std::cout << "there are " << palette.size() << " palette entries\n"; 139 | 140 | int airid = BlockType::get_block_type_id_by_name("minecraft:air ()"); 141 | int total_blocks = 0; 142 | int total_non_air = 0; 143 | for(auto iter : palette) { 144 | total_blocks += bt_by_id[iter.second].count; 145 | if (iter.second != airid) { 146 | total_non_air += bt_by_id[iter.second].count; 147 | } 148 | } 149 | for(auto iter : palette) { 150 | std::cout << iter.first << " id: " << iter.second << " count: " << bt_by_id[iter.second].count << " " << 151 | (double)bt_by_id[iter.second].count / total_blocks * 100.0 << "% " << 152 | (double)bt_by_id[iter.second].count / total_non_air * 100.0 << "% non air " << 153 | "\n"; 154 | } 155 | } 156 | 157 | // this is a string generator for the purpose of palette lookup. 158 | // many of the block types have different configurations that I usually 159 | // don't care about. 160 | std::string BlockType::lookup_string() const 161 | { 162 | std::stringstream ss; 163 | ss << name << " ("; 164 | // add more to the string if you want to differentiate. 165 | ss << ")"; 166 | 167 | return ss.str(); 168 | } 169 | 170 | std::string BlockType::get_string() const 171 | { 172 | std::stringstream ss; 173 | ss << name << NBTObject::get_string(); 174 | 175 | return ss.str(); 176 | } 177 | 178 | std::ostream& operator << (std::ostream& o, const BlockType &bt) 179 | { 180 | o << bt.get_string(); 181 | return o; 182 | } 183 | -------------------------------------------------------------------------------- /BlockType.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef BLOCK_TYPE_HH 3 | #define BLOCK_TYPE_HH 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | #if 0 12 | // this was an attempt to do a map with block_type as a lookup key. It didn't work. don't know 13 | // why. didn't feel like debugging, but it bugs me that I don't know why it didn't work. 14 | struct cmpPalettes { 15 | bool operator()(const BlockType& a, const BlockType& b) const { 16 | if (a.name != b.name) { return a.name.compare(b.name); } 17 | 18 | int c = map_compare(a.byte_properties, b.byte_properties); 19 | if (c != 0) { return c==-1; } 20 | 21 | c = map_compare(a.int_properties, b.int_properties); 22 | if (c != 0) { return c==-1; } 23 | 24 | c = map_compare(a.string_properties, b.string_properties); 25 | if (c != 0) { return c==-1; } 26 | 27 | return false; 28 | } 29 | }; 30 | #endif 31 | 32 | class BlockType : public NBTObject { 33 | public: 34 | 35 | BlockType() : id(-1), count(0), earthly(false), liquid(false), air(false) { /* empty */ }; 36 | 37 | const std::string &get_name() const { return name; } 38 | 39 | bool is_earthly() { return earthly; } 40 | bool is_liquid() { return liquid; } 41 | bool is_air() { return air; } 42 | 43 | std::string get_string() const; 44 | std::string lookup_string() const; 45 | 46 | void add_byte(std::string tagname, uint8_t value) override { 47 | if (tagname == "infiniburn_bit") { 48 | infiniburn_bit = value; 49 | } else { 50 | NBTObject::add_byte(tagname, value); 51 | } 52 | }; 53 | 54 | void add_short(std::string tagname, int16_t value) override { 55 | if (tagname == "val") { 56 | // don't know what val is but I have this here to supress the stdout 57 | short_properties[tagname] = value; 58 | } else { 59 | NBTObject::add_short(tagname, value); 60 | } 61 | } 62 | 63 | void add_string(std::string tagname, std::string value) override; 64 | 65 | void add_int(std::string tagname, int32_t value) override { 66 | if (tagname == "version") { 67 | version = value; 68 | } else if (tagname == "liquid_depth") { 69 | liquid_depth = value; 70 | } else { 71 | NBTObject::add_int(tagname, value); 72 | } 73 | } 74 | 75 | void incr_count() { count++; }; 76 | 77 | static int get_block_type_id(BlockType &bt); 78 | static void add_block_type(BlockType &bt); 79 | static void print_block_types(); 80 | static BlockType &get_block_type_by_id(unsigned int id); 81 | static int get_block_type_id_by_name(std::string name); 82 | 83 | private: 84 | int id; 85 | int count; 86 | std::string name; 87 | std::string stone_dirt_type; 88 | uint8_t infiniburn_bit; 89 | int version; 90 | int liquid_depth; 91 | 92 | // this represents whether this is a solid block type. 93 | // but not just any solid block. one that's part of the ground. 94 | // dirt, stone,... 95 | bool earthly; 96 | 97 | bool liquid; 98 | bool air; 99 | 100 | }; 101 | 102 | std::ostream& operator << (std::ostream& o, const BlockType &bt); 103 | 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /CaveVectors.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | 9 | //https://github.com/nlohmann/json#json-as-first-class-data-type 10 | // this json header is copied from the above github. I don't know how to include 11 | // a single file as a submodule and the git report itself is pretty large. 12 | #include 13 | 14 | CaveVectors::CaveVectors(MinecraftWorld& world) 15 | { 16 | 17 | for (auto scix : world.chunks_by_y) { 18 | int chunkx = scix.first; 19 | UNUSED(chunkx); 20 | for (auto sciz : scix.second) { 21 | int chunkz = sciz.first; 22 | UNUSED(chunkz); 23 | 24 | Grid16 &chunk_top_earthly = world.top_earthly[chunkx][chunkz]; 25 | 26 | for (auto sciy : sciz.second) { 27 | int chunky = sciy.first; 28 | UNUSED(chunky); 29 | auto sc = sciy.second; 30 | 31 | 32 | 33 | for (auto iter=sc->begin(); iter!=sc->end(); ++iter) { 34 | auto loc = *iter; 35 | // The loc has real world coords. 36 | int rawx = loc.x - chunkx*16; 37 | int rawz = loc.z - chunkz*16; 38 | 39 | if (loc.y >= chunk_top_earthly[rawx][rawz]) { continue; } 40 | 41 | BlockType bt = BlockType::get_block_type_by_id(loc.type); 42 | 43 | if (bt.is_air() || bt.is_liquid()) { 44 | add_rectangle_to_polygon_set(polysets[loc.y/world.y_resolution], loc.x, loc.z, loc.x+1, loc.z+1); 45 | } 46 | } 47 | } 48 | } 49 | } 50 | } 51 | 52 | 53 | void CaveVectors::write(std::string filename) 54 | { 55 | nlohmann::json top; 56 | 57 | top["type"] = "FeatureCollection"; 58 | 59 | PolygonHolesSet prev; 60 | for(auto iter = polysets.rbegin(); iter!=polysets.rend(); iter++) { 61 | int elevation = (*iter).first; 62 | 63 | // we're going from upper elevations on down. each layer, merge in the polys from above. 64 | PolygonHolesSet &ps = (*iter).second; 65 | 66 | PolygonHolesSet merged; 67 | 68 | #if 0 69 | prev.insert(prev.end(), ps.begin(), ps.end()); 70 | std::cout << "for elevation: " << elevation << " we have " << prev.size() << " going in and "; 71 | gtl::assign(merged, prev); 72 | // can't I just assign above? 73 | prev = merged; 74 | std::cout << merged.size() << " coming out\n" << std::flush; 75 | #else 76 | std::cout << "for elevation: " << elevation << " we have " << ps.size() << " going in and "; 77 | gtl::assign(merged, ps); 78 | std::cout << merged.size() << " coming out\n" << std::flush; 79 | #endif 80 | 81 | for (auto poly : merged) { 82 | std::optional coords = polygon_to_json(poly); 83 | if (!coords) { continue; } 84 | 85 | nlohmann::json feature; 86 | 87 | feature["type"] = "Feature"; 88 | feature["properties"]["elevation"] = elevation; 89 | 90 | feature["geometry"]["type"] = "Polygon"; 91 | 92 | feature["geometry"]["coordinates"] = *coords; 93 | 94 | top["features"].push_back(feature); 95 | } 96 | } 97 | 98 | std::ofstream file; 99 | file.open(filename); 100 | file << top.dump(2); 101 | file.close(); 102 | 103 | } 104 | -------------------------------------------------------------------------------- /CaveVectors.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #pragma once 4 | 5 | 6 | #include 7 | #include 8 | 9 | class CaveVectors { 10 | 11 | public: 12 | CaveVectors(MinecraftWorld& world); 13 | void write(std::string filename); 14 | 15 | private: 16 | std::map polysets; 17 | }; 18 | -------------------------------------------------------------------------------- /ElevationVectors.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | // this file is basically the same as biome vectors. They should be merged. 9 | 10 | ElevationVectors::ElevationVectors(MinecraftWorld &world) 11 | : y_resolution(world.y_resolution) 12 | { 13 | /* empty */ 14 | } 15 | 16 | void ElevationVectors::add_chunk(int chunkx, int chunkz, Grid16 elevation) 17 | { 18 | for(int x=0; x<16; x++) { 19 | for(int z=0; z<16; z++) { 20 | auto e = elevation[x][z]; 21 | int xl = (chunkx*16+x); 22 | int zl = (chunkz*16+z); 23 | int xh = xl + 1; 24 | int zh = zl + 1; 25 | add_rectangle_to_polygon_set(polysets[e/y_resolution], xl, zl, xh, zh); 26 | } 27 | } 28 | } 29 | 30 | 31 | void ElevationVectors::write(std::string filename) 32 | { 33 | nlohmann::json top; 34 | 35 | top["type"] = "FeatureCollection"; 36 | 37 | PolygonHolesSet prev; 38 | for(auto iter = polysets.rbegin(); iter!=polysets.rend(); iter++) { 39 | //for(auto ps : polysets) { 40 | int elevation = (*iter).first; 41 | 42 | // we're going from upper elevations on down. each layer, merge in the polys from above. 43 | PolygonHolesSet &ps = (*iter).second; 44 | 45 | PolygonHolesSet merged; 46 | 47 | #if 0 48 | prev.insert(prev.end(), ps.begin(), ps.end()); 49 | std::cout << "for elevation: " << elevation << " we have " << prev.size() << " going in and "; 50 | gtl::assign(merged, prev); 51 | // can't I just assign above? 52 | prev = merged; 53 | std::cout << merged.size() << " coming out\n" << std::flush; 54 | #else 55 | std::cout << "for elevation: " << elevation << " we have " << ps.size() << " going in and "; 56 | gtl::assign(merged, ps); 57 | std::cout << merged.size() << " coming out\n" << std::flush; 58 | #endif 59 | 60 | for (auto poly : merged) { 61 | // if a poly simplifies to nothing. 62 | #if 0 63 | std::optional coords = polygon_to_json_simplified(poly); 64 | #else 65 | std::optional coords = polygon_to_json(poly); 66 | #endif 67 | if (!coords) { continue; } 68 | 69 | nlohmann::json feature; 70 | 71 | feature["type"] = "Feature"; 72 | feature["properties"]["elevation"] = elevation; 73 | 74 | feature["geometry"]["type"] = "Polygon"; 75 | 76 | feature["geometry"]["coordinates"] = *coords; 77 | 78 | top["features"].push_back(feature); 79 | } 80 | } 81 | 82 | std::ofstream file; 83 | file.open(filename); 84 | file << top.dump(2); 85 | file.close(); 86 | 87 | } 88 | -------------------------------------------------------------------------------- /ElevationVectors.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class ElevationVectors { 9 | public: 10 | ElevationVectors(MinecraftWorld &world); 11 | 12 | void add_chunk(int chunkx, int chunkz, Grid16); 13 | 14 | void write(std::string filename); 15 | 16 | private: 17 | std::map polysets; 18 | int y_resolution; 19 | }; 20 | -------------------------------------------------------------------------------- /EntityJSON.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #define UNUSED(x) (void)(x) 8 | 9 | void GenerateEntityJSON(MinecraftWorld &world, std::string filename) 10 | { 11 | 12 | boost::property_tree::ptree tree; 13 | 14 | tree.put("type", "FeatureCollection"); 15 | 16 | boost::property_tree::ptree features; 17 | 18 | int num = 0; 19 | int natural = 0; 20 | for (auto i1 : world.chunk_entities) { 21 | int chunkx = i1.first; 22 | UNUSED(chunkx); 23 | for(auto i2 : i1.second) { 24 | int chunkz = i2.first; 25 | UNUSED(chunkz); 26 | for(auto entity : i2.second) { 27 | num++; 28 | auto ns = entity->get_byte_property("NaturalSpawn"); 29 | if (ns and *ns) { 30 | natural++; 31 | } 32 | 33 | std::cout << "got entity of type: " << entity->get_type() << " at "; 34 | std::cout << entity->get_position().get_string() << " and rot: "; 35 | std::cout << entity->get_rotation().get_string(); 36 | std::cout << " uid: " << entity->get_unique_id(); 37 | 38 | for(auto def : entity->get_definitions().get_defs()) { 39 | std::cout << " " << def; 40 | } 41 | std::cout << "\n";; 42 | 43 | boost::property_tree::ptree feature_node; 44 | 45 | feature_node.put("type", "Feature"); 46 | feature_node.put("properties.type", entity->get_type()); 47 | 48 | if (entity->get_type() == "minecraft:item") { 49 | std::cout << "more info " << entity->get_string() << "\n"; 50 | feature_node.put("properties.item_type", entity->get_name()); 51 | if (entity->get_name() == "") { 52 | std::cout << "no subtype\n"; 53 | } 54 | } 55 | 56 | // this seems like a goofy way to have a list. 57 | boost::property_tree::ptree coords; 58 | boost::property_tree::ptree coord; 59 | 60 | // the 0 coord is x, 1 coord is y (the height), 2 coord is z. 61 | 62 | // this first list is the full coordinates for reporting. 63 | for (int i : {0,1,2}) { 64 | coord.put("", entity->get_position().get_value(i)); 65 | coords.push_back(std::make_pair("", coord)); 66 | } 67 | feature_node.add_child("properties.coords", coords); 68 | 69 | feature_node.put("geometry.type", "Point"); 70 | 71 | // this second list of coordinates is for the geojson format. 72 | // 0(x) and 2(z) are the normal "xy" coordinates 73 | coords.clear(); 74 | for (int i : {0,2}) { 75 | coord.put("", entity->get_position().get_value(i)); 76 | coords.push_back(std::make_pair("", coord)); 77 | } 78 | 79 | feature_node.add_child("geometry.coordinates", coords); 80 | 81 | features.push_back(std::make_pair("", feature_node)); 82 | } 83 | } 84 | } 85 | 86 | tree.add_child("features", features); 87 | 88 | boost::property_tree::write_json(filename, tree); 89 | 90 | std::cout << "wrote " << num << " entities\n"; 91 | std::cout << "wrote " << natural << " natural spawns\n"; 92 | 93 | } 94 | -------------------------------------------------------------------------------- /EntityJSON.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #pragma once 5 | 6 | #include 7 | 8 | void GenerateEntityJSON(MinecraftWorld &world, std::string filename); 9 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | all: type_mst basic_test show_clusters write_top 6 | 7 | 8 | .SUFFIXES: 9 | 10 | # add -pg for profiling 11 | OPT = -ggdb3 12 | #OPT = -O2 -ggdb3 13 | 14 | CPPFLAGS = -Wall -std=c++17 $(OPT) -pthread -DDLLX= -I. -I../leveldb-mcpe/include -fno-builtin-memcmp -pthread -DOS_LINUX -DLEVELDB_PLATFORM_POSIX -DLEVELDB_ATOMIC_PRESENT -DSNAPPY 15 | 16 | LDDFLAGS = -L../leveldb-mcpe/out-static -lleveldb -L../snappy/install/lib -lsnappy -lpthread -lz -lboost_program_options -lboost_filesystem -lboost_system 17 | 18 | ifneq ("$(wildcard /usr/lib/x86_64-linux-gnu/ImageMagick-6.9.7/bin-q16/Magick++-config)","") 19 | IMAGICK_CPP = $(shell /usr/lib/x86_64-linux-gnu/ImageMagick-6.9.7/bin-q16/Magick++-config --cxxflags) 20 | IMAGICK_LDD = $(shell /usr/lib/x86_64-linux-gnu/ImageMagick-6.9.7/bin-q16/Magick++-config --ldflags) 21 | else 22 | IMAGICK_CPP = $(shell Magick++-config --cxxflags) 23 | IMAGICK_LDD = $(shell Magick++-config --ldflags) 24 | endif 25 | 26 | DEPFLAGS = -MT $@ -MMD -MP -MF $*.Td 27 | CPPFLAGS += $(DEPFLAGS) 28 | 29 | COMMON_OBJS := parse_bedrock.o MinecraftWorld.o BlockType.o SubChunk.o polygon.o biome.o BiomeVectors.o BiomeImage.o NBTObject.o EntityJSON.o BlockEntityJSON.o VillageJSON.o ElevationVectors.o CaveVectors.o ResourceVectors.o 30 | 31 | %.o:%.cpp 32 | echo IMAGIC $(IMAGICK_CPP) 33 | g++ $(CPPFLAGS) $(IMAGICK_CPP) $(DEPFLAGS) -c -o $@ $< 34 | 35 | basic_test: basic_test.o $(COMMON_OBJS) 36 | g++ $(OPT) $(IMAGICK_LDD) -o $@ $^ $(IMAGICK_LDD) $(LDDFLAGS) 37 | 38 | type_mst: type_mst.o compute_mst.o $(COMMON_OBJS) 39 | g++ $(OPT) -o $@ $^ -lCGAL -lgmp $(IMAGICK_LDD) $(LDDFLAGS) 40 | 41 | show_clusters: show_clusters.o compute_mst.o compute_ore_stats.o $(COMMON_OBJS) 42 | g++ $(OPT) -o $@ $^ -lCGAL -lgmp $(IMAGICK_LDD) $(LDDFLAGS) 43 | 44 | write_top: write_top.o $(COMMON_OBJS) 45 | g++ $(OPT) -o $@ $^ -lCGAL -lgmp $(IMAGICK_LDD) $(LDDFLAGS) 46 | 47 | clean: 48 | rm *o *.Td type_mst basic_test 49 | 50 | 51 | 52 | include $(wildcard *.Td) 53 | -------------------------------------------------------------------------------- /MinecraftWorld.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | //#include 9 | #include 10 | 11 | //https://github.com/nlohmann/json#json-as-first-class-data-type 12 | // this json header is copied from the above github. I don't know how to include 13 | // a single file as a submodule and the git report itself is pretty large. 14 | #include 15 | 16 | void MinecraftWorld::populate_extra() 17 | { 18 | for (auto scix : theworld) { 19 | int chunkx = scix.first; 20 | UNUSED(chunkx); 21 | for (auto sciy : scix.second) { 22 | int chunky = sciy.first; 23 | UNUSED(chunky); 24 | for (auto sciz : sciy.second) { 25 | int chunkz = sciz.first; 26 | UNUSED(chunkz); 27 | auto sc = sciz.second; 28 | 29 | chunks_by_y[chunkx][chunkz][chunky] = sc; 30 | } 31 | } 32 | } 33 | 34 | 35 | for (auto scix : chunks_by_y) { 36 | int chunkx = scix.first; 37 | UNUSED(chunkx); 38 | for (auto sciz : scix.second) { 39 | int chunkz = sciz.first; 40 | UNUSED(chunkz); 41 | 42 | 43 | // This is the highed elevation of a stone or dirt block at each location. 44 | Grid16 &chunk_top_earthly = top_earthly[chunkx][chunkz]; 45 | 46 | for (auto sciy : sciz.second) { 47 | int chunky = sciy.first; 48 | UNUSED(chunky); 49 | auto sc = sciy.second; 50 | for (auto iter=sc->begin(); iter!=sc->end(); ++iter) { 51 | auto loc = *iter; 52 | 53 | BlockType bt = BlockType::get_block_type_by_id(loc.type); 54 | 55 | // The loc has real world coords. 56 | int rawx = loc.x - chunkx*16; 57 | int rawz = loc.z - chunkz*16; 58 | 59 | if (bt.is_earthly()) { 60 | chunk_top_earthly[rawx][rawz] = std::max(chunk_top_earthly[rawx][rawz], loc.y); 61 | } 62 | } 63 | } 64 | 65 | #if 0 66 | std::cout << "for " << chunkx << ", " << chunkz << "\n"; 67 | for(int x=0; x<16; x++) { 68 | std::cout << "x"; 69 | for(int z=0; z<16; z++) { 70 | std::cout << " " << chunk_top_earthly[x][z]; 71 | } 72 | std::cout << "\n"; 73 | } 74 | #endif 75 | } 76 | } 77 | 78 | } 79 | 80 | void MinecraftWorld::write_world_json(std::string filename) 81 | { 82 | 83 | int xcl, xch, zcl, zch; 84 | bool first = true; 85 | 86 | // I'm iterating on chunk biomes because world.js was originally used to 87 | // bound biome image tiles display 88 | for(auto it1 : chunk_biomes) { 89 | int chunkx = it1.first; 90 | for(auto it2 : it1.second) { 91 | int chunkz = it2.first; 92 | if (first) { 93 | xcl = xch = chunkx; 94 | zcl = zch = chunkz; 95 | first = false; 96 | } else { 97 | xcl = std::min(xcl, chunkx); 98 | xch = std::max(xch, chunkx); 99 | zcl = std::min(zcl, chunkz); 100 | zch = std::max(zch, chunkz); 101 | } 102 | } 103 | } 104 | 105 | nlohmann::json top; 106 | 107 | const int max_tile_level = std::ceil(log(std::max(xch-xcl, zch-zcl))/log(2)); 108 | 109 | top["chunk_xl"] = xcl; 110 | top["chunk_zl"] = zcl; 111 | top["chunk_xh"] = xch; 112 | top["chunk_zh"] = zch; 113 | top["max_tile_level"] = max_tile_level; 114 | top["tile_0_size"] = pow(2, max_tile_level)*16; 115 | top["y_resolution"] = y_resolution; 116 | 117 | auto end = std::chrono::system_clock::now(); 118 | std::time_t end_time = std::chrono::system_clock::to_time_t(end); 119 | 120 | top["update_time"] = std::ctime(&end_time); 121 | 122 | 123 | std::ofstream file; 124 | file.open(filename); 125 | file << top.dump(2); 126 | file.close(); 127 | } 128 | 129 | void real_to_chunk(int realcoord, int &chunkcoord, int &offset) 130 | { 131 | chunkcoord = realcoord/16; 132 | offset = realcoord - chunkcoord*16; 133 | if (offset < 0) { 134 | chunkcoord--; 135 | offset = realcoord - chunkcoord*16; 136 | } 137 | assert(offset<16 && offset>=0); 138 | } 139 | 140 | int MinecraftWorld::num_chunks() 141 | { 142 | std::set > setofchunks; 143 | 144 | for (auto scix : theworld) { 145 | int chunkx = scix.first; 146 | for (auto sciy : scix.second) { 147 | //int chunky = sciy.first; 148 | for (auto sciz : sciy.second) { 149 | int chunkz = sciz.first; 150 | //auto sc = sciz.second; 151 | 152 | setofchunks.insert(std::make_pair(chunkx, chunkz)); 153 | } 154 | } 155 | } 156 | 157 | // I have to do it this way because not all subchunks are stores. 158 | return setofchunks.size(); 159 | } 160 | 161 | 162 | void 163 | MinecraftWorld::set_type_at(int realx, int realy, int realz, uint8_t type) 164 | { 165 | int chunkx, x; 166 | real_to_chunk(realx, chunkx, x); 167 | int chunky, y; 168 | real_to_chunk(realy, chunky, y); 169 | int chunkz, z; 170 | real_to_chunk(realz, chunkz, z); 171 | 172 | SubChunk *subchunk = theworld[chunkx][chunky][chunkz]; 173 | subchunk->chunkx = chunkx; 174 | subchunk->chunky = chunky; 175 | subchunk->chunkz = chunkz; 176 | 177 | subchunk->set_type_at(x,y,z, type); 178 | } 179 | 180 | uint8_t 181 | MinecraftWorld::get_type_at(int realx, int realy, int realz) 182 | { 183 | 184 | int chunkx, x; 185 | real_to_chunk(realx, chunkx, x); 186 | int chunky, y; 187 | real_to_chunk(realy, chunky, y); 188 | int chunkz, z; 189 | real_to_chunk(realz, chunkz, z); 190 | 191 | if (theworld.find(chunkx) == theworld.end()) { 192 | return 0; 193 | } 194 | if (theworld[chunkx].find(chunky) == theworld[chunkx].end()) { 195 | return 0; 196 | } 197 | 198 | if (theworld[chunkx][chunky].find(chunkz) == theworld[chunkx][chunky].end()) { 199 | return 0; 200 | } 201 | 202 | // a return value of 0 means undefined. It's not in the database 203 | return theworld[chunkx][chunky][chunkz]->get_type_at(x,y,z); 204 | 205 | } 206 | 207 | 208 | SubChunk * 209 | MinecraftWorld::get_sub_chunk(int chunkx, int chunky, int chunkz) 210 | { 211 | if (theworld[chunkx][chunky].find(chunkz) == theworld[chunkx][chunky].end()) { 212 | theworld[chunkx][chunky][chunkz] = new SubChunk(); 213 | } 214 | 215 | SubChunk *subchunk = theworld[chunkx][chunky][chunkz]; 216 | subchunk->chunkx = chunkx; 217 | subchunk->chunky = chunky; 218 | subchunk->chunkz = chunkz; 219 | return subchunk; 220 | } 221 | 222 | 223 | void MinecraftWorld::populate_entity_table() 224 | { 225 | for(auto it1 : chunk_entities) { 226 | for(auto it2 : it1.second) { 227 | for(auto entity : it2.second) { 228 | entities_by_id[entity->get_unique_id()] = entity; 229 | } 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /MinecraftWorld.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef MINECRAFT_WORLD_HH 4 | #define MINECRAFT_WORLD_HH 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | #define UNUSED(x) (void)(x) 15 | 16 | // based on https://www.artificialworlds.net/blog/2017/05/11/c-iterator-example-and-an-iterable-range/ 17 | 18 | class MinecraftWorld { 19 | public: 20 | MinecraftWorld() : y_resolution(4) { 21 | /* empty */ 22 | } 23 | 24 | void write_world_json(std::string filename); 25 | 26 | // this method is called after parsing and populates some extra convenience 27 | // data structs. 28 | void populate_extra(); 29 | 30 | std::map > > theworld; 31 | std::map > chunk_biomes; 32 | 33 | std::map > chunk_elevation; 34 | 35 | std::map > > chunk_entities; 36 | std::map entities_by_id; 37 | 38 | std::map > > chunk_block_entities; 39 | 40 | std::map > > chunks_by_y; 41 | 42 | // This is the highed elevation of a stone or dirt block at each location. 43 | std::map > top_earthly; 44 | 45 | // can't populate this table during parsing because entities can move around due to 46 | // vector resizing. 47 | void populate_entity_table(); 48 | 49 | int num_chunks(); 50 | 51 | void set_type_at(int x, int y, int z, uint8_t type); 52 | 53 | // a return value of 0 means undefined. It's not in the database 54 | uint8_t get_type_at(int x, int y, int z); 55 | 56 | SubChunk *get_sub_chunk(int chunkx, int chunky, int chunkz); 57 | 58 | VillageType &get_village(std::string vname) { 59 | auto it = villages.find(vname); 60 | if (it == villages.end()) { 61 | return (*(villages.insert(std::make_pair(vname, VillageType(vname))).first)).second; 62 | } else { 63 | return (*it).second; 64 | } 65 | } 66 | 67 | std::optional get_entity_by_id(long id) { 68 | auto it = entities_by_id.find(id); 69 | if (it == entities_by_id.end()) { 70 | return std::optional(); 71 | } else { 72 | EntityType *e = (*it).second; 73 | return std::optional(e); 74 | } 75 | } 76 | 77 | std::map villages; 78 | 79 | const int y_resolution; 80 | 81 | }; 82 | 83 | 84 | 85 | #endif 86 | -------------------------------------------------------------------------------- /NBTObject.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | 5 | #include 6 | std::string NBTObject::indent; 7 | 8 | // this class is just to force printing some memory stats 9 | class NBTObjectProperties { 10 | public: 11 | NBTObjectProperties() { 12 | std::cout << "memory properties for NBTObjects\n"; 13 | std::cout << "NBTObject " << sizeof(NBTObject) << "\n"; 14 | std::cout << "Equipment " << sizeof(EquipmentType) << "\n"; 15 | std::cout << "TupleType " << sizeof(TupleType) << "\n"; 16 | std::cout << "EntityType " << sizeof(EntityType) << "\n"; 17 | 18 | } 19 | }; 20 | 21 | static NBTObjectProperties justadummyvar; 22 | 23 | std::string NBTObject::get_string() const 24 | { 25 | std::stringstream ss; 26 | ss << "("; 27 | 28 | bool first = true; 29 | for(auto elt : byte_properties) { 30 | if (!first) { 31 | ss << ", "; 32 | } 33 | first = false; 34 | ss << elt.first << ":" << std::hex << static_cast(elt.second) << std::dec; 35 | } 36 | for(auto elt : int_properties) { 37 | if (!first) { 38 | ss << ", "; 39 | } 40 | first = false; 41 | ss << elt.first << ":" << std::hex << elt.second << std::dec; 42 | } 43 | for(auto elt : short_properties) { 44 | if (!first) { 45 | ss << ", "; 46 | } 47 | first = false; 48 | ss << elt.first << ":" << std::hex << elt.second << std::dec; 49 | } 50 | for(auto elt : string_properties) { 51 | if (!first) { 52 | ss << ", "; 53 | } 54 | first = false; 55 | ss << elt.first << ":" << elt.second; 56 | } 57 | 58 | ss << ")"; 59 | return ss.str(); 60 | } 61 | 62 | std::string TupleType::get_string() 63 | { 64 | std::stringstream ss; 65 | ss << " ("; 66 | bool first = true; 67 | for(auto value : values) { 68 | if (!first) { ss << ", "; } 69 | first = false; 70 | ss << value << std::dec; 71 | } 72 | ss << ")"; 73 | return ss.str(); 74 | } 75 | 76 | static std::set known_byte_properties = { 77 | "Chested", 78 | "Color", 79 | "Color2", 80 | "Dead", 81 | "Invulnerable", 82 | "IsAngry", 83 | "IsAutonomous", 84 | "IsBaby", 85 | "IsEating", 86 | "IsGliding", 87 | "IsGlobal", 88 | "IsIllagerCaptain", 89 | "IsOrphaned", 90 | "IsPregnant", 91 | "IsRoaring", 92 | "IsScared", 93 | "IsStunned", 94 | "IsSwimming", 95 | "IsTamed", 96 | "IsTrusting", 97 | "LootDropped", 98 | "NaturalSpawn", 99 | "OnGround", 100 | "Persistent", 101 | "RewardPlayersOnFirstFounding", 102 | "Saddled", 103 | "Sheared", 104 | "ShowBottom", 105 | "Sitting", 106 | "Surface", 107 | "hasBoundOrigin", 108 | }; 109 | 110 | static std::set known_short_properties = { 111 | "Air", 112 | "AttackTime", 113 | "DeathTime", 114 | "Fire", 115 | "HurtTime", 116 | }; 117 | 118 | static std::set known_int_properties = { 119 | "LastDimensionId", 120 | "MarkVariant", 121 | "PortalCooldown", 122 | "SkinID", 123 | "Strength", 124 | "StrengthMax", 125 | "TradeExperience", 126 | "TradeTier", 127 | "Variant", 128 | "boundX", 129 | "boundY", 130 | "boundZ", 131 | "limitedLife", 132 | }; 133 | 134 | 135 | void EntityType::add_byte(std::string tagname, uint8_t value) 136 | { 137 | 138 | if (known_byte_properties.find(tagname) != known_byte_properties.end()) { 139 | byte_properties[tagname] = value; 140 | } else { 141 | NBTObject::add_byte(tagname, value); 142 | } 143 | } 144 | 145 | void EntityType::add_short(std::string tagname, int16_t value) 146 | { 147 | if (known_short_properties.find(tagname) != known_short_properties.end()) { 148 | short_properties[tagname] = value; 149 | } else { 150 | NBTObject::add_short(tagname, value); 151 | } 152 | } 153 | 154 | void EntityType::add_int(std::string tagname, int32_t value) 155 | { 156 | if (known_int_properties.find(tagname) != known_int_properties.end()) { 157 | int_properties[tagname] = value; 158 | } else { 159 | NBTObject::add_int(tagname, value); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /NBTObject.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | class NBTObject { 16 | 17 | public: 18 | static std::string indent; 19 | 20 | virtual std::string get_string() const; 21 | 22 | 23 | virtual void add_byte(std::string tagname, uint8_t value) { 24 | std::cout << indent << "byte " << tagname << ":" << std::hex << (unsigned int) value << std::dec << "\n"; 25 | byte_properties[tagname] = value; 26 | }; 27 | 28 | virtual void add_int(std::string tagname, int32_t value) { 29 | std::cout << indent << "int " << tagname << "(" << value << ")\n"; 30 | int_properties[tagname] = value; 31 | } 32 | 33 | virtual void add_short(std::string tagname, int16_t value) { 34 | std::cout << indent << "short " << tagname << "(" << value << ")\n"; 35 | short_properties[tagname] = value; 36 | } 37 | 38 | virtual void add_string(std::string tagname, std::string value) { 39 | std::cout << indent << "astring " << tagname << ":" << value << ":\n"; 40 | string_properties[tagname] = value; 41 | } 42 | 43 | virtual void add_long(std::string tagname, long value) { 44 | std::cout << indent << "long " << tagname << "(" << value << ")\n"; 45 | long_properties[tagname] = value; 46 | } 47 | 48 | virtual std::optional get_byte_property(std::string tagname) { 49 | auto i = byte_properties.find(tagname); 50 | if (i==byte_properties.end()) { 51 | return std::optional(); 52 | } else { 53 | return (*i).second; 54 | } 55 | 56 | } 57 | 58 | virtual std::optional get_string_property(std::string tagname) { 59 | auto i = string_properties.find(tagname); 60 | if (i==string_properties.end()) { 61 | return std::optional(); 62 | } else { 63 | return (*i).second; 64 | } 65 | 66 | } 67 | 68 | protected: 69 | std::map byte_properties; 70 | std::map int_properties; 71 | std::map short_properties; 72 | std::map string_properties; 73 | std::map long_properties; 74 | }; 75 | 76 | 77 | class Attribute : public NBTObject { 78 | public: 79 | std::string get_string() const override { 80 | std::stringstream ss; 81 | ss << "Attribute: " << name << "(" << Base << "," << Current << "," << Max << "," << Amount; 82 | ss << NBTObject::get_string(); 83 | return ss.str(); 84 | } 85 | 86 | virtual void add_string(std::string tagname, std::string value) override { 87 | if (tagname == "Name") { 88 | name = value; 89 | } else { 90 | NBTObject::add_string(tagname, value); 91 | } 92 | } 93 | 94 | virtual void add_float(std::string tagname, float value) { 95 | if (tagname == "Base") { 96 | Base = value; 97 | } else if (tagname == "Current") { 98 | Current = value; 99 | } else if (tagname == "Max") { 100 | Max = value; 101 | } else if (tagname == "Amount") { 102 | Amount = value; 103 | } else { 104 | std::cout << "unexpected attr tag " << tagname << "\n"; 105 | } 106 | } 107 | private: 108 | std::string name; 109 | float Base; 110 | float Current; 111 | float Max; 112 | float Amount; 113 | }; 114 | 115 | class TupleType : public NBTObject { 116 | public: 117 | void add_value(float value) { 118 | values.push_back(value); 119 | } 120 | float get_value(int i) { return values[i]; } 121 | 122 | std::string get_string(); 123 | private: 124 | std::vector values; 125 | }; 126 | 127 | class PositionType : public TupleType { 128 | // empty 129 | }; 130 | 131 | class RotationType : public TupleType { 132 | // empty. the action is in the tuple. this type is just for error catching. 133 | }; 134 | 135 | class MotionType : public TupleType { 136 | // empty 137 | }; 138 | 139 | class DefinitionsType : public NBTObject { 140 | public: 141 | std::set get_defs() { return defs; } 142 | 143 | void add_string(std::string tagname, std::string value) override { 144 | if (tagname == "") { 145 | defs.insert(value); 146 | } else { 147 | NBTObject::add_string(tagname, value); 148 | } 149 | } 150 | 151 | private: 152 | std::set defs; 153 | }; 154 | 155 | class EquipmentType : public NBTObject { 156 | public: 157 | void add_byte(std::string tagname, uint8_t value) override { 158 | if (tagname == "Count") { 159 | Count = value; 160 | } else { 161 | NBTObject::add_byte(tagname, value); 162 | } 163 | } 164 | 165 | void add_short(std::string tagname, int16_t value) override { 166 | if (tagname == "Damage") { 167 | Damage = value; 168 | } else { 169 | NBTObject::add_short(tagname, value); 170 | } 171 | } 172 | 173 | void add_int(std::string tagname, int32_t value) override { 174 | if (tagname == "Damage") { 175 | Damage = value; 176 | } else { 177 | NBTObject::add_int(tagname, value); 178 | } 179 | } 180 | 181 | void add_string(std::string tagname, std::string value) override { 182 | if (tagname == "Name") { 183 | name = value; 184 | } else { 185 | NBTObject::add_string(tagname, value); 186 | } 187 | } 188 | private: 189 | uint8_t Count; 190 | uint16_t Damage; 191 | std::string name; 192 | }; 193 | 194 | 195 | 196 | class EntityType : public NBTObject { 197 | public: 198 | std::string get_type() { return type; }; 199 | long get_unique_id() { return unique_id; }; 200 | std::string get_name() { return name; }; 201 | 202 | EquipmentType &get_armor(int id) { 203 | assert(sizeof(armor)/sizeof(armor[0]) > (long unsigned int) id); 204 | return armor[id]; 205 | }; 206 | 207 | Attribute &get_attr(int id) { 208 | if ((long unsigned int)id == attrs.size()) { 209 | attrs.push_back(Attribute()); 210 | return attrs.back(); 211 | } else { 212 | return attrs[id]; 213 | } 214 | }; 215 | 216 | RotationType &get_rotation() { return rotation; } 217 | PositionType &get_position() { return position; } 218 | MotionType &get_motion() { return motion; } 219 | 220 | EquipmentType &get_main_hand() { return main_hand; } 221 | EquipmentType &get_off_hand() { return off_hand; } 222 | EquipmentType &get_item_in_hand() { return item_in_hand; } 223 | 224 | DefinitionsType &get_definitions() { return definitions; } 225 | 226 | void add_string(std::string tagname, std::string value) override { 227 | if (tagname == "identifier") { 228 | type = value; 229 | } else if ((tagname == "name") || (tagname == "Name")) { 230 | name = value; 231 | } else { 232 | NBTObject::add_string(tagname, value); 233 | } 234 | }; 235 | 236 | void add_long(std::string tagname, long value) override { 237 | if (tagname == "UniqueID") { 238 | unique_id = value; 239 | } else { 240 | NBTObject::add_long(tagname, value); 241 | } 242 | } 243 | 244 | // these are in cpp because I filter messages for a long list of known property names. 245 | void add_byte(std::string tagname, uint8_t value) override; 246 | void add_short(std::string tagname, int16_t value) override; 247 | void add_int(std::string tagname, int32_t value) override; 248 | private: 249 | 250 | EquipmentType armor[4]; 251 | std::vector attrs; 252 | RotationType rotation; 253 | PositionType position; 254 | MotionType motion; 255 | EquipmentType main_hand; 256 | EquipmentType item_in_hand; 257 | EquipmentType off_hand; 258 | DefinitionsType definitions; 259 | std::string type; 260 | std::string name; 261 | long unique_id; 262 | 263 | }; 264 | 265 | 266 | class BlockEntityType : public NBTObject { 267 | public: 268 | std::string get_string() const override { 269 | std::stringstream ss; 270 | 271 | ss << "blockentity at (" << x << "," << y << "," << z << ") of type: " << type << " moveable " << moveable; 272 | ss << NBTObject::get_string(); 273 | return ss.str(); 274 | } 275 | 276 | 277 | std::string get_type() { return type; }; 278 | 279 | std::string get_position_string() { 280 | std::stringstream ss; 281 | ss << x << "," << y << "," << z; 282 | return ss.str(); 283 | } 284 | std::vector get_position() { 285 | return {x,y,z}; 286 | }; 287 | 288 | void add_int(std::string tagname, int32_t value) override { 289 | if (tagname == "x") { 290 | x = value; 291 | } else if (tagname == "y") { 292 | y = value; 293 | } else if (tagname == "z") { 294 | z = value; 295 | } else { 296 | NBTObject::add_int(tagname, value); 297 | } 298 | } 299 | 300 | void add_byte(std::string tagname, uint8_t value) override { 301 | if (tagname == "isMovable") { 302 | moveable = value; 303 | } else { 304 | NBTObject::add_byte(tagname, value); 305 | } 306 | } 307 | 308 | void add_string(std::string tagname, std::string value) override { 309 | if (tagname == "id") { 310 | type = value; 311 | } else { 312 | NBTObject::add_string(tagname, value); 313 | } 314 | }; 315 | 316 | private: 317 | int x,y,z; 318 | bool moveable; 319 | std::string type; 320 | }; 321 | 322 | class VillageType : public NBTObject { 323 | public: 324 | struct BBox { 325 | int xl, yl, zl, xh, yh, zh; 326 | }; 327 | 328 | std::string get_string() const override { 329 | std::stringstream ss; 330 | 331 | ss << "village at ("; 332 | ss << bbox.xl << "," << bbox.yl << "," << bbox.zl << ")("; 333 | ss << bbox.xh << "," << bbox.yh << "," << bbox.zh << ") named: " << village_name; 334 | ss << " with " << dwellers.size() << " dwellers"; 335 | ss << " " << NBTObject::get_string(); 336 | return ss.str(); 337 | } 338 | 339 | 340 | VillageType(std::string name) : village_name(name) { /* empty */ }; 341 | 342 | std::string get_name() { return village_name; } 343 | 344 | BBox get_bbox() { return bbox; }; 345 | std::vector get_dwellers() { return dwellers; } 346 | 347 | void add_long(std::string tagname, long value) override { 348 | if (tagname == "ID") { 349 | dwellers.push_back(value); 350 | } else { 351 | NBTObject::add_long(tagname, value); 352 | } 353 | } 354 | 355 | void add_int(std::string tagname, int32_t value) override { 356 | if (tagname == "X0") { 357 | bbox.xl = value; 358 | } else if (tagname == "Y0") { 359 | bbox.yl = value; 360 | } else if (tagname == "Z0") { 361 | bbox.zl = value; 362 | } else if (tagname == "X1") { 363 | bbox.xh = value; 364 | } else if (tagname == "Y1") { 365 | bbox.yh = value; 366 | } else if (tagname == "Z1") { 367 | bbox.zh = value; 368 | } else { 369 | NBTObject::add_int(tagname, value); 370 | } 371 | } 372 | 373 | private: 374 | std::vector dwellers; 375 | 376 | std::string village_name; 377 | BBox bbox; 378 | }; 379 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # minecraft_mmccoo 2 | some code for parsing and analyzing Minecraft Bedrock leveldb data. Check out this video showing what this code does: 3 | 4 | [![demo](http://img.youtube.com/vi/S-5TawHSw1U/0.jpg)](http://www.youtube.com/watch?v=S-5TawHSw1U "Demo") 5 | 6 | 7 | Setup instructions for Ubuntu (though my main machine is Arch): 8 | 9 | # General packages you will need: 10 | sudo apt install npm 11 | sudo apt-get install libboost-all-dev 12 | sudo apt install cmake 13 | sudo apt-get install libcgal-dev 14 | sudo apt install build-essential 15 | 16 | # setup Snappy 17 | git clone https://github.com/google/snappy.git 18 | cd snappy 19 | mkdir build 20 | mkdir install 21 | cd build 22 | cmake .. -DCMAKE_INSTALL_PREFIX=`realpath ../install` 23 | 24 | # Magick++ needs to be installed if you want to generate tiles. 25 | // apt-file search Magick++.h 26 | // apt-file search Magick++-config 27 | sudo apt-get install libmagick++-6-headers libmagick++-6.q16-dev 28 | 29 | # Compile leveldb-mcpe 30 | git clone https://github.com/Mojang/leveldb-mcpe.git 31 | cd leveldb-mcpe 32 | git apply ../minecraft_mmccoo/snappy_compressor.cc.patch 33 | git apply ../minecraft_mmccoo/table_test.cc.patch 34 | make 35 | 36 | # Compile the code of this repo 37 | First, compile the executeable 38 | 39 | make 40 | 41 | # Now setup the web stuff 42 | 43 | cd web_stuff 44 | npm install 45 | npm run build 46 | 47 | # Copy web files: 48 | cp -r web_stuff/dist/* /var/www/ 49 | 50 | # generate files for your world 51 | cd /var/www/ 52 | 53 | // This will generate files in the map directory. The web htmls and js will look for that map directory 54 | minecraft_mmccoo/basic_test 55 | -------------------------------------------------------------------------------- /ResourceVectors.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | 9 | //https://github.com/nlohmann/json#json-as-first-class-data-type 10 | // this json header is copied from the above github. I don't know how to include 11 | // a single file as a submodule and the git report itself is pretty large. 12 | #include 13 | 14 | 15 | std::set rare_interesting_resources = { 16 | "minecraft:diamond_ore", 17 | "minecraft:torch", 18 | "minecraft:emerald_ore", // not sure this is the right name. 19 | "minecraft:lapis_ore", 20 | "minecraft:bee_nest", 21 | "minecraft:bell", 22 | }; 23 | 24 | ResourceVectors::ResourceVectors(MinecraftWorld& world) 25 | { 26 | for (auto scix : world.chunks_by_y) { 27 | int chunkx = scix.first; 28 | UNUSED(chunkx); 29 | for (auto sciz : scix.second) { 30 | int chunkz = sciz.first; 31 | UNUSED(chunkz); 32 | 33 | auto &te = world.top_earthly[chunkx][chunkz]; 34 | 35 | for (auto sciy : sciz.second) { 36 | int chunky = sciy.first; 37 | UNUSED(chunky); 38 | auto sc = sciy.second; 39 | 40 | for (auto iter=sc->begin(); iter!=sc->end(); ++iter) { 41 | auto loc = *iter; 42 | 43 | BlockType bt = BlockType::get_block_type_by_id(loc.type); 44 | if (rare_interesting_resources.find(bt.get_name()) == rare_interesting_resources.end()) { 45 | continue; 46 | } 47 | 48 | int rawx = loc.x - chunkx*16; 49 | int rawz = loc.z - chunkz*16; 50 | 51 | resources.push_back({loc.x, loc.y, loc.z, bt, loc.y > te[rawx][rawz]}); 52 | } 53 | } 54 | } 55 | } 56 | } 57 | 58 | 59 | void ResourceVectors::write(std::string filename) 60 | { 61 | nlohmann::json top; 62 | 63 | top["type"] = "FeatureCollection"; 64 | 65 | for(auto res : resources) { 66 | nlohmann::json feature; 67 | feature["type"] = "Feature"; 68 | feature["properties"]["coords"] = { res.x, res.y, res.z }; 69 | feature["properties"]["name"] = res.bt.get_name(); 70 | feature["properties"]["surface"] = res.isSurface; 71 | 72 | feature["geometry"]["type"] = "Point"; 73 | 74 | feature["geometry"]["coordinates"] = { res.x, res.z }; 75 | 76 | top["features"].push_back(feature); 77 | } 78 | 79 | std::ofstream file; 80 | file.open(filename); 81 | file << top.dump(2); 82 | file.close(); 83 | 84 | } 85 | -------------------------------------------------------------------------------- /ResourceVectors.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #pragma once 5 | 6 | 7 | #include 8 | #include 9 | 10 | class ResourceVectors { 11 | public: 12 | ResourceVectors(MinecraftWorld& world); 13 | void write(std::string filename); 14 | 15 | private: 16 | struct Resource { 17 | int x; 18 | int y; 19 | int z; 20 | BlockType bt; 21 | bool isSurface; 22 | }; 23 | 24 | std::vector resources; 25 | }; 26 | -------------------------------------------------------------------------------- /SubChunk.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | 6 | SubChunk::SubChunk() 7 | { 8 | memset(block_type_ids, 0, 16*16*16); 9 | chunkx=-1; 10 | chunky=-1; 11 | chunkz=-1; 12 | } 13 | 14 | 15 | void SubChunk::set_type_at(int x, int y, int z, uint8_t type) 16 | { 17 | assert(x<16); 18 | assert(y<16); 19 | assert(z<16); 20 | assert(x>=0); 21 | assert(y>=0); 22 | assert(z>=0); 23 | 24 | assert(chunky>=0); 25 | block_type_ids[x][y][z] = type; 26 | } 27 | 28 | uint8_t SubChunk::get_type_at(int x, int y, int z) 29 | { 30 | assert(x<16); 31 | assert(y<16); 32 | assert(z<16); 33 | assert(x>=0); 34 | assert(y>=0); 35 | assert(z>=0); 36 | 37 | return block_type_ids[x][y][z]; 38 | } 39 | -------------------------------------------------------------------------------- /SubChunk.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef SUB_CHUNK_HH 3 | #define SUB_CHUNK_HH 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | 15 | class SubChunk { 16 | struct Iter { 17 | private: 18 | struct Location { 19 | Location(int x, int y, int z, uint8_t type) : x(x), y(y), z(z), type(type) { /* empty */ }; 20 | int x; 21 | int y; 22 | int z; 23 | uint8_t type; 24 | }; 25 | 26 | int curx; 27 | int cury; 28 | int curz; 29 | int type_filter; 30 | SubChunk *subchunk; 31 | 32 | public: 33 | Iter(int tf, SubChunk *sc) : curx(0), cury(0), curz(0), type_filter(tf), subchunk(sc) { /* empty */ } 34 | static Iter get_end() { 35 | Iter retval(-1, 0x0); 36 | retval.curx = retval.cury = retval.curz = -1; 37 | return retval; 38 | }; 39 | 40 | // Previously provided by std::iterator - see update below 41 | typedef Location value_type; 42 | typedef std::ptrdiff_t difference_type; 43 | typedef Location* pointer; 44 | typedef Location& reference; 45 | typedef std::input_iterator_tag iterator_category; 46 | 47 | Location operator*() const { 48 | assert(curx>=0 && cury>=0 && curz>=0 && subchunk); 49 | return Location(subchunk->chunkx*16+curx, subchunk->chunky*16+cury, subchunk->chunkz*16+curz, 50 | subchunk->get_type_at(curx,cury,curz)); 51 | } 52 | 53 | bool operator==(const Iter& other) const { 54 | return (other.curx == curx) && (other.cury == cury) && (other.curz == curz); 55 | } 56 | bool operator!=(const Iter& other) const { return !(*this == other); } 57 | #if 0 58 | // when do I have to have post incr? 59 | intholder operator++(int) { 60 | 61 | intholder ret(value_); 62 | ++*this; 63 | return ret; 64 | } 65 | #endif 66 | Iter& operator++() { 67 | if ((curx==-1) || (cury==-1) || (curz==-1)) { curx = cury = curz = -1; } 68 | 69 | while (1) { 70 | curz++; 71 | if (curz>=16) { 72 | curz = 0; 73 | curx++; 74 | if (curx >= 16) { 75 | curx = 0; 76 | cury++; 77 | if (cury>=16) { 78 | curx = cury = curz = -1; 79 | break; 80 | } 81 | } 82 | } 83 | //std::cout << "looing in " << curx << "," << cury << "," << curz << "\n"; 84 | if (type_filter != -1) { 85 | if (type_filter != subchunk->get_type_at(curx,cury,curz)) { 86 | continue; 87 | } 88 | } 89 | break; 90 | } 91 | return *this; 92 | }; 93 | 94 | }; 95 | public: 96 | SubChunk(); 97 | 98 | int chunkx; 99 | int chunky; 100 | int chunkz; 101 | 102 | 103 | uint8_t block_type_ids[16][16][16]; 104 | 105 | void set_type_at(int x, int y, int z, uint8_t type); 106 | uint8_t get_type_at(int x, int y, int z); 107 | 108 | Iter begin(int type_filter=-1) { 109 | Iter retval(type_filter, this); 110 | if (type_filter != -1 && (*retval).type != type_filter) { 111 | ++retval; 112 | } 113 | return retval; 114 | } 115 | Iter end() { return Iter::get_end(); } 116 | }; 117 | 118 | 119 | 120 | 121 | 122 | #endif 123 | -------------------------------------------------------------------------------- /VillageJSON.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #define UNUSED(x) (void)(x) 8 | 9 | void GenerateVillageJSON(MinecraftWorld &world, std::string filename) 10 | { 11 | 12 | boost::property_tree::ptree tree; 13 | 14 | tree.put("type", "FeatureCollection"); 15 | 16 | boost::property_tree::ptree features; 17 | 18 | for(auto it : world.villages) { 19 | VillageType &village = it.second; 20 | 21 | std::cout << "writing village json\n"; 22 | std::cout << village.get_string() << "\n"; 23 | std::cout << "dwellers:\n"; 24 | std::map dcount; 25 | for(long villagerid : village.get_dwellers()) { 26 | std::optional dweller = world.get_entity_by_id(villagerid); 27 | if (!dweller) { 28 | std::cout << "couldn't find dweller with id: " << villagerid << "\n"; 29 | continue; 30 | } 31 | 32 | std::cout << " " << villagerid << " " << (*dweller)->get_type() << "\n"; 33 | dcount[(*dweller)->get_type()]++; 34 | } 35 | 36 | 37 | boost::property_tree::ptree feature_node; 38 | 39 | feature_node.put("type", "Feature"); 40 | feature_node.put("properties.name", village.get_name()); 41 | 42 | for (auto it : dcount) { 43 | feature_node.put("properties.counts." + it.first, it.second); 44 | } 45 | 46 | 47 | // this seems like a goofy way to have a list. 48 | boost::property_tree::ptree coords; 49 | boost::property_tree::ptree coord; 50 | 51 | auto bbox = village.get_bbox(); 52 | 53 | coords.clear(); 54 | for(int i : {bbox.xl, bbox.yl, bbox.zl, bbox.xh, bbox.yh, bbox.zh}) { 55 | coord.put("", i); 56 | coords.push_back(std::make_pair("", coord)); 57 | } 58 | feature_node.add_child("properties.bounds", coords); 59 | 60 | std::vector > rectpts = { 61 | { bbox.xl, bbox.zl }, 62 | { bbox.xh, bbox.zl }, 63 | { bbox.xh, bbox.zh }, 64 | { bbox.xl, bbox.zh }, 65 | { bbox.xl, bbox.zl } 66 | }; 67 | 68 | boost::property_tree::ptree polygon; 69 | for (std::pair c : rectpts) { 70 | coords.clear(); 71 | coord.put("", c.first); 72 | coords.push_back(std::make_pair("", coord)); 73 | coord.put("", c.second); 74 | coords.push_back(std::make_pair("", coord)); 75 | 76 | polygon.push_back(std::make_pair("", coords)); 77 | } 78 | 79 | // geojson wants an array of first the polygon followed by the holes. 80 | // in our case, we have no holes. 81 | boost::property_tree::ptree polyholepair; 82 | polyholepair.push_back(std::make_pair("", polygon)); 83 | 84 | feature_node.put("geometry.type", "Polygon"); 85 | feature_node.add_child("geometry.coordinates", polyholepair); 86 | 87 | 88 | 89 | features.push_back(std::make_pair("", feature_node)); 90 | } 91 | 92 | 93 | tree.add_child("features", features); 94 | 95 | boost::property_tree::write_json(filename, tree); 96 | 97 | std::cout << "wrote " << world.villages.size() << " block entities\n"; 98 | 99 | } 100 | -------------------------------------------------------------------------------- /VillageJSON.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | 9 | void GenerateVillageJSON(MinecraftWorld &world, std::string filename); 10 | -------------------------------------------------------------------------------- /basic_test.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define UNUSED(x) (void)(x) 19 | 20 | int main(int argc, char** argv) { 21 | 22 | std::string dbpath; 23 | boost::program_options::options_description cmdline_options("Allowed options"); 24 | cmdline_options.add_options() 25 | ("help", "produce this help message") 26 | 27 | ("db_path", 28 | boost::program_options::value(&dbpath), 29 | "input db path") 30 | ; 31 | 32 | boost::program_options::variables_map vm; 33 | try { 34 | boost::program_options::store(boost::program_options::command_line_parser(argc, argv) 35 | .options(cmdline_options) 36 | .run(), 37 | vm); 38 | boost::program_options::notify(vm); 39 | } catch (std::exception &e) { 40 | std::cout << e.what() << "\n"; 41 | std::cout << cmdline_options << "\n"; 42 | return 1; 43 | } 44 | 45 | if (dbpath.empty()) { 46 | std::cout << "please give the path to your db directory.\n"; 47 | std::cout << cmdline_options << "\n"; 48 | return 1; 49 | } 50 | 51 | MinecraftWorld world; 52 | 53 | //std::string dbpath = "/bubba/electronicsDS/minecraft/sampledata/2019.11.26.04.00.53/worlds/Bedrock level/db"; 54 | //std::string dbpath = "/bubba/electronicsDS/minecraft/sampledata/2020.02.04.04.00.53/worlds/Bedrock level/db"; 55 | // http://www.trulybedrock.com/downloads/ 56 | //std::string dbpath = "/bubba/electronicsDS/minecraft/sampledata/TBSeason0/db"; 57 | //std::string dbpath = "/bubba/electronicsDS/minecraft/sampledata/myserver_feb_13/Bedrock level/db"; 58 | 59 | parse_bedrock(dbpath, world); 60 | BlockType::print_block_types(); 61 | 62 | # if 0 63 | std::cout << "iterating locations\n"; 64 | 65 | int diamond = BlockType::get_block_type_id_by_name("minecraft:diamond_ore ()"); 66 | std::cout << "filtering by " << diamond << "\n"; 67 | 68 | for (auto scix : world.theworld) { 69 | int chunkx = scix.first; 70 | UNUSED(chunkx); 71 | for (auto sciy : scix.second) { 72 | int chunky = sciy.first; 73 | UNUSED(chunky); 74 | for (auto sciz : sciy.second) { 75 | int chunkz = sciz.first; 76 | UNUSED(chunkz); 77 | auto sc = sciz.second; 78 | 79 | for (auto iter=sc.begin(diamond); iter!=sc.end(); ++iter) { 80 | //for (auto loc : sc) { 81 | auto loc = *iter; 82 | BlockType bt = BlockType::get_block_type_by_id(loc.type); 83 | std::cout << "at " << loc.x << ", " << loc.y << ", " << loc.z << ":" << bt.get_name() << "\n"; 84 | } 85 | } 86 | } 87 | } 88 | #endif 89 | { 90 | ResourceVectors resourcevectors(world); 91 | resourcevectors.write("map/resources.json"); 92 | } 93 | 94 | { 95 | CaveVectors cavevectors(world); 96 | cavevectors.write("map/caves.json"); 97 | } 98 | 99 | boost::filesystem::create_directory("map"); 100 | 101 | world.write_world_json("map/world.json"); 102 | write_biome_properties("map/biomeproperties.json"); 103 | 104 | 105 | { 106 | BiomeVectors bvectors; 107 | for(auto it1 : world.chunk_biomes) { 108 | int chunkx = it1.first; 109 | for(auto it2 : it1.second) { 110 | int chunkz = it2.first; 111 | bvectors.add_chunk(chunkx, chunkz, it2.second); 112 | } 113 | } 114 | bvectors.write("map/biomes.json"); 115 | } 116 | 117 | // elevation stuff generates really large files. 100MB+ for a not that large world. 118 | if (0) { 119 | ElevationVectors evectors(world); 120 | for(auto it1 : world.chunk_elevation) { 121 | int chunkx = it1.first; 122 | //if (chunkx%2) { continue; } 123 | for(auto it2 : it1.second) { 124 | int chunkz = it2.first; 125 | //if (chunkz%2) { continue; } 126 | evectors.add_chunk(chunkx, chunkz, it2.second); 127 | #if 0 128 | std::cout << "elevations for " << chunkx << ", " << chunkz << "\n"; 129 | for(int z=0; z<16; z++) { 130 | for(int x=0; x<16; x++) { 131 | std::cout << std::setw(4) << it2.second[x][z] << " "; 132 | } 133 | std::cout << "\n"; 134 | } 135 | std::cout << "\n"; 136 | #endif 137 | } 138 | } 139 | evectors.write("map/elevations.json"); 140 | } 141 | 142 | if (1) { 143 | std::cout << "computing elevations\n"; 144 | ElevationVectors evectors(world); 145 | for(auto it1 : world.top_earthly) { 146 | int chunkx = it1.first; 147 | //if (chunkx%2) { continue; } 148 | for(auto it2 : it1.second) { 149 | int chunkz = it2.first; 150 | //if (chunkz%2) { continue; } 151 | evectors.add_chunk(chunkx, chunkz, it2.second); 152 | } 153 | } 154 | evectors.write("map/elevations.json"); 155 | } 156 | 157 | world.populate_entity_table(); 158 | 159 | GenerateVillageJSON(world, "map/villages.json"); 160 | 161 | GenerateEntityJSON(world, "map/entities.json"); 162 | 163 | GenerateBlockEntityJSON(world, "map/block_entities.json"); 164 | 165 | return 0; 166 | } 167 | -------------------------------------------------------------------------------- /biome.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | 5 | //https://github.com/nlohmann/json#json-as-first-class-data-type 6 | // this json header is copied from the above github. I don't know how to include 7 | // a single file as a submodule and the git report itself is pretty large. 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | // if you change this, make sure to update biomeproperties.js in web_stuff/map 15 | Biome Biomes[] = { 16 | {"Ocean", 0, {2,0,107}}, 17 | {"Plains", 1, {140,178,100}}, 18 | {"Desert", 2, {247,146,42}}, 19 | {"Mountains", 3, {95,95,95}}, 20 | {"Forest", 4, {21,101,41}}, 21 | {"Taiga", 5, {24,101,89}}, 22 | {"Swamp", 6, {46,249,181}}, 23 | {"River", 7, {0,0,250}}, 24 | {"Nether", 8, {252,0,1}}, 25 | {"TheEnd", 9, {125,121,251}}, 26 | {"FrozenOcean", 0xa, {141,141,156}}, 27 | {"FrozenRiver", 0xb, {158,155,252}}, 28 | {"SnowyTundra", 0xc, {254,254,254}}, 29 | {"SnowyMountains", 0xd, {156,157,156}}, 30 | {"MushroomFields", 0xe, {251,0,249}}, 31 | {"MushroomFieldShore", 0xf, {156,0,249}}, 32 | 33 | {"Beach", 0x10, {249,222,93}}, 34 | {"DesertHills", 0x11, {207,94,31}}, 35 | {"WoodedHills", 0x12, {39,85,35}}, 36 | {"TaigaHills", 0x13, {28,58,53}}, 37 | {"MountainEdge", 0x14, {122,127,157}}, 38 | {"Jungle", 0x15, {84,122,30}}, 39 | {"JungleHills", 0x16, {48,68,19}}, 40 | {"JungleEdge", 0x17, {98,138,40}}, 41 | {"DeepOcean", 0x18, {3,2,49}}, 42 | {"StoneShore", 0x19, {158,159,130}}, 43 | {"SnowyBeach", 0x1a, {249,239,191}}, 44 | {"BirchForest", 0x1b, {52,115,70}}, 45 | {"BirchForestHills", 0x1c, {39,96,56}}, 46 | {"DarkForest", 0x1d, {67,83,36}}, 47 | {"SnowyTaiga", 0x1e, {52,85,74}}, 48 | {"SnowyTaigaHills", 0x1f, {41,65,57}}, 49 | {"GiantTreeTaiga", 0x20, {88,101,81}}, 50 | {"GiantTreeTaigaHills", 0x21, {84,94,64}}, 51 | {"WoodedMountains", 0x22, {80,111,81}}, 52 | {"Savanna", 0x23, {187,178,104}}, 53 | {"SavannaPlateau", 0x24, {164,155,101}}, 54 | {"Badlands", 0x25, {213,67,29}}, 55 | {"WoodedBadlandsPlateau", 0x26, {174,150,103}}, 56 | {"BadlandsPlateau", 0x27, {198,137,101}}, 57 | {"Unknown28", 0x28, {0,0,0}}, 58 | {"Unknown2a", 0x2a, {0,0,0}}, 59 | {"Unknown2b", 0x2b, {0,0,0}}, 60 | {"WarmOcean", 0x2c, {76,35,183}}, 61 | {"LukewarmOcean", 0x2d, {87,58,168}}, 62 | {"ColdOcean", 0x2e, {69,116,214}}, 63 | {"DeepWarmOcean", 0x2f, {100,53,227}}, 64 | {"DeepLukewarmOcean", 0x30, {105,55,238}}, 65 | {"DeepColdOcean", 0x31, {29,72,164}}, 66 | {"DeepFrozenOcean", 0x32, {158,184,237}}, 67 | {"Void", 0x7f, {3,3,3}}, 68 | {"SunflowerPlains", 0x81, {181,220,140}}, 69 | {"DesertLakes", 0x82, {253,186,75}}, 70 | {"GravellyMountains", 0x83, {133,133,133}}, 71 | {"FlowerForest", 0x84, {105,115,46}}, 72 | {"TaigaMountains", 0x85, {88,101,81}}, 73 | {"SwampHills", 0x86, {65,254,218}}, 74 | {"IceSpikes", 0x8c, {178,218,218}}, 75 | {"ModifiedJungle", 0x95, {122,162,59}}, 76 | {"ModifiedJungleEdge", 0x97, {138,178,71}}, 77 | {"TallBirchForest", 0x9b, {89,154,108}}, 78 | {"TallBirchHills", 0x9c, {79,137,96}}, 79 | {"DarkForestHills", 0x9d, {104,121,71}}, 80 | {"SnowyTaigaMountains", 0x9e, {89,123,112}}, 81 | {"GiantSpruceTaiga", 0xa0, {106,95,78}}, 82 | {"GiantSprucetaigaHills", 0xa1, {106,116,100}}, 83 | {"GravellyMountainsPlus", 0xa2, {119,150,119}}, 84 | {"ShatteredSavanna", 0xa3, {227,217,136}}, 85 | {"ShatteredsavannaPlateau", 0xa4, {204,195,140}}, 86 | {"ErodedBadlands", 0xa5, {251,107,65}}, 87 | {"ModifiedWoodedBadlandsPlateau", 0xa6, {213,189,141}}, 88 | {"ModifiedBadlandsPlateau", 0xa7, {239,176,139}}, 89 | {"BambooJungle", 0xa8, {113,230,52}}, 90 | {"BambooJungleHills", 0xa9, {96,197,45}}, 91 | 92 | {"UnknownBiome", 0xff, {255, 0, 0}} 93 | }; 94 | 95 | 96 | const Biome &get_biome(int id) { 97 | static bool init=false; 98 | static std::map biome_map; 99 | if (!init) { 100 | for(long unsigned int i=0; i 5 | #include 6 | 7 | typedef std::array, 16> Grid16; 8 | 9 | struct Biome { 10 | std::string name; 11 | int id; 12 | struct { 13 | int r; 14 | int g; 15 | int b; 16 | } color; 17 | }; 18 | 19 | const Biome &get_biome(int id); 20 | 21 | void write_biome_properties(std::string filename); 22 | -------------------------------------------------------------------------------- /compute_mst.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | typedef CGAL::Exact_predicates_inexact_constructions_kernel K; 28 | typedef CGAL::Delaunay_triangulation_3 Triangulation; 29 | typedef Triangulation::Point Point; 30 | 31 | #define UNUSED(x) (void)(x) 32 | 33 | 34 | // using a global for this is not good. I really should be passing it 35 | // some other way. 36 | double scale_z=1.0; 37 | 38 | // these two functions are for my readvtk function. 39 | Triangulation::Vertex_handle 40 | add_vertex(Triangulation &T, double x, double y, double z) 41 | { 42 | return T.insert(Point(x,y,z)); 43 | } 44 | 45 | void add_edge(Triangulation &T, 46 | Triangulation::Vertex_handle s, 47 | Triangulation::Vertex_handle t) 48 | { 49 | // no edges to add. CGAL does that. 50 | } 51 | 52 | struct VertexData { 53 | std::string name; 54 | double x,y,z; 55 | }; 56 | 57 | struct EdgeData { 58 | double distance; 59 | }; 60 | 61 | typedef boost::adjacency_list 65 | > MyGraphType; 66 | 67 | 68 | 69 | // these are needed by readvtk 70 | typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; 71 | typedef typename boost::graph_traits::edge_descriptor edge_descriptor; 72 | typename boost::graph_traits::vertex_descriptor 73 | add_vertex(MyGraphType &G, std::string& vname, double x, double y, double z) 74 | { 75 | typedef typename boost::graph_traits::vertex_descriptor vertex_descriptor; 76 | vertex_descriptor v = add_vertex(G); 77 | G[v].x = x; 78 | G[v].y = y; 79 | G[v].z = z; 80 | return v; 81 | } 82 | 83 | inline 84 | double distance(MyGraphType &G, 85 | typename boost::graph_traits::vertex_descriptor v1, 86 | typename boost::graph_traits::vertex_descriptor v2) 87 | { 88 | return sqrt((G[v1].x - G[v2].x)*(G[v1].x - G[v2].x) + 89 | (G[v1].y - G[v2].y)*(G[v1].y - G[v2].y) + 90 | (G[v1].z - G[v2].z)*(G[v1].z - G[v2].z)); 91 | } 92 | 93 | 94 | typename boost::graph_traits::edge_descriptor 95 | add_edge(MyGraphType &G, 96 | typename boost::graph_traits::vertex_descriptor v1, 97 | typename boost::graph_traits::vertex_descriptor v2) 98 | { 99 | typedef typename boost::graph_traits::edge_descriptor edge_descriptor; 100 | edge_descriptor e = add_edge(v1, v2, G).first; 101 | boost::property_map::type weightmap = get(boost::edge_weight, G); 102 | weightmap[e] = distance(G, v1, v2); 103 | return e; 104 | } 105 | 106 | 107 | enum MSTAlgorithm { PRIM, KRUSKAL }; 108 | std::istream& operator>>(std::istream& in, MSTAlgorithm &format) 109 | { 110 | std::string token; 111 | in >> token; 112 | if (token == "prim") 113 | format = PRIM; 114 | else if (token == "kruskal") 115 | format = KRUSKAL; 116 | else 117 | in.setstate(std::ios_base::failbit); 118 | return in; 119 | } 120 | 121 | 122 | 123 | 124 | void compute_mst(std::vector > locs, std::vector > &clusters, float maxdist) 125 | { 126 | 127 | Triangulation T; 128 | 129 | for(auto loc : locs) { 130 | int x,y,z; 131 | std::tie(x,y,z) = loc; 132 | add_vertex(T, x, y, z); 133 | } 134 | 135 | assert( T.is_valid() ); // checking validity of T 136 | 137 | // at this point, the points have been loaded and the triangulation is done. 138 | 139 | MyGraphType G; 140 | 141 | std::map vertex_map; 142 | 143 | Triangulation::Finite_vertices_iterator viter; 144 | for (viter = T.finite_vertices_begin(); 145 | viter != T.finite_vertices_end(); 146 | viter++) { 147 | Triangulation::Triangulation_data_structure::Vertex v = *viter; 148 | Point p = v.point(); 149 | double x = CGAL::to_double(p.x()); 150 | double y = CGAL::to_double(p.y()); 151 | double z = CGAL::to_double(p.z()); 152 | std::string d(""); 153 | auto boost_vertex = vertex_map[v.point()] = add_vertex(G, d, x,y,z); 154 | UNUSED(boost_vertex); 155 | } 156 | 157 | Triangulation::Finite_edges_iterator iter; 158 | for(iter = T.finite_edges_begin(); 159 | iter != T.finite_edges_end(); 160 | iter++) { 161 | // edges are not represented as edges in CGAL triangulation graphs. 162 | // Instead, they are stored in faces/cells. 163 | 164 | Triangulation::Triangulation_data_structure::Edge e = *iter; 165 | Triangulation::Triangulation_data_structure::Cell_handle c = e.first; 166 | int i = e.second; 167 | int j = e.third; 168 | auto boost_edge = add_edge(G, vertex_map[c->vertex(i)->point()], vertex_map[c->vertex(j)->point()]); 169 | UNUSED(boost_edge); 170 | } 171 | 172 | 173 | 174 | boost::adjacency_list CGraph; 175 | 176 | 177 | std::vector mst_prim(num_vertices(G)); 178 | 179 | // the not particularly helpful doc for iterator_property_map: 180 | // http://www.boost.org/doc/libs/1_64_0/libs/property_map/doc/iterator_property_map.html 181 | // iterator_property_map 182 | // 183 | typedef boost::property_map::type IdMap; 184 | boost::iterator_property_map::iterator, 185 | IdMap, 186 | vertex_descriptor, 187 | vertex_descriptor&> 188 | predmap(mst_prim.begin(), get(boost::vertex_index, G)); 189 | 190 | boost::prim_minimum_spanning_tree(G, predmap); 191 | 192 | boost::property_map::type weightmap = get(boost::edge_weight, G); 193 | 194 | // I'm not merging this for loop with the one right after because 195 | // I don't want to double add vertices. adding an edge implicitly adds a vertex. 196 | // if there are blocks that are loners not in clusters (like spawners) 197 | // adding vertices here will ensure they are represented in the connected 198 | // components stuff. 199 | for(long unsigned int i=0; i maxdist) { 213 | continue; 214 | } 215 | } else { 216 | std::cout << "unexpected edge missing\n"; 217 | } 218 | 219 | boost::add_edge(i, mst_prim[i], CGraph); 220 | } 221 | 222 | std::vector component(num_vertices(CGraph)); 223 | int num = connected_components(CGraph, &component[0]); 224 | 225 | //std::cout << "Total number of clusters: " << num << std::endl; 226 | 227 | clusters.resize(0); 228 | clusters.resize(num); 229 | std::map compsize; 230 | for(std::vector::size_type i=0; i > locs, float maxdist, std::string filename) 237 | { 238 | 239 | Triangulation T; 240 | 241 | for(auto loc : locs) { 242 | int x,y,z; 243 | std::tie(x,y,z) = loc; 244 | add_vertex(T, x, y, z); 245 | } 246 | 247 | assert( T.is_valid() ); // checking validity of T 248 | 249 | // at this point, the points have been loaded and the triangulation is done. 250 | 251 | MyGraphType G; 252 | 253 | std::map vertex_map; 254 | 255 | Triangulation::Finite_vertices_iterator viter; 256 | for (viter = T.finite_vertices_begin(); 257 | viter != T.finite_vertices_end(); 258 | viter++) { 259 | Triangulation::Triangulation_data_structure::Vertex v = *viter; 260 | Point p = v.point(); 261 | double x = CGAL::to_double(p.x()); 262 | double y = CGAL::to_double(p.y()); 263 | double z = CGAL::to_double(p.z()); 264 | std::string d(""); 265 | auto boost_vertex = vertex_map[v.point()] = add_vertex(G, d, x,y,z); 266 | UNUSED(boost_vertex); 267 | 268 | } 269 | 270 | Triangulation::Finite_edges_iterator iter; 271 | for(iter = T.finite_edges_begin(); 272 | iter != T.finite_edges_end(); 273 | iter++) { 274 | // edges are not represented as edges in CGAL triangulation graphs. 275 | // Instead, they are stored in faces/cells. 276 | 277 | Triangulation::Triangulation_data_structure::Edge e = *iter; 278 | Triangulation::Triangulation_data_structure::Cell_handle c = e.first; 279 | int i = e.second; 280 | int j = e.third; 281 | auto boost_edge = add_edge(G, vertex_map[c->vertex(i)->point()], vertex_map[c->vertex(j)->point()]); 282 | UNUSED(boost_edge); 283 | } 284 | 285 | 286 | 287 | 288 | std::cerr << "running prim" << std::endl; 289 | std::vector mst_prim(num_vertices(G)); 290 | 291 | 292 | std::ofstream outfile; 293 | outfile.open(filename); 294 | 295 | outfile << "# vtk DataFile Version 1.0\n"; 296 | outfile << "3D triangulation data\n"; 297 | outfile << "ASCII\n"; 298 | outfile << std::endl; 299 | outfile << "DATASET POLYDATA\n"; 300 | 301 | outfile << "POINTS " << num_vertices(G) << " float\n"; 302 | for(long unsigned int i=0; i > edges_to_include; 307 | boost::adjacency_list CGraph; 308 | 309 | //if (mst_algorithm == PRIM) { 310 | if (1) { 311 | 312 | // the not particularly helpful doc for iterator_property_map: 313 | // http://www.boost.org/doc/libs/1_64_0/libs/property_map/doc/iterator_property_map.html 314 | // iterator_property_map 315 | // 316 | typedef boost::property_map::type IdMap; 317 | boost::iterator_property_map::iterator, 318 | IdMap, 319 | vertex_descriptor, 320 | vertex_descriptor&> 321 | predmap(mst_prim.begin(), get(boost::vertex_index, G)); 322 | 323 | boost::prim_minimum_spanning_tree(G, predmap); 324 | 325 | boost::property_map::type weightmap = get(boost::edge_weight, G); 326 | 327 | for(long unsigned int i=0; i maxdist) { 336 | continue; 337 | } 338 | } else { 339 | std::cout << "unexpected edge missing\n"; 340 | } 341 | 342 | edges_to_include.push_back({i, mst_prim[i]}); 343 | boost::add_edge(i, mst_prim[i], CGraph); 344 | } 345 | 346 | } else { 347 | //if (mst_algorithm == KRUSKAL) { 348 | std::cerr << "running kruskal" << std::endl; 349 | std::list::edge_descriptor> mst_kruskal; 350 | boost::kruskal_minimum_spanning_tree(G, std::back_inserter(mst_kruskal)); 351 | 352 | for(auto iter=mst_kruskal.begin(); 353 | iter != mst_kruskal.end(); 354 | iter++) { 355 | outfile << "2 " << source(*iter, G) << " " << target(*iter, G) < component(num_vertices(CGraph)); 369 | int num = connected_components(CGraph, &component[0]); 370 | 371 | std::cout << "Total number of clusters: " << num << " representing " << num_vertices(CGraph) << "ores\n"; 372 | 373 | std::map compsize; 374 | for(std::vector::size_type i=0; i sizehist; 379 | for(std::vector::size_type i=0; i 8 | #include 9 | #include 10 | 11 | void compute_mst(std::vector > locs, float maxdist, std::string filename); 12 | void compute_mst(std::vector > locs, 13 | std::vector > &clusters, 14 | float maxdist); 15 | #endif 16 | -------------------------------------------------------------------------------- /compute_ore_stats.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | void 9 | show_ore_stats(MinecraftWorld &world, 10 | std::vector > &ores, 11 | int oretype 12 | ) 13 | { 14 | 15 | std::map blocks_above; 16 | std::map blocks_below; 17 | std::map blocks_adjacent; 18 | std::map level_hist; 19 | 20 | int lava = BlockType::get_block_type_id_by_name("minecraft:lava ()"); 21 | 22 | const bool show_lava = false; 23 | 24 | for(auto ore : ores) { 25 | int x,y,z; 26 | std::tie(x,y,z) = ore; 27 | 28 | level_hist[y]++; 29 | 30 | int type = world.get_type_at(x, y + 1, z); 31 | blocks_above[type]++; 32 | if (show_lava && type == lava) { 33 | std::cout << "below lava: " << x << "," << y << "," << z << "\n"; 34 | } 35 | 36 | 37 | if (y>0) { 38 | type = world.get_type_at(x, y - 1, z); 39 | blocks_below[type]++; 40 | } 41 | 42 | std::vector > offsets = { 43 | {1,0,0}, {-1,0,0}, {0,0,1},{0,0,-1} 44 | }; 45 | 46 | for(auto offset : offsets) { 47 | int type = world.get_type_at(x + std::get<0>(offset), 48 | y + std::get<1>(offset), 49 | z + std::get<2>(offset)); 50 | blocks_adjacent[type]++; 51 | if (show_lava && type == lava) { 52 | std::cout << "next to lava: " << x << "," << y << "," << z << "\n"; 53 | } 54 | } 55 | } 56 | 57 | std::cout << "level stats\n"; 58 | for(auto lev : level_hist) { 59 | std::cout << " " << std::setw(4) << lev.first << ": " << std::setw(10) << lev.second << " " << std::setprecision(5) << (double) lev.second / ores.size() * 100.0 << "%\n"; 60 | } 61 | 62 | std::vector& > > cats = { 63 | {"blocks above",blocks_above},{"blocks below",blocks_below},{"blocks adjacent",blocks_adjacent} 64 | }; 65 | for(auto t : cats) { 66 | std::cout << t.first << "\n"; 67 | for(auto iter : t.second) { 68 | std::cout << " " << BlockType::get_block_type_by_id(iter.first).get_name() << " " << iter.second << "\n"; 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /compute_ore_stats.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef COMPUTE_ORE_STATS 4 | #define COMPUTE_ORE_STATS 5 | 6 | #include 7 | #include 8 | 9 | void 10 | show_ore_stats(MinecraftWorld &world, 11 | std::vector > &ores, 12 | int oretype); 13 | 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /gen_vtk_grid.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | points = [] 5 | lines = [] 6 | 7 | xmin = -500 8 | xmax = 1500 9 | ymin = -800 10 | ymax = 500 11 | 12 | for x in range(xmin,xmax, 16*4): 13 | lines.append("2 {} {}".format(len(points), len(points)+1)) 14 | 15 | points.append("{} 0 {}".format(x, ymin)) 16 | points.append("{} 0 {}".format(x, ymax)) 17 | 18 | 19 | for y in range(ymin,ymax, 16*4): 20 | lines.append("2 {} {}".format(len(points), len(points)+1)) 21 | 22 | points.append("{} 0 {}".format(xmin, y)) 23 | points.append("{} 0 {}".format(xmax, y)) 24 | 25 | 26 | #for y in range(ymin, ymax, 50): 27 | 28 | 29 | 30 | print("# vtk DataFile Version 1.0") 31 | print("3D triangulation data") 32 | print("ASCII") 33 | print("") 34 | print("DATASET POLYDATA") 35 | print("POINTS {} float".format(len(points))) 36 | 37 | for pt in points: 38 | print(pt) 39 | 40 | 41 | print("LINES {} {}".format(len(lines), len(lines)*3)) 42 | 43 | for line in lines: 44 | print(line) 45 | -------------------------------------------------------------------------------- /parse_bedrock.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include "leveldb/db.h" 19 | #include "leveldb/env.h" 20 | #include "leveldb/filter_policy.h" 21 | #include "leveldb/cache.h" 22 | #include "leveldb/zlib_compressor.h" 23 | #include "leveldb/decompress_allocator.h" 24 | 25 | #include "BlockType.h" 26 | #include 27 | #include 28 | 29 | #include 30 | 31 | #include 32 | 33 | // this page was really useful: 34 | // http://web.archive.org/web/20110723210920/http://www.minecraft.net/docs/NBT.txt 35 | 36 | // The MCC ToolChest PE was also invaluable in verifying my numbers. 37 | // http://www.mcctoolchest.com/ 38 | 39 | #define UNUSED(x) (void)(x) 40 | 41 | 42 | int parse_nbt_tag(leveldb::Slice& slice, int offset, NBTObject &nbtobj, std::string indent); 43 | 44 | // I could used function overloading, but I want to be sure to get the right data size. 45 | int32_t get_intval(leveldb::Slice slice, uint32_t offset) 46 | { 47 | int32_t retval = 0; 48 | // need to switch this to union based like the others. 49 | for(int i=0; i<4; i++) { 50 | // if I don't do the static cast, the top bit will be sign extended. 51 | retval |= (static_cast(slice[offset+i])<(slice[offset+i])<(slice[offset+i])< Tag_Names = { 132 | {NBT_Tags::End, "TagEnd"}, 133 | {NBT_Tags::Byte, "TagByte"}, 134 | {NBT_Tags::Short, "TagShort"}, 135 | {NBT_Tags::Int, "TagInt"}, 136 | {NBT_Tags::Long, "TagLong"}, 137 | {NBT_Tags::Float, "TagFloat"}, 138 | {NBT_Tags::ByteArray, "TagByteArray"}, 139 | {NBT_Tags::String, "TagString"}, 140 | {NBT_Tags::List, "TagList"}, 141 | {NBT_Tags::Compound, "TagCompound"}, 142 | }; 143 | 144 | // I have this function because of lists. with lists, you have a bunch of the same type 145 | // but the type and tagname is only represented once for the list 146 | int parse_nbt_payload(leveldb::Slice& slice, 147 | int curoffset, 148 | std::string &tagname, 149 | int id, 150 | NBTObject &nbtobj, 151 | std::string indent="") 152 | { 153 | NBTObject::indent = indent; 154 | 155 | switch (id) { 156 | case NBT_Tags::Compound: { 157 | NBTObject *obj_to_pass = &nbtobj; 158 | if (tagname == "ItemInHand") { 159 | assert(typeid(nbtobj) == typeid(EntityType)); 160 | obj_to_pass = &(dynamic_cast(nbtobj).get_item_in_hand()); 161 | } else if ((tagname == "states") && (typeid(nbtobj) == typeid(BlockType))) { 162 | // this is here to suppress the info message. 163 | } else if ((tagname == "tag") && (typeid(nbtobj) == typeid(EquipmentType))) { 164 | // this is here to suppress the info message. 165 | // equipment has Damage inside a tag. 166 | } else if (tagname == "") { 167 | // no sense in reporting an no-name compound 168 | } else { 169 | std::cout << indent << "compound(" << tagname << ")\n"; 170 | } 171 | while(slice[curoffset] != NBT_Tags::End) { 172 | curoffset = parse_nbt_tag(slice, curoffset, *obj_to_pass, indent + " "); 173 | } 174 | 175 | // this is to skip over the NBT_Tags::End. 176 | curoffset += 1; 177 | 178 | return curoffset; 179 | break; 180 | } 181 | 182 | case NBT_Tags::String: { 183 | // for strings, there's the nbt tag name and the value of the string itself. 184 | 185 | // now comes the length of the string itself 186 | int16_t strlength = get_shortval(slice, curoffset); 187 | curoffset += 2; 188 | 189 | std::string strvalue = get_strval(slice, curoffset, strlength); 190 | curoffset += strlength; 191 | 192 | nbtobj.add_string(tagname, strvalue); 193 | 194 | return curoffset; 195 | break; 196 | } 197 | 198 | case NBT_Tags::Byte: { 199 | int8_t byteval = (int8_t) slice[curoffset]; 200 | UNUSED(byteval); 201 | curoffset += 1; 202 | 203 | nbtobj.add_byte(tagname, byteval); 204 | 205 | return curoffset; 206 | break; 207 | } 208 | 209 | case NBT_Tags::Short: { 210 | int16_t s = get_shortval(slice,curoffset); 211 | curoffset += 2; 212 | 213 | nbtobj.add_short(tagname, s); 214 | 215 | return curoffset; 216 | break; 217 | } 218 | 219 | case NBT_Tags::Int: { 220 | int32_t intval = get_intval(slice, curoffset); 221 | UNUSED(intval); 222 | curoffset += 4; 223 | 224 | 225 | nbtobj.add_int(tagname, intval); 226 | 227 | return curoffset; 228 | break; 229 | } 230 | 231 | case NBT_Tags::Long: { 232 | long longval = get_longval(slice, curoffset); 233 | UNUSED(longval); 234 | curoffset += 8; 235 | 236 | nbtobj.add_long(tagname, longval); 237 | 238 | return curoffset; 239 | break; 240 | } 241 | case NBT_Tags::Float: { 242 | float floatval = get_floatval(slice, curoffset); 243 | UNUSED(floatval); 244 | curoffset += 4; 245 | 246 | if (typeid(nbtobj) == typeid(RotationType)) { 247 | RotationType &rot = dynamic_cast(nbtobj); 248 | rot.add_value(floatval); 249 | } else if (typeid(nbtobj) == typeid(PositionType)) { 250 | PositionType &pos = dynamic_cast(nbtobj); 251 | pos.add_value(floatval); 252 | } else if (typeid(nbtobj) == typeid(MotionType)) { 253 | MotionType &motion = dynamic_cast(nbtobj); 254 | motion.add_value(floatval); 255 | } else if (typeid(nbtobj) == typeid(Attribute)) { 256 | Attribute &attr = dynamic_cast(nbtobj); 257 | attr.add_float(tagname, floatval); 258 | } else { 259 | std::cout << indent << "float " << tagname << "(" << floatval << std::dec << ")\n"; 260 | } 261 | 262 | return curoffset; 263 | break; 264 | } 265 | 266 | case NBT_Tags::ByteArray: { 267 | // TYPE: 7 NAME: TAG_Byte_Array 268 | // Payload: TAG_Int length 269 | // An array of bytes of unspecified format. The length of this array is bytes 270 | int32_t length = get_intval(slice, curoffset); 271 | curoffset += 4; 272 | std::cout << "got byte array of length " << length << " bytes\n"; 273 | 274 | curoffset += length; 275 | 276 | return curoffset; 277 | break; 278 | } 279 | 280 | case NBT_Tags::List: { 281 | int8_t tagid = (int8_t) slice[curoffset]; 282 | curoffset += 1; 283 | 284 | int32_t length = get_intval(slice, curoffset); 285 | curoffset += 4; 286 | 287 | #if 0 288 | for(size_t i=curoffset; i<(size_t)(curoffset+20); i++) { 289 | std::cout << indent << std::hex << (unsigned int) slice[i] << std::dec << " "; 290 | } 291 | std::cout << "\n"; 292 | for(size_t i=curoffset; i<(size_t)(curoffset+100); i++) { 293 | std::cout << indent << slice[i] << std::dec; 294 | } 295 | std::cout << "\n"; 296 | #endif 297 | 298 | std::string listname{""}; 299 | 300 | if (typeid(nbtobj) == typeid(EntityType)) { 301 | EntityType &entity = dynamic_cast(nbtobj); 302 | 303 | if (tagname == "Armor") { 304 | assert(length == 4); 305 | for(int i=0; iNewIterator(readOptions); 439 | 440 | uint32_t total_blocks = 0; 441 | 442 | //BiomeImage bimage; 443 | 444 | const std::regex village_dweller_regex("VILLAGE_[0-9a-f\\-]+_DWELLERS"); 445 | const std::regex village_info_regex("VILLAGE_[0-9a-f\\-]+_INFO"); 446 | const std::regex village_player_regex("VILLAGE_[0-9a-f\\-]+_PLAYERS"); 447 | const std::regex village_poi_regex("VILLAGE_[0-9a-f\\-]+_POI"); 448 | const std::regex map_regex("map_\\-[0-9]+"); 449 | 450 | int num = 0; 451 | for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { 452 | num++; 453 | 454 | auto k = iter->key(); 455 | auto v = iter->value(); 456 | 457 | if ((k.ToString() == "Nether") || (k.ToString() == "portals")) { 458 | continue; 459 | } 460 | 461 | // the first eight bytes are chunk x and z 462 | int chunkx = k.size()>4 ? get_intval(k,0) : 0; 463 | int chunkz = k.size()>8 ? get_intval(k,4) : 0; 464 | 465 | // overworld subchunk keys are 10 long 466 | int chunky = -1; 467 | if ((k.size() == 10) && (k[8] == 0x2f)) { 468 | chunky = k[9]; 469 | } else if ((k.size() == 14) && (k[12] == 0x2f)) { 470 | // i'm skipping nether and end for the moment. 471 | continue; 472 | // neo is nether(1), end(2), overworld(omitted). 473 | int neo = get_intval(k,8); 474 | UNUSED(neo); 475 | chunky = k[11]; 476 | } else if ((k.size() == 13) && (k[12] == 0x2d)) { 477 | int neo = get_intval(k,8); 478 | 479 | std::cout << "biomes and elevation for " << chunkx << ", " << chunkz << ", " << (neo==1 ? "nether" : "end") << "\n"; 480 | 481 | continue; 482 | } else if ((k.size() == 9) && (k[8] == 0x2d)) { 483 | 484 | std::cout << "biomes and elevation for " << chunkx << ", " << chunkz << ", overworld" << "\n"; 485 | 486 | Grid16 elevations; 487 | for(int i=0; i<256; i++) { 488 | int16_t height = get_shortval(v, i*2); 489 | elevations[i%16][i/16] = height; 490 | } 491 | world.chunk_elevation[chunkx][chunkz] = elevations; 492 | 493 | Grid16 biomes; 494 | for(int i=0; i<256; i++) { 495 | // the biomes are stored after the elevations; 496 | uint8_t biome = v[(256*2)+i]; 497 | biomes[i%16][i/16] = biome; 498 | } 499 | world.chunk_biomes[chunkx][chunkz] = biomes; 500 | //std::cout << "\n"; 501 | 502 | //bimage.add_chunk(chunkx, chunkz, biomes); 503 | continue; 504 | 505 | } else if ((k.size() == 13) && (k[12] == 0x32)) { 506 | int neo = get_intval(k,8); 507 | std::cout << "entities for " << chunkx << ", " << chunkz << (neo==1 ? "nether" : "end") << "\n"; 508 | continue; 509 | 510 | } else if ((k.size() == 9) && (k[8] == 0x32)) { 511 | std::cout << "entities for " << chunkx << ", " << chunkz << ", overworld" << "\n"; 512 | 513 | auto v = iter->value(); 514 | size_t curoffset = 0; 515 | int num = 0; 516 | while (curoffset < v.size()) { 517 | num++; 518 | std::cout << "num " << num << "\n"; 519 | EntityType *entity = new EntityType(); 520 | curoffset = parse_nbt_tag(v,curoffset, *entity); 521 | std::cout << "got entity of type: " << entity->get_type() << " at "; 522 | std::cout << entity->get_position().get_string() << " and rot: "; 523 | std::cout << entity->get_rotation().get_string(); 524 | std::cout << " uid: " << entity->get_unique_id(); 525 | 526 | for(auto def : entity->get_definitions().get_defs()) { 527 | std::cout << " " << def; 528 | } 529 | 530 | std::cout << "\n";; 531 | 532 | if (entity->get_type() == "minecraft:item") { 533 | std::cout << "more info " << entity->get_string() << "\n"; 534 | } 535 | world.chunk_entities[chunkx][chunkz].push_back(entity); 536 | } 537 | 538 | std::cout << "vsize " << v.size() << " offset " << curoffset << "\n"; 539 | if (v.size() != curoffset) { std::cout << "different\n"; } 540 | 541 | continue; 542 | } else if ((k.size() == 13) && (k[12] == 0x31)) { 543 | int neo = get_intval(k,8); 544 | std::cout << "block entities for " << chunkx << ", " << chunkz << (neo==1 ? "nether" : "end") << "\n"; 545 | continue; 546 | 547 | 548 | } else if ((k.size() == 9) && (k[8] == 0x31)) { 549 | std::cout << "block entities for " << chunkx << ", " << chunkz << ", overworld" << "\n"; 550 | 551 | #if 0 552 | for(size_t i=0; i<30; i++) { 553 | std::cout << std::hex << (unsigned int) iter->value()[i] << std::dec << " "; 554 | } 555 | std::cout << "\n"; 556 | #endif 557 | 558 | auto v = iter->value(); 559 | size_t curoffset = 0; 560 | // seems like nbt would tell you how many to expect. 561 | while (curoffset < v.size()) { 562 | BlockEntityType blockentity; 563 | curoffset = parse_nbt_tag(v,curoffset, blockentity); 564 | std::cout << "got block entity of type " << blockentity.get_type() << " at " << blockentity.get_position_string() << "\n";; 565 | world.chunk_block_entities[chunkx][chunkz].push_back(blockentity); 566 | } 567 | 568 | if (v.size() != curoffset) { 569 | std::cout << "vsize " << v.size() << " offset " << curoffset << "\n"; 570 | } 571 | 572 | continue; 573 | } else if ((k.size() == 9) && (k[8] == 0x36)) { 574 | //std::cout << "finalized state for overworld\n"; 575 | continue; 576 | } else if ((k.size() == 13) && (k[12] == 0x36)) { 577 | int neo = get_intval(k,8); 578 | UNUSED(neo); 579 | //std::cout << "finalized state for << " << (neo==1 ? "nether" : "end") << "\n"; 580 | continue; 581 | } else if ((k.size() == 9) && (k[8] == 0x76)) { 582 | //std::cout << "version for overworld\n"; 583 | continue; 584 | } else if ((k.size() == 13) && (k[12] == 0x76)) { 585 | int neo = get_intval(k,8); 586 | UNUSED(neo); 587 | //std::cout << "version for " << (neo==1 ? "nether" : "end") << "\n"; 588 | continue; 589 | } else if ((k.size() == 13) && (k[12] == 0x39)) { 590 | int neo = get_intval(k,8); 591 | UNUSED(neo); 592 | std::cout << "39 data " << (neo==1 ? "nether" : "end") << " length " << v.size() << "\n"; 593 | for(size_t i=0; ivalue()[i] << std::dec << " "; 595 | } 596 | std::cout << "\n"; 597 | continue; 598 | 599 | // doesn't seem to be nbt 600 | size_t curoffset = 0; 601 | int num = 0; 602 | while (curoffset < v.size()) { 603 | num++; 604 | std::cout << "num " << num << "\n"; 605 | NBTObject nbtobj; 606 | curoffset = parse_nbt_tag(v,curoffset, nbtobj); 607 | } 608 | continue; 609 | } else if ((k.size() == 9) && (k[8] == 0x35)) { 610 | std::cout << "35 data overworld length " << v.size() << "\n"; 611 | 612 | for(size_t i=0; ivalue()[i] << std::dec << " "; 614 | } 615 | std::cout << "\n"; 616 | continue; 617 | } else if (std::regex_match(k.ToString(), village_dweller_regex) || 618 | std::regex_match(k.ToString(), village_info_regex) || 619 | std::regex_match(k.ToString(), village_player_regex) || 620 | std::regex_match(k.ToString(), village_poi_regex)) { 621 | std::cout << "village data: " << k.ToString() << "\n"; 622 | // sample data name VILLAGE_52c7ed3f-9531-4563-91b8-bcfde4151e4d_PLAYERS 623 | // the strlens should be optimized into constants. 624 | std::string vname = k.ToString().substr(strlen("VILLAGE_"), strlen("52c7ed3f-9531-4563-91b8-bcfde4151e4d")); 625 | 626 | size_t curoffset = 0; 627 | auto v = iter->value(); 628 | VillageType &village = world.get_village(vname); 629 | curoffset = parse_nbt_tag(v,curoffset, village); 630 | continue; 631 | } else if (std::regex_match(k.ToString(), map_regex)) { 632 | std::cout << "map " << k.ToString() << " length " << v.size() << "\n"; 633 | continue; 634 | } else if (k.ToString() == "BiomeData") { 635 | std::cout << "Biome data\n"; 636 | size_t curoffset = 0; 637 | auto v = iter->value(); 638 | NBTObject nbtobj; 639 | curoffset = parse_nbt_tag(v,curoffset, nbtobj); 640 | continue; 641 | } else if (k.ToString() == "AutonomousEntities") { 642 | std::cout << "AutonomousEntities\n"; 643 | size_t curoffset = 0; 644 | auto v = iter->value(); 645 | NBTObject nbtobj; 646 | curoffset = parse_nbt_tag(v,curoffset, nbtobj); 647 | continue; 648 | } else if (k.ToString() == "Overworld") { 649 | std::cout << "Overworld\n"; 650 | size_t curoffset = 0; 651 | auto v = iter->value(); 652 | NBTObject nbtobj; 653 | curoffset = parse_nbt_tag(v,curoffset, nbtobj); 654 | continue; 655 | } else if (k.ToString() == "TheEnd") { 656 | std::cout << "TheEnd\n"; 657 | continue; 658 | } else { 659 | #if 1 660 | std::cout << "non subchunk " << chunkx << ", " << chunkz << "\n"; 661 | std::cout << k.ToString() << "\n"; 662 | int neo = get_intval(k,4); 663 | UNUSED(neo); 664 | 665 | // these are the non-subchunk entries 666 | for(size_t i=0; ikey().size(); i++) { 667 | std::cout << std::hex << (unsigned int) iter->key()[i] << std::dec << " "; 668 | } 669 | std::cout << "\n"; 670 | 671 | for(size_t i=0; ivalue().size(); i++) { 672 | std::cout << std::hex << (unsigned int) iter->value()[i] << std::dec << " "; 673 | } 674 | std::cout << "\n"; 675 | 676 | #endif 677 | continue; 678 | } 679 | 680 | 681 | #if 0 682 | std::cout << "\n(" << chunkx << "," << chunkz << ") id: " << chunky; 683 | std::cout << " vsize: " << iter->value().size(); 684 | std::cout << "\n"; 685 | #endif 686 | 687 | SubChunk *curchunk = world.get_sub_chunk(chunkx, chunky, chunkz); 688 | 689 | // This format is basically documented in https://minecraft.gamepedia.com/Bedrock_Edition_level_format 690 | // in the SubChunkPrefix section. 691 | 692 | int curoffset = 0; 693 | 694 | // version is 8 num storage blocks is 1 695 | int version = v[curoffset]; 696 | curoffset++; 697 | if (version != 0x08) { 698 | std::cout << "unexpected version " << version << "\n"; 699 | } 700 | 701 | int num_storage_blocks = v[curoffset]; 702 | curoffset++; 703 | 704 | if (num_storage_blocks >1) { 705 | std::cout << "more storage blocks\n"; 706 | } 707 | 708 | // what does it mean to have multiple storage blocks? 709 | // the cases I've seen just add air water and flowing water. 710 | // I'm going to guess it's for waterlogged blocks. 711 | for(int blocknum=0; blocknum> 1; 716 | int blocksPerWord = std::floor(32.0 / bitsPerBlock); 717 | int numints = ceil(4096.0 / blocksPerWord); 718 | 719 | // the first byte of block storage is at: 720 | // 0 - Version (always 8?) 721 | // 1 - Storage count (always 1?) 722 | // 2 - first byte of first storage 723 | // 2 - storage version + bitsperblock (lsb/version should always be 0) 724 | // 3->3+(4*numints)-1 These are the block states 725 | // 2 + 4*numints is the first byte of palette storage 726 | // (assuming only one block storage) 727 | 728 | int block_data_offset = curoffset; 729 | 730 | int paletteoffset = block_data_offset+4*numints; 731 | 732 | int psize = get_intval(v,paletteoffset); 733 | paletteoffset += 4; 734 | 735 | 736 | #if 0 737 | std::cout << "palette: ("; 738 | std::cout << psize << ", " << v.size()-paletteoffset << ") "; 739 | if (psize>19) { 740 | std::cout << "large palette " << psize; 741 | } 742 | std::cout << "\n"; 743 | #endif 744 | 745 | std::vector block_types; 746 | for(int i=0; i0) { 751 | std::cout << "blocknum " << blocknum << block_type.get_name() << " " << block_type.get_string() << "\n"; 752 | } 753 | 754 | BlockType::add_block_type(block_type); 755 | int id = BlockType::get_block_type_id(block_type); 756 | block_types.push_back(id); 757 | } 758 | 759 | // this is important. there's usually only one block, but sometimes more. 760 | curoffset = paletteoffset; 761 | 762 | // Blocks are stored in XZY order (i.e. incrementing Y first) 763 | for (int i=0; i<4096; i++) { 764 | int32_t maskval = get_intval(v, block_data_offset+(i/blocksPerWord)*4); 765 | long unsigned int block_val = (maskval >> ((i%blocksPerWord)*bitsPerBlock)) & ((1<= block_types.size()) { 767 | std::cout << "oob\n"; 768 | } 769 | 770 | int block_type_id = block_types[block_val]; 771 | #if 0 772 | int x = chunkx*16 + ((i>>8) & 0xf); 773 | int z = chunkz*16 + ((i>>4) & 0xf); 774 | int y = chunky*16 + ((i>>0) & 0xf); 775 | // doing it this way is a bit slow since the subchunk has to be looked up every time. 776 | // but we're loading data chunk by chunk. 777 | world.set_type_at(x,y,z, block_types[block_val]); 778 | #else 779 | 780 | int x = ((i>>8) & 0xf); 781 | int z = ((i>>4) & 0xf); 782 | int y = ((i>>0) & 0xf); 783 | 784 | BlockType &bt = BlockType::get_block_type_by_id(block_type_id); 785 | if (blocknum==0) { 786 | curchunk->set_type_at(x,y,z, block_type_id); 787 | bt.incr_count(); 788 | } else { 789 | if(bt.get_name() != "minecraft:air") { 790 | std::cout << "alternate at " << chunkx*16+x << ", " << chunky*16+y << ", " << chunkz*16+z << bt.get_name() << "\n"; 791 | } 792 | } 793 | #endif 794 | 795 | total_blocks++; 796 | #if 0 797 | if (block_types[block_val] == 19) { 798 | std::cout << "at (" << x << ", " << y << ", " << z 799 | 800 | << ") idx " << block_val << "\n"; 801 | } 802 | #endif 803 | 804 | } 805 | } 806 | } 807 | std::cout << "total blocks " << total_blocks << "\n"; 808 | 809 | 810 | //BlockType::print_block_types(); 811 | 812 | delete iter; 813 | delete db; 814 | 815 | world.populate_extra(); 816 | } 817 | -------------------------------------------------------------------------------- /parse_bedrock.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef PARSE_BEDROCK_HH 4 | #define PARSE_BEDROCK_HH 5 | 6 | #include 7 | #include 8 | 9 | 10 | void parse_bedrock(std::string dbpath, MinecraftWorld &world); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /polygon.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | // these are to simplify the polygons. 5 | #include 6 | #include 7 | 8 | 9 | void add_rectangle_to_polygon_set(PolygonSet &theset, int xl, int zl, int xh, int zh) 10 | { 11 | typedef gtl::polygon_traits::point_type Point; 12 | // we extend the center of the line, giving up a pointy diamond. 13 | Point pts[] = { Point{xl, zl}, 14 | Point{xh, zl}, 15 | Point{xh, zh}, 16 | Point{xl, zh}}; 17 | 18 | Polygon rect; 19 | gtl::set_points(rect, pts, pts+sizeof(pts)/sizeof(Point)); 20 | 21 | theset.push_back(rect); 22 | } 23 | 24 | void add_rectangle_to_polygon_set(PolygonHolesSet &theset, int xl, int zl, int xh, int zh) 25 | { 26 | typedef gtl::polygon_traits::point_type Point; 27 | // we extend the center of the line, giving up a pointy diamond. 28 | Point pts[] = { Point{xl, zl}, 29 | Point{xh, zl}, 30 | Point{xh, zh}, 31 | Point{xl, zh}}; 32 | 33 | Polygon_Holes rect; 34 | gtl::set_points(rect, pts, pts+sizeof(pts)/sizeof(Point)); 35 | 36 | theset.push_back(rect); 37 | } 38 | 39 | nlohmann::json polygon_to_json(Polygon &polygon) 40 | { 41 | nlohmann::json boundary; 42 | 43 | nlohmann::json ring; 44 | std::vector points; 45 | for (auto point : polygon) { 46 | ring.push_back({gtl::x(point), gtl::y(point)}); 47 | } 48 | ring.push_back(ring.front()); 49 | 50 | // I'm using push_back because coordinates is a list of rings/boundaries. 51 | // The first is the outer bounds. After that come the holes. 52 | boundary.push_back(ring); 53 | 54 | return boundary; 55 | } 56 | 57 | nlohmann::json polygon_to_json(Polygon_Holes &polygon) 58 | { 59 | nlohmann::json boundary; 60 | 61 | nlohmann::json ring; 62 | for (auto point : polygon) { 63 | ring.push_back({gtl::x(point), gtl::y(point)}); 64 | } 65 | ring.push_back(ring.front()); 66 | 67 | // I'm using push_back because coordinates is a list of rings/boundaries. 68 | // The first is the outer bounds. After that come the holes. 69 | boundary.push_back(ring); 70 | 71 | for(auto hiter = gtl::begin_holes(polygon); hiter!= gtl::end_holes(polygon); hiter++) { 72 | nlohmann::json ring; 73 | for (auto point : *hiter) { 74 | ring.push_back({gtl::x(point), gtl::y(point)}); 75 | } 76 | ring.push_back(ring.front()); 77 | 78 | boundary.push_back(ring); 79 | } 80 | 81 | return boundary; 82 | } 83 | 84 | std::optional polygon_to_json_simplified(Polygon_Holes &polygon) 85 | { 86 | 87 | namespace bg = boost::geometry; 88 | typedef bg::model::point point_t; 89 | typedef bg::model::polygon polygon_t; 90 | 91 | polygon_t poly1; 92 | 93 | for (auto point : polygon) { 94 | bg::append(poly1.outer(), point_t(gtl::x(point), gtl::y(point))); 95 | } 96 | //bg::append(poly1.outer(), point_t(gtl::x(*polygon.begin()), gtl::y(*polygon.begin()))); 97 | 98 | poly1.inners().resize(polygon.size_holes()); 99 | int hole_num=0; 100 | for(auto hiter = gtl::begin_holes(polygon); hiter!= gtl::end_holes(polygon); hiter++, hole_num++) { 101 | auto &hole = *hiter; 102 | nlohmann::json ring; 103 | for (auto point : hole) { 104 | bg::append(poly1.inners()[hole_num], point_t(gtl::x(point), gtl::y(point))); 105 | } 106 | //bg::append(poly1.inners()[hole_num], point_t(gtl::x(*hole.begin()), gtl::y(*hole.begin()))); 107 | } 108 | 109 | polygon_t poly_simple; 110 | boost::geometry::simplify(poly1, poly_simple, .5); 111 | 112 | if (poly_simple.outer().size() == 0) { 113 | return std::optional(); 114 | } 115 | 116 | nlohmann::json boundary; 117 | 118 | nlohmann::json ring; 119 | for(auto pt : poly_simple.outer()) { 120 | ring.push_back({int(bg::get<0>(pt)), int(bg::get<1>(pt))}); 121 | } 122 | ring.push_back(ring.front()); 123 | boundary.push_back(ring); 124 | 125 | for(auto &out : poly_simple.inners()) { 126 | nlohmann::json ring; 127 | for (auto point : out) { 128 | ring.push_back({int(bg::get<0>(point)), int(bg::get<1>(point))}); 129 | } 130 | ring.push_back(ring.front()); 131 | 132 | boundary.push_back(ring); 133 | } 134 | 135 | return boundary; 136 | } 137 | -------------------------------------------------------------------------------- /polygon.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef POLYGON_HH 4 | #define POLYGON_HH 5 | 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | //https://github.com/nlohmann/json#json-as-first-class-data-type 13 | // this json header is copied from the above github. I don't know how to include 14 | // a single file as a submodule and the git report itself is pretty large. 15 | #include 16 | 17 | namespace gtl = boost::polygon; 18 | typedef gtl::polygon_90_data Polygon; 19 | typedef gtl::polygon_90_with_holes_data Polygon_Holes; 20 | typedef std::vector PolygonSet; 21 | typedef std::vector PolygonHolesSet; 22 | //typedef gtl::polygon_traits::point_type Point; 23 | typedef gtl::rectangle_data Rect; 24 | 25 | void add_rectangle_to_polygon_set(PolygonSet &theset, int xl, int zl, int xh, int zh); 26 | void add_rectangle_to_polygon_set(PolygonHolesSet &theset, int xl, int zl, int xh, int zh); 27 | 28 | 29 | nlohmann::json polygon_to_json(Polygon &polygon); 30 | nlohmann::json polygon_to_json(Polygon_Holes &polygon); 31 | std::optional polygon_to_json_simplified(Polygon_Holes &polygon); 32 | #endif 33 | -------------------------------------------------------------------------------- /show_clusters.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | bool 17 | adjacent_to_type(MinecraftWorld &world, 18 | std::vector > &ores, 19 | std::set &cluster, 20 | int othertype 21 | ) 22 | { 23 | for(int id : cluster) { 24 | int x,y,z; 25 | std::tie(x,y,z) = ores[id]; 26 | 27 | std::vector > offsets = { 28 | {1,0,0}, {-1,0,0}, {0,1,0}, {0,-1,0}, {0,0,1},{0,0,-1} 29 | }; 30 | 31 | for(auto offset : offsets) { 32 | int type = world.get_type_at(x + std::get<0>(offset), 33 | y + std::get<1>(offset), 34 | z + std::get<2>(offset)); 35 | 36 | if (type == othertype) { 37 | return true; 38 | } 39 | 40 | } 41 | } 42 | 43 | return false; 44 | } 45 | 46 | void 47 | write_cluster_vtk(std::vector > &clusters, 48 | std::vector > &ores, 49 | std::string filename) 50 | { 51 | 52 | std::ofstream outfile; 53 | outfile.open(filename); 54 | 55 | outfile << "# vtk DataFile Version 1.0\n"; 56 | outfile << "3D triangulation data\n"; 57 | outfile << "ASCII\n"; 58 | outfile << std::endl; 59 | outfile << "DATASET POLYDATA\n"; 60 | 61 | 62 | 63 | std::vector points; 64 | for (auto cluster : clusters) { 65 | float x=0; 66 | float y=0; 67 | float z=0; 68 | 69 | for (auto loc : cluster) { 70 | x += std::get<0>(ores[loc]); 71 | y += std::get<1>(ores[loc]); 72 | z += std::get<2>(ores[loc]); 73 | } 74 | std::stringstream ss; 75 | ss << x/cluster.size() << " " << y/cluster.size() << " " << z/cluster.size() << "\n"; 76 | points.push_back(ss.str()); 77 | } 78 | 79 | outfile << "POINTS " << points.size() << " float\n"; 80 | for (auto str : points) { 81 | outfile << str; 82 | } 83 | 84 | 85 | outfile.close(); 86 | } 87 | 88 | int 89 | main(int argc,char* argv[]) 90 | { 91 | 92 | MinecraftWorld world; 93 | 94 | //std::string dbpath = "/bubba/electronicsDS/minecraft/sampledata/2019.11.26.04.00.53/worlds/Bedrock level/db"; 95 | std::string dbpath = "/bubba/electronicsDS/minecraft/gfhtx2/gfHTXZk9AQA/db"; 96 | 97 | parse_bedrock(dbpath, world); 98 | 99 | int num_chunks = world.num_chunks(); 100 | 101 | // 16 subchunks/chunk and 16*16*16 per subchunk 102 | std::cout << "num chunks " << num_chunks << "\n"; 103 | std::cout << "num blocks in world " << num_chunks*16*16*16*16 << "\n"; 104 | 105 | std::map layers = { 106 | {"diamond_cluster.vtk", "minecraft:diamond_ore ()"}, 107 | {"lapis_cluster.vtk", "minecraft:lapis_ore ()"}, 108 | {"iron_cluster.vtk", "minecraft:iron_ore ()"}, 109 | {"redstone_cluster.vtk", "minecraft:redstone_ore ()"}, 110 | {"spawner_cluster.vtk", "minecraft:mob_spawner ()"}, 111 | }; 112 | 113 | int air_id = BlockType::get_block_type_id_by_name("minecraft:air ()"); 114 | 115 | std::map > cluster_stats; 116 | int max_cluster_size = 0; 117 | 118 | std::map > level_stats; 119 | std::map totals; 120 | int max_level = 0; 121 | 122 | for(auto layer : layers) { 123 | int ore_id = BlockType::get_block_type_id_by_name(layer.second); 124 | std::cout << "filtering by " << ore_id << "\n"; 125 | 126 | std::vector > ores; 127 | 128 | for (auto scix : world.theworld) { 129 | //int chunkx = scix.first; 130 | for (auto sciy : scix.second) { 131 | //int chunky = sciy.first; 132 | for (auto sciz : sciy.second) { 133 | //int chunkz = sciz.first; 134 | auto sc = sciz.second; 135 | 136 | for (auto iter=sc->begin(ore_id); iter!=sc->end(); ++iter) { 137 | auto loc = *iter; 138 | ores.push_back({loc.x, loc.y, loc.z}); 139 | level_stats[layer.second][loc.y]++; 140 | totals[layer.second]++; 141 | max_level = std::max(max_level, loc.y); 142 | } 143 | } 144 | } 145 | } 146 | 147 | std::vector > clusters; 148 | compute_mst(ores, clusters, 2); 149 | std::cout << "Total number ores: " << ores.size() << " in " << clusters.size() << " clusters\n"; 150 | 151 | write_cluster_vtk(clusters, ores, layer.first); 152 | 153 | 154 | int nexttoair=0; 155 | int ores_represented=0; 156 | std::map sizehist; 157 | 158 | // found_via_tunnel[l][p] 159 | // where l is the layer of your feet. 160 | // p is the pitch/spacing of the tunnels. p=3 means you go down x=0,2,5,8... 161 | std::map > found_via_tunnel; 162 | 163 | 164 | for (auto cluster : clusters) { 165 | sizehist[cluster.size()]++; 166 | if (adjacent_to_type(world, ores, cluster, air_id)) { 167 | nexttoair++; 168 | ores_represented += cluster.size(); 169 | 170 | // for(auto id : cluster) { 171 | // int x,y,z; 172 | // std::tie(x,y,z) = ores[id]; 173 | // //std::cout << " (" << x << ", " << y << ", " << z << "\n"; 174 | //} 175 | } else { 176 | // std::cout << "not adjacent\n"; 177 | // for(auto id : cluster) { 178 | // int x,y,z; 179 | // std::tie(x,y,z) = ores[id]; 180 | // std::cout << " (" << x << ", " << y << ", " << z << "\n"; 181 | //} 182 | } 183 | } 184 | 185 | 186 | std::vector levels = {2,3,4,5,6,7,8,9,10,11,12,13,14,15}; 187 | 188 | // 0 pitch means you tunnel every row. 100% digging 189 | // 1 pitch means you tunnel every other row. 50& digging 190 | // 2 pitch means you skip two inbetween each. This seems to be the most commonly mentioned one. 191 | 192 | // let's say there's a diamond at location 123 and your tunnel pitch is 3... 193 | // _XXX_XXX_XXX_XXX_XXX_X 194 | // 1 2 195 | // 0123456789012345678901 196 | // 197 | // x%(3+1) 0123012301230123012301 198 | // 199 | // so x%(pitch+1) will be == 0 for x's 0,4,8,12,16 --> the diamond with be in front of you. 200 | // x%(pitch+1) will be == 1 for x's 1,5,9,13,13,17,21 -> the diamond is to the right of a tunnel 201 | // x%(pitch+1) will be == 3(AKA pitch) for x's 3,7,11,15,19 -> the diamond is the left of a tunnel. 202 | // other values will be missed. 203 | 204 | std::vector pitches = {0,1,2,3,4,5,6,7,8,9,10}; 205 | 206 | 207 | for(int level : levels) { 208 | for(int pitch : pitches) { 209 | for (auto cluster : clusters) { 210 | bool would_be_found=false; 211 | for(int ore : cluster) { 212 | int x,y,z; 213 | std::tie(x,y,z) = ores[ore]; 214 | 215 | // is the ore to the left or right? 216 | int loc_in_pitch = x%(pitch+1); 217 | if ((level == y) && 218 | ((loc_in_pitch == 0) || // the ore will be in front of you 219 | (loc_in_pitch == 1) || // the ore will be to the right 220 | (loc_in_pitch == (pitch-1)))) { // the ore with be to the left. 221 | would_be_found = true; 222 | break; 223 | } 224 | 225 | // the diamond needs in your tunnel row and somewhere between one below your feet upto 226 | // two above your feet. 227 | if ((loc_in_pitch == 0) && 228 | (y >= (level-1)) && 229 | (y <= (level+2))) { 230 | would_be_found = true; 231 | break; 232 | } 233 | 234 | } // cluster 235 | if (would_be_found) { 236 | found_via_tunnel[level][pitch] += cluster.size(); 237 | } 238 | } // clusters 239 | 240 | } // pitches 241 | } // levels 242 | std::cout << std::setprecision(4); 243 | 244 | 245 | std::cout << " % of the two layers you have to dig\n"; 246 | 247 | std::cout << "pitch/level, "; 248 | for(int pitch : pitches) { 249 | std::cout << std::setw(6) << pitch << "/" << std::setw(6) << 1.0 / (pitch+1.0) * 100.0 << ","; 250 | } 251 | std::cout << "\n"; 252 | 253 | 254 | for(int level : levels) { 255 | std::cout << "level " << std::setw(6) << level << ","; 256 | for(int pitch : pitches) { 257 | double percent_found = (double) found_via_tunnel[level][pitch] / ores.size(); 258 | 259 | // let's say there is a total of D diamonds and a total of B blocks per level. 260 | // when tunnelling you have to tunnel 2 levels. 261 | // so the number of blocks dug is 2B/(pitch+1.0) 262 | // The number of diamonds found is D*%found 263 | // so work/diamond is: 2B/(pitch+1.0) * 1/(D*%found) 264 | // or 2B 265 | // --------------------- 266 | // (pitch+1.0)*D*%found 267 | // We'll drop 2B and D 268 | 269 | // D is equal to ores.size() 270 | // B is equal to num_chunks*16*16 271 | double work_per_diamond = (2.0*num_chunks*16*16) / (pitch+1.0) / (percent_found * ores.size()); 272 | 273 | std::cout << std::setw(6) << 100.0*percent_found << "/"; 274 | std::cout << std::setw(6) << work_per_diamond << ","; 275 | } 276 | std::cout << "\n"; 277 | } 278 | std::cout << "\n"; 279 | 280 | std::cout << "Cluster distribution:\n"; 281 | for(auto h : sizehist) { 282 | std::cout << " " << h.first << ": " << h.second << "\n"; 283 | cluster_stats[layer.second][h.first] = h.second; 284 | max_cluster_size = std::max(max_cluster_size, h.first); 285 | } 286 | 287 | std::cout << "out of " << clusters.size() << " clusters, " << nexttoair << 288 | " are next to air representing " << ores_represented << " ores\n"; 289 | 290 | 291 | show_ore_stats(world, ores, ore_id); 292 | } 293 | 294 | for(auto iter : cluster_stats) { 295 | std::cout << iter.first << ", "; 296 | } 297 | std::cout << "\n"; 298 | 299 | for(int i=1; i <= max_cluster_size; i++) { 300 | std::cout << i; 301 | for(auto iter : cluster_stats) { 302 | std::cout << ", " << iter.second[i]; 303 | } 304 | std::cout << "\n"; 305 | } 306 | 307 | for(auto iter : level_stats) { 308 | std::cout << iter.first << "(" << totals[iter.first] << "),"; 309 | } 310 | std::cout << "\n"; 311 | 312 | for(int i=0; i 10 | +#include 11 | 12 | namespace leveldb { 13 | void SnappyCompressor::compressImpl(const char* input, size_t length, ::std::string& output) const 14 | @@ -27,4 +27,4 @@ namespace leveldb { 15 | 16 | } 17 | 18 | -#endif 19 | \ No newline at end of file 20 | +#endif 21 | -------------------------------------------------------------------------------- /table_test.cc.patch: -------------------------------------------------------------------------------- 1 | diff --git a/table/table_test.cc b/table/table_test.cc 2 | index 05cb80e..b969447 100644 3 | --- a/table/table_test.cc 4 | +++ b/table/table_test.cc 5 | @@ -828,10 +828,11 @@ TEST(TableTest, ApproximateOffsetOfPlain) { 6 | } 7 | 8 | static bool SnappyCompressionSupported() { 9 | + return true; 10 | #ifdef SNAPPY 11 | std::string out; 12 | Slice in = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"; 13 | - return port::Snappy_Compress(in.data(), in.size(), &out); 14 | + //return port::Snappy_Compress(in.data(), in.size(), &out); 15 | #else 16 | return false; 17 | #endif 18 | @@ -854,7 +855,7 @@ TEST(TableTest, ApproximateOffsetOfCompressed) { 19 | KVMap kvmap; 20 | Options options; 21 | options.block_size = 1024; 22 | - options.compressors[0] = new leveldb::SnappyCompressor(); 23 | + //options.compressors[0] = new leveldb::SnappyCompressor(); 24 | c.Finish(options, &keys, &kvmap); 25 | 26 | // Expected upper and lower bounds of space used by compressible strings. 27 | -------------------------------------------------------------------------------- /type_mst.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | 15 | int 16 | main(int argc,char* argv[]) 17 | { 18 | 19 | MinecraftWorld world; 20 | 21 | //std::string dbpath = "/bubba/electronicsDS/minecraft/sampledata/2019.11.26.04.00.53/worlds/Bedrock level/db"; 22 | std::string dbpath = "/bubba/electronicsDS/minecraft/gfhtx2/gfHTXZk9AQA/db"; 23 | 24 | parse_bedrock(dbpath, world); 25 | 26 | std::map layers = { 27 | {"diamond.vtk", "minecraft:diamond_ore ()"}, 28 | {"lapis.vtk", "minecraft:lapis_ore ()"}, 29 | {"iron.vtk", "minecraft:iron_ore ()"}, 30 | {"redstone.vtk", "minecraft:redstone_ore ()"}, 31 | {"spawner.vtk", "minecraft:mob_spawner ()"}, 32 | }; 33 | 34 | 35 | for(auto layer : layers) { 36 | int ore_id = BlockType::get_block_type_id_by_name(layer.second); 37 | std::cout << "filtering by " << ore_id << "\n"; 38 | 39 | std::vector > ores; 40 | 41 | for (auto scix : world.theworld) { 42 | //int chunkx = scix.first; 43 | for (auto sciy : scix.second) { 44 | //int chunky = sciy.first; 45 | for (auto sciz : sciy.second) { 46 | //int chunkz = sciz.first; 47 | auto sc = sciz.second; 48 | 49 | for (auto iter=sc->begin(ore_id); iter!=sc->end(); ++iter) { 50 | auto loc = *iter; 51 | ores.push_back({loc.x, loc.y, loc.z}); 52 | } 53 | } 54 | } 55 | } 56 | 57 | compute_mst(ores, 4, layer.first); 58 | 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /web_stuff/MyGeoJSON.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | var __extends = (this && this.__extends) || (function () { 5 | var extendStatics = function (d, b) { 6 | extendStatics = Object.setPrototypeOf || 7 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 8 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 9 | return extendStatics(d, b); 10 | }; 11 | return function (d, b) { 12 | extendStatics(d, b); 13 | function __() { this.constructor = d; } 14 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 15 | }; 16 | })(); 17 | 18 | 19 | import { assert } from 'ol/asserts.js'; 20 | import Feature from 'ol/Feature.js'; 21 | import { transformGeometryWithOptions } from 'ol/format/Feature.js'; 22 | import JSONFeature from 'ol/format/JSONFeature.js'; 23 | import GeometryCollection from 'ol/geom/GeometryCollection.js'; 24 | import LineString from 'ol/geom/LineString.js'; 25 | import MultiLineString from 'ol/geom/MultiLineString.js'; 26 | import MultiPoint from 'ol/geom/MultiPoint.js'; 27 | import MultiPolygon from 'ol/geom/MultiPolygon.js'; 28 | import Point from 'ol/geom/Point.js'; 29 | import Polygon from 'ol/geom/Polygon.js'; 30 | import { assign, isEmpty } from 'ol/obj.js'; 31 | import { get as getProjection } from 'ol/proj.js'; 32 | import GeometryType from 'ol/geom/GeometryType.js'; 33 | 34 | 35 | 36 | var MyGeoJSON = /** @class */ (function (_super) { 37 | __extends(MyGeoJSON, _super); 38 | 39 | 40 | function MyGeoJSON(yl, yh, opt_options) { 41 | var _this = this; 42 | var options = opt_options ? opt_options : {}; 43 | _this = _super.call(this) || this; 44 | /** 45 | * @inheritDoc 46 | */ 47 | _this.dataProjection = getProjection(options.dataProjection ? 48 | options.dataProjection : 'EPSG:4326'); 49 | if (options.featureProjection) { 50 | _this.defaultFeatureProjection = getProjection(options.featureProjection); 51 | } 52 | /** 53 | * Name of the geometry attribute for features. 54 | * @type {string|undefined} 55 | * @private 56 | */ 57 | _this.geometryName_ = options.geometryName; 58 | /** 59 | * Look for the geometry name in the feature GeoJSON 60 | * @type {boolean|undefined} 61 | * @private 62 | */ 63 | _this.extractGeometryName_ = options.extractGeometryName; 64 | 65 | _this.yl = yl; 66 | _this.yh = yh; 67 | return _this; 68 | } 69 | /** 70 | * @inheritDoc 71 | */ 72 | MyGeoJSON.prototype.readFeatureFromObject = function (object, opt_options) { 73 | /** 74 | * @type {MyGeoJSONFeature} 75 | */ 76 | var geoJSONFeature = null; 77 | if (object['type'] === 'Feature') { 78 | geoJSONFeature = /** @type {MyGeoJSONFeature} */ (object); 79 | } 80 | else { 81 | geoJSONFeature = { 82 | 'type': 'Feature', 83 | 'geometry': /** @type {MyGeoJSONGeometry} */ (object), 84 | 'properties': null 85 | }; 86 | } 87 | var geometry = readGeometry(geoJSONFeature['geometry'], opt_options, this.yl, this.yh); 88 | var feature = new Feature(); 89 | if (this.geometryName_) { 90 | feature.setGeometryName(this.geometryName_); 91 | } 92 | else if (this.extractGeometryName_ && 'geometry_name' in geoJSONFeature !== undefined) { 93 | feature.setGeometryName(geoJSONFeature['geometry_name']); 94 | } 95 | feature.setGeometry(geometry); 96 | if ('id' in geoJSONFeature) { 97 | feature.setId(geoJSONFeature['id']); 98 | } 99 | if (geoJSONFeature['properties']) { 100 | feature.setProperties(geoJSONFeature['properties'], true); 101 | } 102 | return feature; 103 | }; 104 | /** 105 | * @inheritDoc 106 | */ 107 | MyGeoJSON.prototype.readFeaturesFromObject = function (object, opt_options) { 108 | var geoJSONObject = /** @type {MyGeoJSONObject} */ (object); 109 | /** @type {Array} */ 110 | var features = null; 111 | if (geoJSONObject['type'] === 'FeatureCollection') { 112 | var geoJSONFeatureCollection = /** @type {MyGeoJSONFeatureCollection} */ (object); 113 | features = []; 114 | var geoJSONFeatures = geoJSONFeatureCollection['features']; 115 | for (var i = 0, ii = geoJSONFeatures.length; i < ii; ++i) { 116 | features.push(this.readFeatureFromObject(geoJSONFeatures[i], opt_options)); 117 | } 118 | } 119 | else { 120 | features = [this.readFeatureFromObject(object, opt_options)]; 121 | } 122 | return features; 123 | }; 124 | /** 125 | * @inheritDoc 126 | */ 127 | MyGeoJSON.prototype.readGeometryFromObject = function (object, opt_options, yl, yh) { 128 | return readGeometry(/** @type {MyGeoJSONGeometry} */ (object), opt_options, yl, yh); 129 | }; 130 | /** 131 | * @inheritDoc 132 | */ 133 | MyGeoJSON.prototype.readProjectionFromObject = function (object) { 134 | var crs = object['crs']; 135 | var projection; 136 | if (crs) { 137 | if (crs['type'] == 'name') { 138 | projection = getProjection(crs['properties']['name']); 139 | } 140 | else { 141 | assert(false, 36); // Unknown SRS type 142 | } 143 | } 144 | else { 145 | projection = this.dataProjection; 146 | } 147 | return ( 148 | /** @type {import("../proj/Projection.js").default} */ (projection)); 149 | }; 150 | /** 151 | * Encode a feature as a MyGeoJSON Feature object. 152 | * 153 | * @param {import("../Feature.js").default} feature Feature. 154 | * @param {import("./Feature.js").WriteOptions=} opt_options Write options. 155 | * @return {MyGeoJSONFeature} Object. 156 | * @override 157 | * @api 158 | */ 159 | MyGeoJSON.prototype.writeFeatureObject = function (feature, opt_options) { 160 | opt_options = this.adaptOptions(opt_options); 161 | /** @type {MyGeoJSONFeature} */ 162 | var object = { 163 | 'type': 'Feature', 164 | geometry: null, 165 | properties: null 166 | }; 167 | var id = feature.getId(); 168 | if (id !== undefined) { 169 | object.id = id; 170 | } 171 | var geometry = feature.getGeometry(); 172 | if (geometry) { 173 | object.geometry = writeGeometry(geometry, opt_options); 174 | } 175 | var properties = feature.getProperties(); 176 | delete properties[feature.getGeometryName()]; 177 | if (!isEmpty(properties)) { 178 | object.properties = properties; 179 | } 180 | return object; 181 | }; 182 | /** 183 | * Encode an array of features as a MyGeoJSON object. 184 | * 185 | * @param {Array} features Features. 186 | * @param {import("./Feature.js").WriteOptions=} opt_options Write options. 187 | * @return {MyGeoJSONFeatureCollection} MyGeoJSON Object. 188 | * @override 189 | * @api 190 | */ 191 | MyGeoJSON.prototype.writeFeaturesObject = function (features, opt_options) { 192 | opt_options = this.adaptOptions(opt_options); 193 | var objects = []; 194 | for (var i = 0, ii = features.length; i < ii; ++i) { 195 | objects.push(this.writeFeatureObject(features[i], opt_options)); 196 | } 197 | return { 198 | type: 'FeatureCollection', 199 | features: objects 200 | }; 201 | }; 202 | /** 203 | * Encode a geometry as a MyGeoJSON object. 204 | * 205 | * @param {import("../geom/Geometry.js").default} geometry Geometry. 206 | * @param {import("./Feature.js").WriteOptions=} opt_options Write options. 207 | * @return {MyGeoJSONGeometry|MyGeoJSONGeometryCollection} Object. 208 | * @override 209 | * @api 210 | */ 211 | MyGeoJSON.prototype.writeGeometryObject = function (geometry, opt_options) { 212 | return writeGeometry(geometry, this.adaptOptions(opt_options)); 213 | }; 214 | return MyGeoJSON; 215 | }(JSONFeature)); 216 | /** 217 | * @param {MyGeoJSONGeometry|MyGeoJSONGeometryCollection} object Object. 218 | * @param {import("./Feature.js").ReadOptions=} opt_options Read options. 219 | * @return {import("../geom/Geometry.js").default} Geometry. 220 | */ 221 | function readGeometry(object, opt_options, yl, yh) { 222 | if (!object) { 223 | return null; 224 | } 225 | 226 | if (('coordinates' in object) && (object['coordinates'].length > 0)) { 227 | let coords = object['coordinates'] 228 | 229 | // this is for things like polygons. coordinates has a bunch of 230 | // lists of pairs. the first one is the boundary and the ones after 231 | // are holes. 232 | if (Array.isArray(coords[0])) { 233 | for (let poly of coords) { 234 | for (let pt of poly) { 235 | pt[1] = yl+yh-pt[1]; 236 | } 237 | } 238 | } else { 239 | coords[1] = yl+yh-coords[1]; 240 | } 241 | } 242 | 243 | /** 244 | * @type {import("../geom/Geometry.js").default} 245 | */ 246 | var geometry; 247 | switch (object['type']) { 248 | case GeometryType.POINT: { 249 | geometry = readPointGeometry(/** @type {MyGeoJSONPoint} */ (object)); 250 | break; 251 | } 252 | case GeometryType.LINE_STRING: { 253 | geometry = readLineStringGeometry(/** @type {MyGeoJSONLineString} */ (object)); 254 | break; 255 | } 256 | case GeometryType.POLYGON: { 257 | geometry = readPolygonGeometry(/** @type {MyGeoJSONPolygon} */ (object)); 258 | break; 259 | } 260 | case GeometryType.MULTI_POINT: { 261 | geometry = readMultiPointGeometry(/** @type {MyGeoJSONMultiPoint} */ (object)); 262 | break; 263 | } 264 | case GeometryType.MULTI_LINE_STRING: { 265 | geometry = readMultiLineStringGeometry(/** @type {MyGeoJSONMultiLineString} */ (object)); 266 | break; 267 | } 268 | case GeometryType.MULTI_POLYGON: { 269 | geometry = readMultiPolygonGeometry(/** @type {MyGeoJSONMultiPolygon} */ (object)); 270 | break; 271 | } 272 | case GeometryType.GEOMETRY_COLLECTION: { 273 | geometry = readGeometryCollectionGeometry(/** @type {MyGeoJSONGeometryCollection} */ (object)); 274 | break; 275 | } 276 | default: { 277 | throw new Error('Unsupported MyGeoJSON type: ' + object.type); 278 | } 279 | } 280 | return transformGeometryWithOptions(geometry, false, opt_options); 281 | } 282 | /** 283 | * @param {MyGeoJSONGeometryCollection} object Object. 284 | * @param {import("./Feature.js").ReadOptions=} opt_options Read options. 285 | * @return {GeometryCollection} Geometry collection. 286 | */ 287 | function readGeometryCollectionGeometry(object, opt_options) { 288 | var geometries = object['geometries'].map( 289 | /** 290 | * @param {MyGeoJSONGeometry} geometry Geometry. 291 | * @return {import("../geom/Geometry.js").default} geometry Geometry. 292 | */ 293 | function (geometry) { 294 | return readGeometry(geometry, opt_options); 295 | }); 296 | return new GeometryCollection(geometries); 297 | } 298 | /** 299 | * @param {MyGeoJSONPoint} object Object. 300 | * @return {Point} Point. 301 | */ 302 | function readPointGeometry(object) { 303 | return new Point(object['coordinates']); 304 | } 305 | /** 306 | * @param {MyGeoJSONLineString} object Object. 307 | * @return {LineString} LineString. 308 | */ 309 | function readLineStringGeometry(object) { 310 | return new LineString(object['coordinates']); 311 | } 312 | /** 313 | * @param {MyGeoJSONMultiLineString} object Object. 314 | * @return {MultiLineString} MultiLineString. 315 | */ 316 | function readMultiLineStringGeometry(object) { 317 | return new MultiLineString(object['coordinates']); 318 | } 319 | /** 320 | * @param {MyGeoJSONMultiPoint} object Object. 321 | * @return {MultiPoint} MultiPoint. 322 | */ 323 | function readMultiPointGeometry(object) { 324 | return new MultiPoint(object['coordinates']); 325 | } 326 | /** 327 | * @param {MyGeoJSONMultiPolygon} object Object. 328 | * @return {MultiPolygon} MultiPolygon. 329 | */ 330 | function readMultiPolygonGeometry(object) { 331 | return new MultiPolygon(object['coordinates']); 332 | } 333 | /** 334 | * @param {MyGeoJSONPolygon} object Object. 335 | * @return {Polygon} Polygon. 336 | */ 337 | function readPolygonGeometry(object) { 338 | return new Polygon(object['coordinates']); 339 | } 340 | /** 341 | * @param {import("../geom/Geometry.js").default} geometry Geometry. 342 | * @param {import("./Feature.js").WriteOptions=} opt_options Write options. 343 | * @return {MyGeoJSONGeometry} MyGeoJSON geometry. 344 | */ 345 | function writeGeometry(geometry, opt_options) { 346 | geometry = transformGeometryWithOptions(geometry, true, opt_options); 347 | var type = geometry.getType(); 348 | /** @type {MyGeoJSONGeometry} */ 349 | var geoJSON; 350 | switch (type) { 351 | case GeometryType.POINT: { 352 | geoJSON = writePointGeometry(/** @type {Point} */ (geometry), opt_options); 353 | break; 354 | } 355 | case GeometryType.LINE_STRING: { 356 | geoJSON = writeLineStringGeometry(/** @type {LineString} */ (geometry), opt_options); 357 | break; 358 | } 359 | case GeometryType.POLYGON: { 360 | geoJSON = writePolygonGeometry(/** @type {Polygon} */ (geometry), opt_options); 361 | break; 362 | } 363 | case GeometryType.MULTI_POINT: { 364 | geoJSON = writeMultiPointGeometry(/** @type {MultiPoint} */ (geometry), opt_options); 365 | break; 366 | } 367 | case GeometryType.MULTI_LINE_STRING: { 368 | geoJSON = writeMultiLineStringGeometry(/** @type {MultiLineString} */ (geometry), opt_options); 369 | break; 370 | } 371 | case GeometryType.MULTI_POLYGON: { 372 | geoJSON = writeMultiPolygonGeometry(/** @type {MultiPolygon} */ (geometry), opt_options); 373 | break; 374 | } 375 | case GeometryType.GEOMETRY_COLLECTION: { 376 | geoJSON = writeGeometryCollectionGeometry(/** @type {GeometryCollection} */ (geometry), opt_options); 377 | break; 378 | } 379 | case GeometryType.CIRCLE: { 380 | geoJSON = { 381 | type: 'GeometryCollection', 382 | geometries: [] 383 | }; 384 | break; 385 | } 386 | default: { 387 | throw new Error('Unsupported geometry type: ' + type); 388 | } 389 | } 390 | return geoJSON; 391 | } 392 | /** 393 | * @param {GeometryCollection} geometry Geometry. 394 | * @param {import("./Feature.js").WriteOptions=} opt_options Write options. 395 | * @return {MyGeoJSONGeometryCollection} MyGeoJSON geometry collection. 396 | */ 397 | function writeGeometryCollectionGeometry(geometry, opt_options) { 398 | var geometries = geometry.getGeometriesArray().map(function (geometry) { 399 | var options = assign({}, opt_options); 400 | delete options.featureProjection; 401 | return writeGeometry(geometry, options); 402 | }); 403 | return { 404 | type: 'GeometryCollection', 405 | geometries: geometries 406 | }; 407 | } 408 | /** 409 | * @param {LineString} geometry Geometry. 410 | * @param {import("./Feature.js").WriteOptions=} opt_options Write options. 411 | * @return {MyGeoJSONGeometry} MyGeoJSON geometry. 412 | */ 413 | function writeLineStringGeometry(geometry, opt_options) { 414 | return { 415 | type: 'LineString', 416 | coordinates: geometry.getCoordinates() 417 | }; 418 | } 419 | /** 420 | * @param {MultiLineString} geometry Geometry. 421 | * @param {import("./Feature.js").WriteOptions=} opt_options Write options. 422 | * @return {MyGeoJSONGeometry} MyGeoJSON geometry. 423 | */ 424 | function writeMultiLineStringGeometry(geometry, opt_options) { 425 | return { 426 | type: 'MultiLineString', 427 | coordinates: geometry.getCoordinates() 428 | }; 429 | } 430 | /** 431 | * @param {MultiPoint} geometry Geometry. 432 | * @param {import("./Feature.js").WriteOptions=} opt_options Write options. 433 | * @return {MyGeoJSONGeometry} MyGeoJSON geometry. 434 | */ 435 | function writeMultiPointGeometry(geometry, opt_options) { 436 | return { 437 | type: 'MultiPoint', 438 | coordinates: geometry.getCoordinates() 439 | }; 440 | } 441 | /** 442 | * @param {MultiPolygon} geometry Geometry. 443 | * @param {import("./Feature.js").WriteOptions=} opt_options Write options. 444 | * @return {MyGeoJSONGeometry} MyGeoJSON geometry. 445 | */ 446 | function writeMultiPolygonGeometry(geometry, opt_options) { 447 | var right; 448 | if (opt_options) { 449 | right = opt_options.rightHanded; 450 | } 451 | return { 452 | type: 'MultiPolygon', 453 | coordinates: geometry.getCoordinates(right) 454 | }; 455 | } 456 | /** 457 | * @param {Point} geometry Geometry. 458 | * @param {import("./Feature.js").WriteOptions=} opt_options Write options. 459 | * @return {MyGeoJSONGeometry} MyGeoJSON geometry. 460 | */ 461 | function writePointGeometry(geometry, opt_options) { 462 | return { 463 | type: 'Point', 464 | coordinates: geometry.getCoordinates() 465 | }; 466 | } 467 | /** 468 | * @param {Polygon} geometry Geometry. 469 | * @param {import("./Feature.js").WriteOptions=} opt_options Write options. 470 | * @return {MyGeoJSONGeometry} MyGeoJSON geometry. 471 | */ 472 | function writePolygonGeometry(geometry, opt_options) { 473 | var right; 474 | if (opt_options) { 475 | right = opt_options.rightHanded; 476 | } 477 | return { 478 | type: 'Polygon', 479 | coordinates: geometry.getCoordinates(right) 480 | }; 481 | } 482 | export default MyGeoJSON; 483 | //# sourceMappingURL=MyGeoJSON.js.map 484 | -------------------------------------------------------------------------------- /web_stuff/dist/sprites/Bed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/Bed.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/Beehive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/Beehive.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/Chest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/Chest.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/Furnace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/Furnace.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/Hopper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/Hopper.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/MobSpawner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/MobSpawner.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/Torch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/Torch.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/Witch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/Witch.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/bat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/bat.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/bee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/bee.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/bee_nest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/bee_nest.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/bell.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/bell.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/blaze.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/blaze.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/cat.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/cave_spider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/cave_spider.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/chicken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/chicken.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/cow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/cow.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/creeper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/creeper.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/diamond_ore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/diamond_ore.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/dolphin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/dolphin.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/drowned.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/drowned.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/emerald_ore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/emerald_ore.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/enderman.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/enderman.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/ghast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/ghast.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/golem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/golem.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/horse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/horse.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/item.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/item.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/lapis_ore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/lapis_ore.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/llama.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/llama.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/ocelot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/ocelot.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/panda.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/panda.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/parrot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/parrot.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/pig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/pig.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/pillager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/pillager.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/rabbit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/rabbit.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/sheep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/sheep.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/skeleton.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/skeleton.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/slime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/slime.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/spider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/spider.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/squid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/squid.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/torch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/torch.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/villager_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/villager_v2.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/wandering_trader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/wandering_trader.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/whither.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/whither.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/witch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/witch.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/wolf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/wolf.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/xp_orb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/xp_orb.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/zombie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/zombie.png -------------------------------------------------------------------------------- /web_stuff/dist/sprites/zombie_villager_v2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mmccoo/minecraft_mmccoo/6d1f000530d8b19918afe1c4d93f7f853802d94a/web_stuff/dist/sprites/zombie_villager_v2.png -------------------------------------------------------------------------------- /web_stuff/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | packaged flatmap 6 | 26 | 27 | 28 |
29 | Show biomes
30 | Show entities
31 | Show passives
32 | Show Slime
33 | Show Elevations
34 | Show Caves
35 | Show Resources
36 | Show Surface Resources
37 |
time
38 |
39 |
40 | 41 |
42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /web_stuff/index.js: -------------------------------------------------------------------------------- 1 | 2 | // for this to work: 3 | // you need the following run results from basic_test (basically, you juse run basic_test in this dir): 4 | // biomeproperties.js 5 | // world.js 6 | // dist/biomes.json 7 | // dist/block_entities.json 8 | // dist/entities.json 9 | // dist/villages.json 10 | 11 | // in this directory, do 12 | // npm install 13 | // npm start 14 | // 15 | // if you want to host on apache2, do 16 | // npm run build 17 | 18 | import 'ol/ol.css'; 19 | import {Map, View, proj} from 'ol'; 20 | import TileLayer from 'ol/layer/Tile'; 21 | import {createXYZ} from 'ol/tilegrid' 22 | import Projection from 'ol/proj/Projection'; 23 | import OSM from 'ol/source/OSM'; 24 | import SourceXYZ from 'ol/source/XYZ'; 25 | import FillStyle from 'ol/style/Fill'; 26 | import StrokeStyle from 'ol/style/Stroke'; 27 | import CircleStyle from 'ol/style/Circle'; 28 | import Icon from 'ol/style/Icon'; 29 | import * as olExtent from 'ol/extent'; 30 | import MousePosition from 'ol/control/MousePosition'; 31 | import OverViewControl from 'ol/control/OverviewMap'; 32 | import VectorImage from 'ol/layer/VectorImage'; 33 | import Vector from 'ol/layer/Vector'; 34 | import SourceVector from 'ol/source/Vector'; 35 | import FormatGeoJSON from 'ol/format/GeoJSON'; 36 | import MyGeoJSON from './MyGeoJSON'; 37 | import Style from 'ol/style/Style'; 38 | import TextStyle from 'ol/style/Text'; 39 | import { createOrUpdateFromFlatCoordinates } from 'ol/extent'; 40 | import Collection from 'ol/Collection'; 41 | 42 | import Stroke from 'ol/style/Stroke'; 43 | import Polygon from 'ol/geom/Polygon.js'; 44 | import Feature from 'ol/Feature.js'; 45 | import LineString from 'ol/geom/LineString'; 46 | import Overlay from 'ol/Overlay'; 47 | 48 | // the mod icons came from here: 49 | // http://i.imgur.com/wR0Lw7o.png 50 | // https://www.minecraftforum.net/forums/mapping-and-modding-java-edition/resource-packs/1245006-free-minecraft-icon-pack-by-wskoie1 51 | // 52 | // which I split using the scripting in this post: 53 | // https://graphicdesign.stackexchange.com/a/12750 54 | 55 | // it's important that the sprites directory is not called icons: 56 | // https://electrictoolbox.com/apache-icons-directory/ 57 | 58 | // from https://github.com/depressed-pho/slime-finder-pe/blob/master/lib/chunk.ts 59 | // The function below is basically the function from slime-finder-pe verbatim. Just 60 | // converted from a class method to a dumb function 61 | var MersenneTwister = require('mersenne-twister'); 62 | 63 | // this function is lifted from https://github.com/depressed-pho/slime-finder-pe/blob/master/lib/umul32.ts 64 | function umul32_lo(a, b) { 65 | let a00 = a & 0xFFFF; 66 | let a16 = a >>> 16; 67 | let b00 = b & 0xFFFF; 68 | let b16 = b >>> 16; 69 | 70 | let c00 = a00 * b00; 71 | let c16 = c00 >>> 16; 72 | 73 | c16 += a16 * b00; 74 | c16 &= 0xFFFF; 75 | c16 += a00 * b16; 76 | 77 | let lo = c00 & 0xFFFF; 78 | let hi = c16 & 0xFFFF; 79 | 80 | return ((hi << 16) | lo) >>> 0; 81 | } 82 | 83 | function isSlimy(x,z) { 84 | /*! MCPE slime-chunk checker; reverse engineered by @protolambda and @jocopa3 85 | * Ported by PHO from Java code: 86 | * https://gist.github.com/protolambda/00b85bf34a75fd8176342b1ad28bfccc 87 | * Simplified by hhhxiao: 88 | * https://github.com/depressed-pho/slime-finder-pe/issues/2 89 | */ 90 | let x_uint = (x / 16) >>> 0; 91 | let z_uint = (z / 16) >>> 0; 92 | let seed = umul32_lo(x_uint, 0x1f1f1f1f) ^ z_uint; 93 | let mt = new MersenneTwister(seed); 94 | let n = mt.random_int(); 95 | return (n % 10 == 0); 96 | } 97 | 98 | 99 | var world_info = {}; //require('./world.js'); 100 | var biome_styles = {}; 101 | 102 | var getJSON = function(url, callback) { 103 | var xhr = new XMLHttpRequest(); 104 | xhr.open('GET', url, true); 105 | xhr.responseType = 'json'; 106 | xhr.onload = function() { 107 | var status = xhr.status; 108 | if (status === 200) { 109 | callback(null, xhr.response); 110 | } else { 111 | callback(status, xhr.response); 112 | } 113 | }; 114 | xhr.send(); 115 | }; 116 | getJSON("./map/world.json", function(err, data) { 117 | console.log(data); 118 | world_info = data; 119 | build_layers_and_map(); 120 | } 121 | ) 122 | 123 | getJSON("./map/biomeproperties.json", function(err, biomeproperties) { 124 | console.log(biomeproperties); 125 | for (var b in biomeproperties.biome_colors) { 126 | biome_styles[b] = new Style({ 127 | fill: new FillStyle({ 128 | color: biomeproperties.biome_colors[b], 129 | }), 130 | stroke: new StrokeStyle({ 131 | color: [0,0,0,1], 132 | width: 1 133 | }), 134 | }) 135 | } 136 | } 137 | ) 138 | 139 | 140 | // colors based on this: https://de.mathworks.com/help/map/ref/demcmap.html 141 | var elevation_colors = { 142 | 0: [0,0,0, 0.5], // bedrock/void black 143 | 10: [207, 74, 10, 0.5], // lava 144 | 20: [100, 240, 222, 0.5], // top of diamond level. 145 | 30: [89, 100, 0, 0.5], 146 | 40: [93, 0, 100, 0.5], 147 | 50: [0, 0, 255, 0.5], // deep blue 148 | 60: [0, 127, 255, 0.5], // lite blue 149 | 70: [0, 102, 51, 0.5], // lite green 150 | 80: [0, 100, 0, 0.5], 151 | 90: [0, 200, 0, 0.5], 152 | 100: [0, 200, 0, 0.5], 153 | 110: [90, 160, 219, 0.5], 154 | 155 | //12: [100, 240, 222], // diamond level. 156 | //53: [8,21,180], // water 157 | //65: [50, 78, 43], // grass 158 | //75: [79, 57, 40], // dirt. 159 | //128: [90, 160, 219], 160 | }; 161 | 162 | var color_fills = {}; 163 | var cur_color = new FillStyle({ color: elevation_colors[0] }); 164 | 165 | for(let i=0; i<256; i++) { 166 | if (i in elevation_colors) { 167 | cur_color = new FillStyle({ color: elevation_colors[i] }); 168 | } 169 | 170 | color_fills[i] = cur_color; 171 | } 172 | 173 | // yes, this is an awful function. It needs to be cleanedup/refactored/so many things. 174 | // It's a function because I need the world_info data. 175 | function build_layers_and_map() 176 | { 177 | const pixels_per_block = 4; 178 | // this is the boundary of the zero level tile. 179 | var mapxl = world_info.chunk_xl*16; 180 | var mapyl = world_info.chunk_zl*16; 181 | var mapxh = world_info.chunk_xl*16 + world_info.tile_0_size; 182 | var mapyh = world_info.chunk_zl*16 + world_info.tile_0_size; 183 | 184 | // this extent is in units of minecraft blocks 185 | var mapextent = [mapxl, mapyl, mapxh, mapyh]; 186 | 187 | var flatProjection = new Projection({ 188 | code: 'ZOOMIFY', 189 | units: 'pixels', 190 | // where is this projection valid. 191 | extent: [-1024*1024, -1024*1024, 1024*1024, 1024*1024], 192 | }) 193 | 194 | var tilegrid = new createXYZ({ 195 | // Extent for the tile grid. The origin for an XYZ tile grid is the top-left 196 | // corner of the extent. The zero level of the grid is defined by the resolution 197 | // at which one tile fits in the provided extent. If not provided, the extent of 198 | // the EPSG:3857 projection is used. 199 | extent: mapextent, 200 | //maxZoom: 10, 201 | tileSize: [64,64], 202 | }); 203 | var res = tilegrid.getResolutions(); 204 | 205 | var maplayer = new TileLayer({ 206 | opacity: .5, 207 | // use interim is important. it prevents repeatedly 208 | // trying to load non-existent tiles. 209 | useInterimTilesOnError: false, 210 | source: new SourceXYZ({ 211 | // default resolution with zoom 1 in view is 3. 212 | url: 'http://localhost/flatmap/tiling/{z}/biome_{x}_{y}.png', 213 | projection: flatProjection, 214 | tileGrid: tilegrid, 215 | //tileUrlFunction: function(coordinate) { 216 | // console.log(coordinate); 217 | // return 'tiles/chunk/biome_' + (coordinate[1]-20) + '_' + (coordinate[2]-20) + '.png'; 218 | //}, 219 | }), 220 | }); 221 | 222 | const fillStyle = new FillStyle({ 223 | color: [84,118,255,.5] 224 | }) 225 | 226 | const strokeStyle = new StrokeStyle({ 227 | color: [46,45,45,1], 228 | width: 1, 229 | }) 230 | 231 | const circleStyle = new CircleStyle({ 232 | fill: new FillStyle({ 233 | color: [245,49,5,1] 234 | }), 235 | radius: 7, 236 | stroke: strokeStyle, 237 | }) 238 | 239 | const iconStyle = new Style({ 240 | image: new Icon({ 241 | src: 'sprites/creeper.png' 242 | }) 243 | }) 244 | const defaultStyle = new Style({ 245 | fill: fillStyle, 246 | stroke: strokeStyle, 247 | //image: circleStyle, 248 | image: new Icon({ 249 | src: 'sprites/creeper.png' 250 | }) 251 | }) 252 | 253 | var invisibleStyle = new Style({ 254 | image: new CircleStyle({ 255 | fill: new FillStyle({ 256 | color: [245,49,5,0] 257 | }), 258 | radius: 7, 259 | stroke: new StrokeStyle({ 260 | color: [46,45,45,0], 261 | width: 1 262 | }) 263 | }), 264 | stroke: new StrokeStyle({ 265 | color: [46,45,45,0], 266 | width: 1 267 | }), 268 | }); 269 | 270 | const testJSONlayer = new VectorImage({ 271 | source: new SourceVector({ 272 | url: './test.json', 273 | //format: new FormatGeoJSON(), 274 | format: new MyGeoJSON(mapyl, mapyh), 275 | projection: flatProjection, 276 | }), 277 | visible: true, 278 | title: "my vectors", 279 | style: new Style({ 280 | fill: fillStyle, 281 | stroke: strokeStyle, 282 | image: circleStyle, 283 | }) 284 | }); 285 | 286 | var biomeStyleFunction = function(feature) { 287 | var biome = feature.get('biome'); 288 | if (!(biome in biome_styles)) { 289 | console.log("don't have color for " + biome); 290 | return defaultStyle; 291 | } 292 | return biome_styles[biome]; 293 | }; 294 | 295 | const biomesJSONlayer = new VectorImage({ 296 | source: new SourceVector({ 297 | url: './map/biomes.json', 298 | format: new MyGeoJSON(mapyl, mapyh), 299 | projection: flatProjection, 300 | }), 301 | visible: true, 302 | title: "my vectors", 303 | style: biomeStyleFunction, 304 | 305 | }); 306 | 307 | var colors = [] 308 | const cspace = 60 309 | for(let i=0; i= cspace*2) { elevation = cspace*2 } 319 | 320 | return new Style({ 321 | //fill: new FillStyle({ color: [elevation,elevation,elevation,0] }), 322 | fill: color_fills[elevation], //new FillStyle({ color: colors[elevation] }), 323 | stroke: strokeStyle, 324 | image: circleStyle, 325 | }); 326 | }; 327 | 328 | const elevationsJSONlayer = new VectorImage({ 329 | source: new SourceVector({ 330 | url: './map/elevations.json', 331 | format: new MyGeoJSON(mapyl, mapyh), 332 | projection: flatProjection, 333 | }), 334 | visible: true, 335 | title: "my vectors", 336 | style: elevationStyleFunction, 337 | }); 338 | 339 | 340 | var caveStyleFunction = function(feature) { 341 | var elevation = feature.get('elevation')*world_info.y_resolution; 342 | 343 | return new Style({ 344 | fill: color_fills[elevation], 345 | stroke: strokeStyle, 346 | image: circleStyle, 347 | }); 348 | } 349 | 350 | const cavesJSONlayer = new VectorImage({ 351 | source: new SourceVector({ 352 | url: './map/caves.json', 353 | format: new MyGeoJSON(mapyl, mapyh), 354 | projection: flatProjection, 355 | }), 356 | visible: true, 357 | title: "my vectors", 358 | style: caveStyleFunction, 359 | }); 360 | 361 | var mobstyles = {}; 362 | 363 | for (let icon of [ 364 | "blaze", 365 | "cow", 366 | "creeper", 367 | "dolphin", 368 | "enderman", 369 | "ghast", 370 | "golem", 371 | "pig", 372 | "sheep", 373 | "skeleton", 374 | "slime", 375 | "villager_v2", 376 | "zombie_villager_v2", 377 | "whither", 378 | "zombie", 379 | "spider", 380 | "cat", 381 | "horse", 382 | "chicken", 383 | "llama", 384 | "cave_spider", 385 | "drowned", 386 | "bat", 387 | "rabbit", 388 | "squid", 389 | "xp_orb", 390 | "parrot", 391 | "bee", 392 | "panda", 393 | "ocelot", 394 | "witch", 395 | "pillager", 396 | "wolf", 397 | "item", 398 | "wandering_trader", 399 | ]) { 400 | mobstyles[icon] = new Style({ 401 | fill: fillStyle, 402 | stroke: strokeStyle, 403 | image: new Icon({ 404 | src: 'sprites/' + icon + '.png', 405 | scale: 0.2, 406 | }), 407 | }) 408 | 409 | }; 410 | 411 | var passives = [ 412 | "cow", 413 | "golem", 414 | "pig", 415 | "sheep", 416 | "cat", 417 | "horse", 418 | "chicken", 419 | "llama", 420 | "bat", 421 | "rabbit", 422 | "squid", 423 | "dolphin", 424 | "xp_orb", 425 | "parrot", 426 | "bee", 427 | "panda", 428 | "ocelot", 429 | "wolf", 430 | "item", 431 | "wandering_trader", 432 | ] 433 | 434 | mobstyles['other'] = new Style({ 435 | fill: fillStyle, 436 | stroke: strokeStyle, 437 | image: circleStyle, 438 | }); 439 | 440 | 441 | var showPassives = true; 442 | var entityStyleFunction = function(feature) { 443 | var type = feature.get("type").substring(10); 444 | 445 | if (!showPassives && passives.includes(type)) { 446 | return invisibleStyle; 447 | } 448 | 449 | if (type in mobstyles) { 450 | return mobstyles[type]; 451 | } else { 452 | return mobstyles['other']; 453 | } 454 | } 455 | 456 | const entitiesJSONLayer = new VectorImage({ 457 | source: new SourceVector({ 458 | url: './map/entities.json', 459 | format: new MyGeoJSON(mapyl, mapyh), 460 | projection: flatProjection, 461 | }), 462 | visible: true, 463 | title: "my vectors", 464 | style: entityStyleFunction, 465 | }) 466 | 467 | var resourceStyles = {}; 468 | for (let icon of [ 469 | "diamond_ore", 470 | "torch", 471 | "emerald_ore", // not sure this is the right name. 472 | "lapis_ore", 473 | "bee_nest", 474 | "bell", 475 | ]) { 476 | resourceStyles[icon] = new Style({ 477 | fill: fillStyle, 478 | stroke: strokeStyle, 479 | image: new Icon({ 480 | src: 'sprites/' + icon + '.png', 481 | scale: 0.2, 482 | }), 483 | }) 484 | 485 | }; 486 | resourceStyles['other'] = new Style({ 487 | fill: fillStyle, 488 | stroke: strokeStyle, 489 | image: circleStyle, 490 | }); 491 | 492 | var showResources = true; 493 | var showSurfaceResources = true; 494 | var resourceStyleFunction = function(feature) { 495 | var type = feature.get("name").substring(10); 496 | var surface = feature.get("surface"); 497 | 498 | if (!showResources) { return invisibleStyle; } 499 | if (!showSurfaceResources && surface) { return invisibleStyle; } 500 | 501 | if (type in resourceStyles) { 502 | return resourceStyles[type]; 503 | } else { 504 | console.log("didn't find " + type); 505 | return resourceStyles['other']; 506 | } 507 | } 508 | 509 | const resourceJSONLayer = new VectorImage({ 510 | source: new SourceVector({ 511 | url: './map/resources.json', 512 | format: new MyGeoJSON(mapyl, mapyh), 513 | projection: flatProjection, 514 | }), 515 | visible: true, 516 | title: "my vectors", 517 | style: resourceStyleFunction, 518 | }) 519 | 520 | 521 | 522 | var block_styles = {} 523 | for (let icon of [ 524 | "Chest", 525 | "MobSpawner", 526 | "Bed", 527 | "Beehive", 528 | "Furnace", 529 | "Hopper", 530 | ]) { 531 | block_styles[icon] = new Style({ 532 | fill: fillStyle, 533 | stroke: strokeStyle, 534 | image: new Icon({ 535 | src: 'sprites/' + icon + '.png', 536 | scale: 0.2, 537 | }), 538 | }) 539 | 540 | }; 541 | block_styles['other'] = new Style({ 542 | fill: fillStyle, 543 | stroke: strokeStyle, 544 | image: new CircleStyle({ 545 | fill: new FillStyle({ 546 | color: [0,249,0,1] 547 | }), 548 | radius: 7, 549 | stroke: strokeStyle, 550 | }), 551 | }); 552 | 553 | var blockEntityStyleFunction = function(feature) { 554 | var type = feature.get("type"); 555 | 556 | if (type in block_styles) { 557 | //console.log("known type is " + type); 558 | return block_styles[type]; 559 | } else { 560 | console.log("unknown type is " + type); 561 | return block_styles['other']; 562 | } 563 | } 564 | 565 | const blockEntitiesJSONLayer = new VectorImage({ 566 | source: new SourceVector({ 567 | url: './map/block_entities.json', 568 | format: new MyGeoJSON(mapyl, mapyh), 569 | projection: flatProjection, 570 | }), 571 | visible: true, 572 | title: "my vectors", 573 | style: blockEntityStyleFunction, 574 | }) 575 | 576 | var villageStyleFunction = function(feature) { 577 | var retval = new Style({ 578 | fill: new FillStyle({ color: [0,118,0,.1] }), 579 | stroke: strokeStyle, 580 | image: circleStyle, 581 | text: new TextStyle({ 582 | font: '20px sans-serif', 583 | fill: new FillStyle({ color: [0,0,0,1] }), 584 | stroke: new StrokeStyle({ 585 | color: [0,0,0,1], width: 1 586 | }), 587 | // overflow is important. without it, the text won't display 588 | overflow : true, 589 | text: 'village', 590 | }) 591 | }); 592 | 593 | return retval; 594 | 595 | } 596 | const villagesJSONLayer = new Vector({ 597 | source : new SourceVector({ 598 | url: './map/villages.json', 599 | format: new MyGeoJSON(mapyl, mapyh), 600 | projection: flatProjection, 601 | }), 602 | visible: true, 603 | title: "villages", 604 | style: villageStyleFunction, 605 | }) 606 | 607 | var myview = new View({ 608 | extent: mapextent, 609 | center: [160,130], // center, 610 | zoom: 1, 611 | //maxZoom: 10, 612 | projection: flatProjection, 613 | resolutions: tilegrid.getResolutions(), 614 | }); 615 | 616 | var poly = new Polygon([ 617 | // array of polygons 618 | [ 619 | // array of points 620 | [92,-560], [169, -560], [169,-648], [92, -648], [92, -560] 621 | ] 622 | ]); 623 | 624 | var line = new LineString([ 625 | [92,-560], [169,-648] 626 | ]); 627 | 628 | var chunkbounds = []; 629 | for(let x=mapxl; x"); 823 | } 824 | 825 | for(let e in blockentities) { 826 | retval += "
blockentities: " + e + " " + blockentities[e]; 827 | } 828 | retval += "
villages:"; 829 | retval += village_sections.join("
") + "
"; 830 | 831 | retval += "elevations " + elevations.join(", ") + "
"; 832 | 833 | retval += "caves " + caves.join(", ") + "
"; 834 | 835 | retval += "resources " + resources.join("
") + ""; 836 | return retval; 837 | 838 | }; 839 | mousePositionControl.setCoordinateFormat(coordinateFormatFunction) 840 | 841 | 842 | var center = olExtent.getCenter(mapextent); 843 | const map = new Map({ 844 | target: 'map', 845 | controls: [mousePositionControl, overviewMapControl], 846 | 847 | layers: [ 848 | //maplayer, 849 | biomesJSONlayer, 850 | resourceJSONLayer, 851 | entitiesJSONLayer, 852 | blockEntitiesJSONLayer, 853 | villagesJSONLayer, 854 | ChunkBoundsLayer, 855 | elevationsJSONlayer, 856 | cavesJSONlayer, 857 | SlimeChunkLayer, 858 | //testJSONlayer, 859 | //new TileLayer({ 860 | // source: new OSM() 861 | //}) 862 | ], 863 | view: myview, 864 | }); 865 | 866 | const interactionElements = document.querySelectorAll('#map-overlay > input'); 867 | 868 | const time_report = document.querySelectorAll('#map-overlay > #time_report'); 869 | for (let tr of time_report) { 870 | tr.innerHTML = world_info.update_time; 871 | } 872 | 873 | function onInteractionClick(e) { 874 | console.log(e); 875 | switch(e.target.value) { 876 | case 'showBiomes': 877 | console.log("got show biome event " + e.target.checked) 878 | biomesJSONlayer.setVisible(e.target.checked); 879 | break; 880 | 881 | case 'showEntities': 882 | console.log("got show entities event " + e.target.checked) 883 | entitiesJSONLayer.setVisible(e.target.checked); 884 | break; 885 | 886 | case 'showPassives': 887 | showPassives = e.target.checked; 888 | entitiesJSONLayer.getSource().refresh(); 889 | break; 890 | 891 | case 'showSlime': 892 | SlimeChunkLayer.setVisible(e.target.checked); 893 | break; 894 | 895 | case 'showElevations': 896 | elevationsJSONlayer.setVisible(e.target.checked); 897 | break; 898 | 899 | case 'showCaves': 900 | cavesJSONlayer.setVisible(e.target.checked); 901 | break; 902 | 903 | case 'showResources': 904 | resourceJSONLayer.setVisible(e.target.checked); 905 | break; 906 | 907 | case 'showSurfaceResources': 908 | showSurfaceResources = e.target.checked; 909 | resourceJSONLayer.getSource().refresh(); 910 | break; 911 | 912 | default: 913 | console.log('got unknown click event ' + e.target.value); 914 | break; 915 | } 916 | } 917 | 918 | for(let ble of interactionElements) { 919 | console.log(ble); 920 | ble.addEventListener('change', onInteractionClick); 921 | } 922 | 923 | 924 | //var popup = new Overlay({ 925 | // element: document.getElementById('map-overlay'), 926 | // positioning: 'center-right', 927 | // position: [100, -500], 928 | // autoPan: true, 929 | //}); 930 | //map.addOverlay(popup); 931 | } 932 | -------------------------------------------------------------------------------- /web_stuff/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openlayers2", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "parcel index.html", 9 | "build": "parcel build --public-url . index.html" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "mersenne-twister": "^1.1.0", 15 | "ol": "^6.1.1" 16 | }, 17 | "devDependencies": { 18 | "parcel-bundler": "^1.12.4" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /write_top.cpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | 17 | void 18 | write_cluster_vtk(std::vector > &clusters, 19 | std::vector > &ores, 20 | std::string filename) 21 | { 22 | 23 | std::ofstream outfile; 24 | outfile.open(filename); 25 | 26 | outfile << "# vtk DataFile Version 1.0\n"; 27 | outfile << "3D triangulation data\n"; 28 | outfile << "ASCII\n"; 29 | outfile << std::endl; 30 | outfile << "DATASET POLYDATA\n"; 31 | 32 | 33 | 34 | std::vector points; 35 | for (auto cluster : clusters) { 36 | float x=0; 37 | float y=0; 38 | float z=0; 39 | 40 | for (auto loc : cluster) { 41 | x += std::get<0>(ores[loc]); 42 | y += std::get<1>(ores[loc]); 43 | z += std::get<2>(ores[loc]); 44 | } 45 | std::stringstream ss; 46 | ss << x/cluster.size() << " " << y/cluster.size() << " " << z/cluster.size() << "\n"; 47 | points.push_back(ss.str()); 48 | } 49 | 50 | outfile << "POINTS " << points.size() << " float\n"; 51 | for (auto str : points) { 52 | outfile << str; 53 | } 54 | 55 | 56 | outfile.close(); 57 | } 58 | 59 | int 60 | main(int argc,char* argv[]) 61 | { 62 | 63 | MinecraftWorld world; 64 | 65 | //std::string dbpath = "/bubba/electronicsDS/minecraft/sampledata/2019.11.26.04.00.53/worlds/Bedrock level/db"; 66 | std::string dbpath = "/bubba/electronicsDS/minecraft/gfhtx2/gfHTXZk9AQA/db"; 67 | 68 | parse_bedrock(dbpath, world); 69 | 70 | int num_chunks = world.num_chunks(); 71 | 72 | // 16 subchunks/chunk and 16*16*16 per subchunk 73 | std::cout << "num chunks " << num_chunks << "\n"; 74 | std::cout << "num blocks in world " << num_chunks*16*16*16*16 << "\n"; 75 | 76 | int air_id = BlockType::get_block_type_id_by_name("minecraft:air ()"); 77 | int stairs_id = BlockType::get_block_type_id_by_name("minecraft:mossy_cobblestone_stairs ()"); 78 | std::map, int > top_height; 79 | 80 | for (auto scix : world.theworld) { 81 | //int chunkx = scix.first; 82 | for (auto sciy : scix.second) { 83 | //int chunky = sciy.first; 84 | for (auto sciz : sciy.second) { 85 | //int chunkz = sciz.first; 86 | auto sc = sciz.second; 87 | 88 | for (auto iter=sc->begin(); iter!=sc->end(); ++iter) { 89 | auto loc = *iter; 90 | uint8_t blocktype = loc.type; 91 | if (blocktype == stairs_id) { 92 | std::cout << "stairs at: " << loc.x << ", " << loc.y << ", " << loc.z << "\n"; 93 | } 94 | 95 | if (blocktype == air_id) { continue; } 96 | 97 | auto point = std::make_pair(loc.x, loc.z); 98 | top_height[point] = std::max(top_height[point], loc.y); 99 | } 100 | } 101 | } 102 | } 103 | 104 | std::string filename = "tops.vtk"; 105 | std::ofstream outfile; 106 | outfile.open(filename); 107 | 108 | outfile << "# vtk DataFile Version 1.0\n"; 109 | outfile << "3D triangulation data\n"; 110 | outfile << "ASCII\n"; 111 | outfile << std::endl; 112 | outfile << "DATASET POLYDATA\n"; 113 | 114 | 115 | outfile << "POINTS " << top_height.size() << " float\n"; 116 | for (auto iter : top_height) { 117 | std::stringstream ss; 118 | ss << iter.first.first << " " << iter.first.second << " " << iter.second << "\n"; 119 | outfile << ss.str(); 120 | } 121 | 122 | 123 | outfile.close(); 124 | 125 | } 126 | --------------------------------------------------------------------------------