├── fire.png ├── endportal.png ├── style.css ├── Makefile ├── rgba.h ├── world.h ├── utils.h ├── chunk.h ├── template.html ├── region.h ├── render.h ├── region.cpp ├── rgba.cpp ├── map.cpp ├── tables.cpp ├── chunk.cpp ├── utils.cpp ├── README ├── world.cpp ├── map.h ├── tables.h └── blockimages.h /fire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equalpants/pigmap/HEAD/fire.png -------------------------------------------------------------------------------- /endportal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/equalpants/pigmap/HEAD/endportal.png -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | html { height: 100% } 2 | body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; } 3 | #mcmap { height: 100% } 4 | 5 | .infoWindow { 6 | height: 100px; 7 | } 8 | 9 | .infoWindow>img { 10 | width:80px; 11 | float: left; 12 | 13 | } 14 | 15 | .infoWindow>p { 16 | text-align: center; 17 | font-family: monospace; 18 | } 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | objects = pigmap.o blockimages.o chunk.o map.o render.o region.o rgba.o tables.o utils.o world.o 2 | 3 | pigmap : $(objects) 4 | g++ $(objects) -o pigmap -l z -l png -l pthread -O3 5 | 6 | pigmap.o : pigmap.cpp blockimages.h chunk.h map.h render.h rgba.h tables.h utils.h world.h 7 | g++ -c pigmap.cpp -O3 8 | blockimages.o : blockimages.cpp blockimages.h rgba.h utils.h 9 | g++ -c blockimages.cpp -O3 10 | chunk.o : chunk.cpp chunk.h map.h region.h tables.h utils.h 11 | g++ -c chunk.cpp -O3 12 | map.o : map.cpp map.h utils.h 13 | g++ -c map.cpp -O3 14 | render.o : render.cpp blockimages.h chunk.h map.h render.h rgba.h tables.h utils.h 15 | g++ -c render.cpp -O3 16 | region.o : region.cpp map.h region.h tables.h utils.h 17 | g++ -c region.cpp -O3 18 | rgba.o : rgba.cpp rgba.h utils.h 19 | g++ -c rgba.cpp -O3 20 | tables.o : tables.cpp map.h tables.h utils.h 21 | g++ -c tables.cpp -O3 22 | utils.o : utils.cpp utils.h 23 | g++ -c utils.cpp -O3 24 | world.o : world.cpp map.h region.h tables.h world.h 25 | g++ -c world.cpp -O3 26 | 27 | clean : 28 | rm -f *.o pigmap 29 | -------------------------------------------------------------------------------- /rgba.h: -------------------------------------------------------------------------------- 1 | // Copyright 2010, 2011 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #ifndef RGBA_H 19 | #define RGBA_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | 26 | typedef uint32_t RGBAPixel; 27 | #define ALPHA(x) ((x & 0xff000000) >> 24) 28 | #define BLUE(x) ((x & 0xff0000) >> 16) 29 | #define GREEN(x) ((x & 0xff00) >> 8) 30 | #define RED(x) (x & 0xff) 31 | 32 | RGBAPixel makeRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); 33 | 34 | void setAlpha(RGBAPixel& p, int a); 35 | void setBlue(RGBAPixel& p, int b); 36 | void setGreen(RGBAPixel& p, int g); 37 | void setRed(RGBAPixel& p, int r); 38 | 39 | struct RGBAImage 40 | { 41 | std::vector data; 42 | int32_t w, h; 43 | 44 | // get pixel 45 | RGBAPixel& operator()(int32_t x, int32_t y) {return data[y*w+x];} 46 | const RGBAPixel& operator()(int32_t x, int32_t y) const {return data[y*w+x];} 47 | 48 | // resize data and initialize to 0 (clear out any existing data) 49 | void create(int32_t ww, int32_t hh); 50 | 51 | bool readPNG(const std::string& filename); 52 | bool writePNG(const std::string& filename); 53 | }; 54 | 55 | struct ImageRect 56 | { 57 | int32_t x, y, w, h; 58 | 59 | ImageRect(int32_t xx, int32_t yy, int32_t ww, int32_t hh) : x(xx), y(yy), w(ww), h(hh) {} 60 | }; 61 | 62 | //------- these are used by the inner rendering loops and must be fast 63 | 64 | // alpha-blend source pixel onto destination pixel 65 | // ...note that the alpha channel of the result is not computed the same way as the RGB channels: 66 | // instead of interpolating between ALPHA(source) and ALPHA(dest), it is the inverse product of the 67 | // inverses of ALPHA(source) and ALPHA(dest), so that when you draw a translucent pixel on top of an 68 | // opaque one, the result stays opaque 69 | void blend(RGBAPixel& dest, const RGBAPixel& source); 70 | 71 | // alpha-blend source rect onto destination rect of same size 72 | void alphablit(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, int32_t dxstart, int32_t dystart); 73 | 74 | // reduce source image into destination rect half its size 75 | // (does nothing if the ImageRect isn't exactly half the size of the source image) 76 | void reduceHalf(RGBAImage& dest, const ImageRect& drect, const RGBAImage& source); 77 | 78 | 79 | //--------- these are used only to generate block images from terrain.png and may be crappy 80 | 81 | // copy source rect into destination rect of possibly different size 82 | void resize(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, const ImageRect& drect); 83 | 84 | // darken a pixel by multiplying its RGB components by some number from 0 to 1 85 | void darken(RGBAPixel& dest, double r, double g, double b); 86 | void darken(RGBAImage& img, const ImageRect& rect, double r, double g, double b); 87 | 88 | // copy source rect into destination rect of same size 89 | void blit(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, int32_t dxstart, int32_t dystart); 90 | 91 | // flip the target rect in the X direction 92 | void flipX(RGBAImage& img, const ImageRect& rect); 93 | 94 | #endif // RGBA_H -------------------------------------------------------------------------------- /world.h: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #ifndef WORLD_H 19 | #define WORLD_H 20 | 21 | #include 22 | 23 | #include "map.h" 24 | #include "tables.h" 25 | 26 | 27 | // see whether the input world is in region format 28 | bool detectRegionFormat(const std::string& inputdir); 29 | 30 | 31 | // find all regions on disk; set them to required in the RegionTable; set all chunks they contain to 32 | // required in the ChunkTable; set all tiles touched by those chunks to required in the TileTable 33 | // returns false if the world is too big to fit in one of the tables 34 | // if mp.baseZoom is set to -1 coming in, then this function will set it to the smallest zoom 35 | // that can fit everything 36 | bool makeAllRegionsRequired(const std::string& inputdir, ChunkTable& chunktable, TileTable& tiletable, RegionTable& regiontable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount, int64_t& reqregioncount); 37 | 38 | // read a list of region filenames from a file; set the regions to required in the RegionTable; set the chunks they 39 | // contain to required in the ChunkTable; set all tiles touched by those chunks to required in the TileTable 40 | // the region filenames can be either old-style (".mcr") or Anvil (".mca"), but only the coordinates from the filename 41 | // will be considered--when rendering is actually performed, Anvil regions will be preferred to old-style regions 42 | // even if ".mcr" was used in this regionlist 43 | // returns 0 on success, -1 if baseZoom is too small, -2 for other errors (can't read regionlist, world too big 44 | // for our internal data structures, etc.) 45 | int readRegionlist(const std::string& regionlist, const std::string& inputdir, ChunkTable& chunktable, TileTable& tiletable, RegionTable& regiontable, const MapParams& mp, int64_t& reqrchunkcount, int64_t& reqtilecount, int64_t& reqregioncount); 46 | 47 | 48 | // find all chunks on disk, set them to required in the ChunkTable, and set all tiles they 49 | // touch to required in the TileTable 50 | // returns false if the world is too big to fit in one of the tables 51 | // if mp.baseZoom is set to -1 coming in, then this function will set it to the smallest zoom 52 | // that can fit everything 53 | bool makeAllChunksRequired(const std::string& inputdir, ChunkTable& chunktable, TileTable& tiletable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount); 54 | 55 | // read a list of chunk filenames from a file and set the chunks to required in the ChunkTable, and set 56 | // any tiles they touch to required in the TileTable 57 | // returns 0 on success, -1 if baseZoom is too small, -2 for other errors (can't read chunklist, world too big 58 | // for our internal data structures, etc.) 59 | int readChunklist(const std::string& chunklist, ChunkTable& chunktable, TileTable& tiletable, const MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount); 60 | 61 | 62 | 63 | 64 | // build a test world by making approximately size chunks required 65 | // if mp.baseZoom is set to -1 coming in, it will be set to the smallest zoom that can fit everything 66 | void makeTestWorld(int size, ChunkTable& chunktable, TileTable& tiletable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount); 67 | 68 | // get the filepaths of all chunks on disk (used only for testing) 69 | void findAllChunks(const std::string& inputdir, std::vector& chunkpaths); 70 | 71 | 72 | #endif // WORLD_H -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | // Copyright 2010-2012 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #ifndef UTILS_H 19 | #define UTILS_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | 26 | // ensure that a directory exists (create any missing directories on path) 27 | void makePath(const std::string& path); 28 | 29 | void renameFile(const std::string& oldpath, const std::string& newpath); 30 | void copyFile(const std::string& oldpath, const std::string& newpath); 31 | 32 | // read a text file and append each of its non-empty lines to a vector 33 | bool readLines(const std::string& filename, std::vector& lines); 34 | 35 | // list names of entries in a directory, not including "." and ".." 36 | // ...returns relative paths beginning with dirpath; appends results to vector 37 | void listEntries(const std::string& dirpath, std::vector& entries); 38 | 39 | bool dirExists(const std::string& dirpath); 40 | 41 | // -read a gzipped file into a vector, overwriting its contents, and expanding it if necessary 42 | // -return 0 on success, -1 for nonexistent file, -2 for other errors 43 | int readGzFile(const std::string& filename, std::vector& data); 44 | 45 | // extract gzip- or zlib-compressed data into a vector, overwriting its contents, and 46 | // expanding it if necessary 47 | // (inbuf is not const only because zlib won't take const pointers for input) 48 | bool readGzOrZlib(uint8_t* inbuf, size_t size, std::vector& data); 49 | 50 | 51 | #define USE_MALLINFO 0 52 | 53 | uint64_t getHeapUsage(); 54 | 55 | 56 | // convert a big-endian int into whatever the current platform endianness is 57 | uint32_t fromBigEndian(uint32_t i); 58 | uint16_t fromBigEndian(uint16_t i); 59 | 60 | // detect whether the platform is big-endian 61 | bool isBigEndian(); 62 | 63 | // switch endianness of an int 64 | void swapEndian(uint32_t& i); 65 | 66 | 67 | // floored division; real value of a/b is floored instead of truncated toward 0 68 | int64_t floordiv(int64_t a, int64_t b); 69 | // ...same thing for ceiling 70 | int64_t ceildiv(int64_t a, int64_t b); 71 | 72 | // positive remainder mod 64, for chunk subdirectories 73 | int64_t mod64pos(int64_t a); 74 | 75 | // given i in [0,destrange), find j in [0,srcrange) 76 | int64_t interpolate(int64_t i, int64_t destrange, int64_t srcrange); 77 | 78 | 79 | // take a row-major index into a SIZExSIZE array and convert it to Z-order 80 | uint32_t toZOrder(uint32_t i, const uint32_t SIZE); 81 | // ...and vice versa 82 | uint32_t fromZOrder(uint32_t i, const uint32_t SIZE); 83 | 84 | 85 | bool fromBase36(const std::string& s, std::string::size_type pos, std::string::size_type n, int64_t& result); 86 | int64_t fromBase36(const std::string& s); 87 | std::string toBase36(int64_t i); 88 | 89 | std::string tostring(int i); 90 | std::string tostring(int64_t i); 91 | bool fromstring(const std::string& s, int64_t& result); 92 | bool fromstring(const std::string& s, int& result); 93 | 94 | 95 | // replace all occurrences of oldstr in text with newstr; return false if none found 96 | bool replace(std::string& text, const std::string& oldstr, const std::string& newstr); 97 | 98 | std::vector tokenize(const std::string& instr, char separator); 99 | 100 | 101 | // find an assignment of costs to threads that attempts to minimize the difference 102 | // between the min and max total thread costs; return the difference by itself, and 103 | // also as a fraction of the max thread cost 104 | std::pair schedule(const std::vector& costs, std::vector& assignments, int threads); 105 | 106 | 107 | class nocopy 108 | { 109 | protected: 110 | nocopy() {} 111 | ~nocopy() {} 112 | private: 113 | nocopy(const nocopy& n); 114 | const nocopy& operator=(const nocopy& n); 115 | }; 116 | 117 | 118 | template struct arrayDeleter 119 | { 120 | T *array; 121 | arrayDeleter(T *a) : array(a) {} 122 | ~arrayDeleter() {delete[] array;} 123 | }; 124 | 125 | 126 | template struct stackPusher 127 | { 128 | std::vector& vec; 129 | stackPusher(std::vector& v, const T& item) : vec(v) {vec.push_back(item);} 130 | ~stackPusher() {vec.pop_back();} 131 | }; 132 | 133 | 134 | // fast version for dividing by 16 (important for BlockIdx::getChunkIdx, which is called very very frequently) 135 | inline int64_t floordiv16(int64_t a) 136 | { 137 | // right-shifting a negative is undefined, so just do the division--the compiler will probably know 138 | // whether it can use a shift anyway 139 | if (a < 0) 140 | return (a - 15) / 16; 141 | return a >> 4; 142 | } 143 | 144 | 145 | #endif // UTILS_H -------------------------------------------------------------------------------- /chunk.h: -------------------------------------------------------------------------------- 1 | // Copyright 2010-2012 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #ifndef CHUNK_H 19 | #define CHUNK_H 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "map.h" 27 | #include "tables.h" 28 | #include "region.h" 29 | 30 | 31 | 32 | // offset into a chunk of a block 33 | struct BlockOffset 34 | { 35 | int64_t x, z, y; 36 | BlockOffset(const BlockIdx& bi) 37 | { 38 | ChunkIdx ci = bi.getChunkIdx(); 39 | x = bi.x - ci.x*16; 40 | z = bi.z - ci.z*16; 41 | y = bi.y; 42 | } 43 | }; 44 | 45 | struct ChunkData 46 | { 47 | uint8_t blockIDs[65536]; // one byte per block (only half of this space used for old-style chunks) 48 | uint8_t blockAdd[32768]; // only in Anvil--extra bits for block ID (4 bits per block) 49 | uint8_t blockData[32768]; // 4 bits per block (only half of this space used for old-style chunks) 50 | bool anvil; // whether this data came from an Anvil chunk or an old-style one 51 | 52 | // these guys assume that the BlockIdx actually points to this chunk 53 | // (so they only look at the lower bits) 54 | uint16_t id(const BlockOffset& bo) const 55 | { 56 | if (!anvil) 57 | return (bo.y > 127) ? 0 : blockIDs[(bo.x * 16 + bo.z) * 128 + bo.y]; 58 | int i = (bo.y * 16 + bo.z) * 16 + bo.x; 59 | if ((i % 2) == 0) 60 | return ((blockAdd[i/2] & 0xf) << 8) | blockIDs[i]; 61 | return ((blockAdd[i/2] & 0xf0) << 4) | blockIDs[i]; 62 | } 63 | uint8_t data(const BlockOffset& bo) const 64 | { 65 | int i; 66 | if (!anvil) 67 | { 68 | if (bo.y > 127) 69 | return 0; 70 | i = (bo.x * 16 + bo.z) * 128 + bo.y; 71 | } 72 | else 73 | i = (bo.y * 16 + bo.z) * 16 + bo.x; 74 | if ((i % 2) == 0) 75 | return blockData[i/2] & 0xf; 76 | return (blockData[i/2] & 0xf0) >> 4; 77 | } 78 | 79 | bool loadFromOldFile(const std::vector& filebuf); 80 | bool loadFromAnvilFile(const std::vector& filebuf); 81 | }; 82 | 83 | 84 | 85 | struct ChunkCacheStats 86 | { 87 | int64_t hits, misses; 88 | // types of misses: 89 | int64_t read; // successfully read from disk 90 | int64_t skipped; // assumed not to exist because not required in a full render 91 | int64_t missing; // non-required chunk not present on disk 92 | int64_t reqmissing; // required chunk not present on disk 93 | int64_t corrupt; // found on disk, but failed to read 94 | 95 | // when in region mode, the miss stats have slightly different meanings: 96 | // read: chunk was successfully read from region cache (which may or may not have triggered an 97 | // actual read of the region file from disk) 98 | // missing: not present in the region file, or region file missing/corrupt 99 | // corrupt: region file itself is okay, but chunk data within it is corrupt 100 | // skipped/reqmissing: unused 101 | 102 | ChunkCacheStats() : hits(0), misses(0), read(0), skipped(0), missing(0), reqmissing(0), corrupt(0) {} 103 | 104 | ChunkCacheStats& operator+=(const ChunkCacheStats& ccs); 105 | }; 106 | 107 | struct ChunkCacheEntry 108 | { 109 | PosChunkIdx ci; // or [-1,-1] if this entry is empty 110 | ChunkData data; 111 | 112 | ChunkCacheEntry() : ci(-1,-1) {} 113 | }; 114 | 115 | #define CACHEBITSX 5 116 | #define CACHEBITSZ 5 117 | #define CACHEXSIZE (1 << CACHEBITSX) 118 | #define CACHEZSIZE (1 << CACHEBITSZ) 119 | #define CACHESIZE (CACHEXSIZE * CACHEZSIZE) 120 | #define CACHEXMASK (CACHEXSIZE - 1) 121 | #define CACHEZMASK (CACHEZSIZE - 1) 122 | 123 | struct ChunkCache : private nocopy 124 | { 125 | ChunkCacheEntry entries[CACHESIZE]; 126 | ChunkData blankdata; // for use with missing chunks 127 | 128 | ChunkTable& chunktable; 129 | RegionTable& regiontable; 130 | ChunkCacheStats& stats; 131 | RegionCache& regioncache; 132 | std::string inputpath; 133 | bool fullrender; 134 | bool regionformat; 135 | std::vector readbuf; // buffer for decompressing into when reading 136 | ChunkCache(ChunkTable& ctable, RegionTable& rtable, RegionCache& rcache, const std::string& inpath, bool fullr, bool regform, ChunkCacheStats& st) 137 | : chunktable(ctable), regiontable(rtable), regioncache(rcache), inputpath(inpath), fullrender(fullr), regionformat(regform), stats(st) 138 | { 139 | memset(blankdata.blockIDs, 0, 65536); 140 | memset(blankdata.blockData, 0, 32768); 141 | memset(blankdata.blockAdd, 0, 32768); 142 | blankdata.anvil = true; 143 | readbuf.reserve(262144); 144 | } 145 | 146 | // look up a chunk and return a pointer to its data 147 | // ...for missing/corrupt chunks, return a pointer to some blank data 148 | ChunkData* getData(const PosChunkIdx& ci); 149 | 150 | static int getEntryNum(const PosChunkIdx& ci) {return (ci.x & CACHEXMASK) * CACHEZSIZE + (ci.z & CACHEZMASK);} 151 | 152 | void readChunkFile(const PosChunkIdx& ci); 153 | void readFromRegionCache(const PosChunkIdx& ci); 154 | void parseReadBuf(const PosChunkIdx& ci, bool anvil); 155 | }; 156 | 157 | 158 | 159 | #endif // CHUNK_H -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 179 | 180 | 181 |
182 | 183 | 184 | -------------------------------------------------------------------------------- /region.h: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #ifndef REGION_H 19 | #define REGION_H 20 | 21 | #include 22 | 23 | #include "map.h" 24 | #include "tables.h" 25 | 26 | 27 | // offset into a region of a chunk 28 | struct ChunkOffset 29 | { 30 | int64_t x, z; 31 | ChunkOffset(const ChunkIdx& ci) 32 | { 33 | RegionIdx ri = ci.getRegionIdx(); 34 | x = ci.x - ri.x*32; 35 | z = ci.z - ri.z*32; 36 | } 37 | }; 38 | 39 | struct string84 40 | { 41 | std::string s; 42 | explicit string84(const std::string& sss) : s(sss) {} 43 | }; 44 | 45 | struct RegionFileReader 46 | { 47 | // region file is broken into 4096-byte sectors; first sector is the header that holds the 48 | // chunk offsets, remaining sectors are chunk data 49 | 50 | // chunk offsets are big-endian; lower (that is, 4th) byte is size in sectors, upper 3 bytes are 51 | // sector offset *in region file* (one more than the offset into chunkdata) 52 | // offsets are indexed by Z*32 + X 53 | std::vector offsets; 54 | // each set of chunk data contains: 55 | // -a 4-byte big-endian data length (not including the length field itself) 56 | // -a single-byte version: 1 for gzip, 2 for zlib (this byte *is* included in the length) 57 | // -length - 1 bytes of actual compressed data 58 | std::vector chunkdata; 59 | // whether this data was read from an Anvil region file or an old-style one 60 | bool anvil; 61 | 62 | RegionFileReader() 63 | { 64 | offsets.resize(32 * 32); 65 | chunkdata.reserve(8388608); 66 | } 67 | 68 | void swap(RegionFileReader& rfr) 69 | { 70 | offsets.swap(rfr.offsets); 71 | chunkdata.swap(rfr.chunkdata); 72 | std::swap(anvil, rfr.anvil); 73 | } 74 | 75 | // extract values from the offsets 76 | static int getIdx(const ChunkOffset& co) {return co.z*32 + co.x;} 77 | uint32_t getSizeSectors(int idx) const {return fromBigEndian(offsets[idx]) & 0xff;} 78 | uint32_t getSectorOffset(int idx) const {return fromBigEndian(offsets[idx]) >> 8;} 79 | bool containsChunk(const ChunkOffset& co) {return offsets[getIdx(co)] != 0;} 80 | 81 | 82 | // attempt to read a region file; return 0 for success, -1 for file not found, -2 for 83 | // other errors 84 | // looks for an Anvil region file (.mca) first, then an old-style one (.mcr) 85 | int loadFromFile(const RegionIdx& ri, const std::string& inputpath); 86 | 87 | // attempt to decompress a chunk into a buffer; return 0 for success, -1 for missing chunk, 88 | // -2 for other errors 89 | // (this is not const only because zlib won't take const pointers for input) 90 | int decompressChunk(const ChunkOffset& co, std::vector& buf); 91 | 92 | // attempt to read only the header (i.e. the chunk offsets) from a region file; return 0 93 | // for success, -1 for file not found, -2 for other errors 94 | // looks for an Anvil region file (.mca) first, then an old-style one (.mcr) 95 | int loadHeaderOnly(const RegionIdx& ri, const std::string& inputpath); 96 | 97 | // open a region file, load only its header, and return a list of chunks it contains (i.e. the ones that 98 | // actually currently exist) 99 | // ...returns 0 for success, -1 for file not found, -2 for other errors 100 | int getContainedChunks(const RegionIdx& ri, const string84& inputpath, std::vector& chunks); 101 | }; 102 | 103 | // iterates over the chunks in a region 104 | struct RegionChunkIterator 105 | { 106 | bool end; // true once we've reached the end 107 | ChunkIdx current; // if end == false, holds the current chunk 108 | 109 | ChunkIdx basechunk; 110 | 111 | // constructor initializes to to first chunk in provided region 112 | RegionChunkIterator(const RegionIdx& ri); 113 | 114 | // move to the next chunk, or to the end 115 | void advance(); 116 | }; 117 | 118 | 119 | struct RegionCacheStats 120 | { 121 | int64_t hits, misses; 122 | // types of misses: 123 | int64_t read; // successfully read from disk 124 | int64_t skipped; // assumed not to exist because not required in a full render 125 | int64_t missing; // non-required region not present on disk 126 | int64_t reqmissing; // required region not present on disk 127 | int64_t corrupt; // found on disk, but failed to read 128 | 129 | RegionCacheStats() : hits(0), misses(0), read(0), skipped(0), missing(0), reqmissing(0), corrupt(0) {} 130 | 131 | RegionCacheStats& operator+=(const RegionCacheStats& rs); 132 | }; 133 | 134 | struct RegionCacheEntry 135 | { 136 | PosRegionIdx ri; // or [-1, -1] if this entry is empty 137 | RegionFileReader regionfile; 138 | 139 | RegionCacheEntry() : ri(-1,-1) {} 140 | }; 141 | 142 | #define RCACHEBITSX 1 143 | #define RCACHEBITSZ 1 144 | #define RCACHEXSIZE (1 << RCACHEBITSX) 145 | #define RCACHEZSIZE (1 << RCACHEBITSZ) 146 | #define RCACHESIZE (RCACHEXSIZE * RCACHEZSIZE) 147 | #define RCACHEXMASK (RCACHEXSIZE - 1) 148 | #define RCACHEZMASK (RCACHEZSIZE - 1) 149 | 150 | struct RegionCache : private nocopy 151 | { 152 | RegionCacheEntry entries[RCACHESIZE]; 153 | 154 | ChunkTable& chunktable; 155 | RegionTable& regiontable; 156 | RegionCacheStats& stats; 157 | std::string inputpath; 158 | bool fullrender; 159 | // readbuf is an extra less-important cache entry--when a new region is read, it's this entry which will be trashed 160 | // and its storage used for the read (which might fail), but if the read succeeds, the new region is swapped 161 | // into its proper place in the cache, and the previous tenant there moves here 162 | RegionCacheEntry readbuf; 163 | RegionCache(ChunkTable& ctable, RegionTable& rtable, const std::string& inpath, bool fullr, RegionCacheStats& st) 164 | : chunktable(ctable), regiontable(rtable), inputpath(inpath), fullrender(fullr), stats(st) 165 | { 166 | } 167 | 168 | // attempt to decompress a chunk into a buffer; return 0 for success, -1 for missing chunk, 169 | // -2 for other errors 170 | // (this is not const only because zlib won't take const pointers for input) 171 | int getDecompressedChunk(const PosChunkIdx& ci, std::vector& buf, bool& anvil); 172 | 173 | static int getEntryNum(const PosRegionIdx& ri) {return (ri.x & RCACHEXMASK) * RCACHEZSIZE + (ri.z & RCACHEZMASK);} 174 | 175 | void readRegionFile(const PosRegionIdx& ri); 176 | }; 177 | 178 | 179 | #endif // REGION_H -------------------------------------------------------------------------------- /render.h: -------------------------------------------------------------------------------- 1 | // Copyright 2010, 2011 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #ifndef RENDER_H 19 | #define RENDER_H 20 | 21 | #include 22 | #include 23 | 24 | #include "map.h" 25 | #include "tables.h" 26 | #include "chunk.h" 27 | #include "blockimages.h" 28 | #include "rgba.h" 29 | 30 | 31 | 32 | struct RenderStats 33 | { 34 | int64_t reqchunkcount, reqregioncount, reqtilecount; // number of required chunks/regions and base tiles 35 | uint64_t heapusage; // estimated peak heap memory usage (if available) 36 | ChunkCacheStats chunkcache; 37 | RegionCacheStats regioncache; 38 | 39 | RenderStats() : reqchunkcount(0), reqregioncount(0), reqtilecount(0), heapusage(0) {} 40 | }; 41 | 42 | 43 | struct SceneGraph; 44 | struct TileCache; 45 | struct ThreadOutputCache; 46 | 47 | struct RenderJob : private nocopy 48 | { 49 | bool fullrender; // whether we're doing the entire world, as opposed to an incremental update 50 | bool regionformat; // whether the world is in region format (chunk format assumed if not) 51 | MapParams mp; 52 | std::string inputpath, outputpath; 53 | BlockImages blockimages; 54 | std::auto_ptr chunktable; 55 | std::auto_ptr chunkcache; 56 | std::auto_ptr regiontable; 57 | std::auto_ptr regioncache; 58 | std::auto_ptr tiletable; 59 | std::auto_ptr tilecache; 60 | std::auto_ptr scenegraph; // reuse this for each tile to avoid reallocation 61 | RenderStats stats; 62 | 63 | // don't actually draw anything or read chunks; just iterate through the data structures 64 | // ...scenegraph, chunkcache, and regioncache are not required if in test mode 65 | bool testmode; 66 | }; 67 | 68 | // render a base tile into an RGBAImage, and also write it to disk 69 | // ...do nothing and return false if the tile is not required or is out of range 70 | bool renderTile(const TileIdx& ti, RenderJob& rj, RGBAImage& tile); 71 | 72 | // recursively render all the required tiles that a zoom tile depends on, and then the tile itself; 73 | // stores the result into the supplied RGBAImage, and also writes it to disk 74 | // do nothing and return false if the tile is not required 75 | bool renderZoomTile(const ZoomTileIdx& zti, RenderJob& rj, RGBAImage& tile); 76 | 77 | // for second phase of multithreaded operation: recursively render all the required tiles that a zoom tile 78 | // depends on, but stop recursing at the ThreadOutputCache level rather than the base tile level 79 | bool renderZoomTile(const ZoomTileIdx& zti, RenderJob& rj, RGBAImage& tile, const ThreadOutputCache& tocache); 80 | 81 | 82 | 83 | // as we render tiles recursively, we need to be able to hold 4 intermediate results at each zoom level; 84 | // this holds the space for those images, so we don't reallocate all the time 85 | struct TileCache 86 | { 87 | struct ZoomLevel 88 | { 89 | bool used[4]; // which of the images actually have data 90 | RGBAImage tiles[4]; // actual image data for the four tiles 91 | }; 92 | 93 | std::vector levels; // indexed by baseZoom - zoom 94 | 95 | TileCache(const MapParams& mp) : levels(mp.baseZoom) 96 | { 97 | // reserve memory 98 | for (int i = 0; i < mp.baseZoom; i++) 99 | for (int j = 0; j < 4; j++) 100 | levels[i].tiles[j].create(mp.tileSize(), mp.tileSize()); 101 | } 102 | }; 103 | 104 | 105 | // when rendering with multiple threads, the individual threads only go up to a certain zoom level, then 106 | // the main thread does the last few levels on its own; the worker threads store their results in this 107 | struct ThreadOutputCache 108 | { 109 | int zoom; // which zoom level the threads are working at 110 | 111 | std::vector images; // use getIndex() to get index into this from zoom tile 112 | std::vector used; // which images actually have data 113 | 114 | int getIndex(const ZoomTileIdx& zti) const; // get index into images, or -1 if zoom is wrong 115 | 116 | ThreadOutputCache(int z) : zoom(z), images((1 << zoom) * (1 << zoom)), used((1 << zoom) * (1 << zoom), false) {} 117 | }; 118 | 119 | 120 | 121 | 122 | // the blocks in a tile can be partitioned by their center pixels into pseudocolumns--sets of blocks that cover 123 | // exactly the same pixels (each block covers the block immediately SED of it, and so on) 124 | // also, each block can partially occlude blocks in 6 neighboring pseudocolumns: E, SE, S, W, NW, N (that is, 125 | // the pseudocolumns that contain the block's immediate neighbors to the E, SE, etc.) 126 | // ...so we can build a DAG representing the blocks in the tile: each block has up to 7 pointers, each one going to 127 | // the topmost occluded block in a pseudocolumn 128 | // a block can be drawn when all its descendents have been drawn 129 | 130 | struct SceneGraphNode 131 | { 132 | int32_t xstart, ystart; // top-left corner of block bounding box in tile image coords 133 | int bimgoffset; // offset into blockimages 134 | // whether to darken various edges to indicate drop-off 135 | bool darkenEU, darkenSU, darkenND, darkenWD; 136 | bool drawn; 137 | BlockIdx bi; 138 | // first child is same pseudocolumn, then N, E, SE, S, W, NW; values are indices into 139 | // the SceneGraph's nodes vector, or -1 for "null" 140 | int children[7]; 141 | 142 | SceneGraphNode(int32_t x, int32_t y, const BlockIdx& bidx, int offset) 143 | : xstart(x), ystart(y), bimgoffset(offset), darkenEU(false), darkenSU(false), darkenND(false), darkenWD(false), 144 | drawn(false), bi(bidx) {std::fill(children, children + 7, -1);} 145 | }; 146 | 147 | struct SceneGraph 148 | { 149 | // all nodes from all pseudocolumns go in here, in sequence (ordered by pseudocolumn, and within 150 | // pseudocolumns by height) 151 | std::vector nodes; 152 | // offset into nodes vector of each pseudocolumn (-1 for pseudocolumns with no nodes) 153 | std::vector pcols; 154 | 155 | void clear() {nodes.clear(); pcols.clear();} 156 | 157 | int getTopNode(int pcol) {return pcols[pcol];} 158 | 159 | // scratch space for use while traversing the DAG 160 | std::vector nodestack; 161 | 162 | SceneGraph() {nodes.reserve(2048);} 163 | }; 164 | 165 | 166 | 167 | 168 | // iterate over the hexagonal block-center grid pixels whose blocks touch a tile 169 | struct TileBlockIterator 170 | { 171 | bool end; // true when there are no more points 172 | 173 | // these guys are valid when end == false: 174 | Pixel current; // the current grid point 175 | int pos; // the position of this point within this tile's sequence of points 176 | int nextN, nextE, nextSE; // the sequence positions of the neighboring points; -1 if the neighbor isn't in the tile 177 | 178 | const MapParams& mparams; 179 | TileIdx tile; 180 | // the tile's bounding box, expanded by half a block's bounding box, so that any block centered on a point 181 | // within this box will hit the tile 182 | BBox expandedBBox; 183 | int lastTop, lastBottom; // positions of the most recent column top, bottom we've encountered (or -1) 184 | 185 | // constructor initializes to the upper-left grid point 186 | TileBlockIterator(const TileIdx& ti, const MapParams& mp); 187 | 188 | // movement goes down the columns, then rightward to the next column 189 | void advance(); 190 | }; 191 | 192 | // iterate through the blocks that project to the same place, from top to bottom 193 | struct PseudocolumnIterator 194 | { 195 | bool end; // true when we've run out of blocks 196 | BlockIdx current; // when end == false, holds current block 197 | 198 | const MapParams& mparams; 199 | 200 | // constructor initializes to topmost block 201 | PseudocolumnIterator(const Pixel& center, const MapParams& mp); 202 | 203 | // move to the next block (which is one step SED), or the end 204 | void advance(); 205 | }; 206 | 207 | 208 | 209 | 210 | void testTileIterator(); 211 | void testPColIterator(); 212 | 213 | 214 | #endif // RENDER_H -------------------------------------------------------------------------------- /region.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "region.h" 23 | #include "utils.h" 24 | 25 | using namespace std; 26 | 27 | 28 | 29 | struct fcloser 30 | { 31 | FILE *f; 32 | fcloser(FILE *ff) : f(ff) {} 33 | ~fcloser() {fclose(f);} 34 | }; 35 | 36 | FILE* openRegionFile(const RegionIdx& ri, const string& inputpath, bool& anvil) 37 | { 38 | string filename = inputpath + "/region/" + ri.toAnvilFileName(); 39 | FILE *anvilfile = fopen(filename.c_str(), "rb"); 40 | if (anvilfile != NULL) 41 | { 42 | anvil = true; 43 | return anvilfile; 44 | } 45 | filename = inputpath + "/region/" + ri.toOldFileName(); 46 | anvil = false; 47 | return fopen(filename.c_str(), "rb"); 48 | } 49 | 50 | int RegionFileReader::loadFromFile(const RegionIdx& ri, const string& inputpath) 51 | { 52 | // open file 53 | FILE *f = openRegionFile(ri, inputpath, anvil); 54 | if (f == NULL) 55 | return -1; 56 | fcloser fc(f); 57 | 58 | // get file length 59 | fseek(f, 0, SEEK_END); 60 | size_t length = (size_t)ftell(f); 61 | fseek(f, 0, SEEK_SET); 62 | if (length < 4096) 63 | return -2; 64 | 65 | // read the header 66 | size_t count = fread(&(offsets[0]), 4096, 1, f); 67 | if (count < 1) 68 | return -2; 69 | 70 | // read the rest of the file 71 | chunkdata.resize(length - 4096); 72 | if (length > 4096) 73 | { 74 | count = fread(&(chunkdata[0]), length - 4096, 1, f); 75 | if (count < 1) 76 | return -2; 77 | } 78 | return 0; 79 | } 80 | 81 | int RegionFileReader::loadHeaderOnly(const RegionIdx& ri, const string& inputpath) 82 | { 83 | // open file 84 | FILE *f = openRegionFile(ri, inputpath, anvil); 85 | if (f == NULL) 86 | return -1; 87 | fcloser fc(f); 88 | 89 | // read the header 90 | size_t count = fread(&(offsets[0]), 4096, 1, f); 91 | if (count < 1) 92 | return -2; 93 | 94 | return 0; 95 | } 96 | 97 | int RegionFileReader::decompressChunk(const ChunkOffset& co, vector& buf) 98 | { 99 | // see if chunk is present 100 | if (!containsChunk(co)) 101 | return -1; 102 | 103 | // attempt to decompress chunk data into buffer 104 | int idx = getIdx(co); 105 | uint32_t sector = getSectorOffset(idx); 106 | if ((sector - 1) * 4096 >= chunkdata.size()) 107 | return -2; 108 | uint8_t *chunkstart = &(chunkdata[(sector - 1) * 4096]); 109 | uint32_t datasize = fromBigEndian(*((uint32_t*)chunkstart)); 110 | bool okay = readGzOrZlib(chunkstart + 5, datasize - 1, buf); 111 | if (!okay) 112 | return -2; 113 | return 0; 114 | } 115 | 116 | int RegionFileReader::getContainedChunks(const RegionIdx& ri, const string84& inputpath, vector& chunks) 117 | { 118 | chunks.clear(); 119 | int result = loadHeaderOnly(ri, inputpath.s); 120 | if (0 != result) 121 | return result; 122 | for (RegionChunkIterator it(ri); !it.end; it.advance()) 123 | if (containsChunk(it.current)) 124 | chunks.push_back(it.current); 125 | return 0; 126 | } 127 | 128 | 129 | 130 | 131 | 132 | RegionChunkIterator::RegionChunkIterator(const RegionIdx& ri) 133 | : end(false), current(ri.baseChunk()), basechunk(ri.baseChunk()) 134 | { 135 | } 136 | 137 | void RegionChunkIterator::advance() 138 | { 139 | current.x++; 140 | if (current.x >= basechunk.x + 32) 141 | { 142 | current.x = basechunk.x; 143 | current.z++; 144 | } 145 | if (current.z >= basechunk.z + 32) 146 | end = true; 147 | } 148 | 149 | 150 | 151 | RegionCacheStats& RegionCacheStats::operator+=(const RegionCacheStats& rcs) 152 | { 153 | hits += rcs.hits; 154 | misses += rcs.misses; 155 | read += rcs.read; 156 | skipped += rcs.skipped; 157 | missing += rcs.missing; 158 | reqmissing += rcs.reqmissing; 159 | corrupt += rcs.corrupt; 160 | return *this; 161 | } 162 | 163 | 164 | int RegionCache::getDecompressedChunk(const PosChunkIdx& ci, vector& buf, bool& anvil) 165 | { 166 | PosRegionIdx ri = ci.toChunkIdx().getRegionIdx(); 167 | int e = getEntryNum(ri); 168 | int state = regiontable.getDiskState(ri); 169 | 170 | if (state == RegionSet::REGION_UNKNOWN) 171 | stats.misses++; 172 | else 173 | stats.hits++; 174 | 175 | // if we already tried and failed to read this region, don't try again 176 | if (state == RegionSet::REGION_CORRUPTED || state == RegionSet::REGION_MISSING) 177 | { 178 | // actually, it shouldn't even be possible to get here, since the disk state 179 | // flags for all chunks in the region should have been set the first time we failed 180 | cerr << "cache invariant failure! tried to read already-failed region" << endl; 181 | return -1; 182 | } 183 | 184 | // if the region is in the cache, try to extract the chunk from it 185 | if (state == RegionSet::REGION_CACHED) 186 | { 187 | // try the "real" cache entry, then the extra readbuf 188 | if (entries[e].ri == ri) 189 | { 190 | anvil = entries[e].regionfile.anvil; 191 | return entries[e].regionfile.decompressChunk(ci.toChunkIdx(), buf); 192 | } 193 | else if (readbuf.ri == ri) 194 | { 195 | anvil = readbuf.regionfile.anvil; 196 | return readbuf.regionfile.decompressChunk(ci.toChunkIdx(), buf); 197 | } 198 | // if it wasn't in one of those two places, it shouldn't have been marked as cached 199 | cerr << "grievous region cache failure!" << endl; 200 | cerr << "[" << ri.x << "," << ri.z << "] [" << entries[e].ri.x << "," << entries[e].ri.z << "] [" << readbuf.ri.x << "," << readbuf.ri.z << "]" << endl; 201 | exit(-1); 202 | } 203 | 204 | // if this is a full render and the region is not required, we already know it doesn't exist 205 | bool req = regiontable.isRequired(ri); 206 | if (fullrender && !req) 207 | { 208 | stats.skipped++; 209 | regiontable.setDiskState(ri, RegionSet::REGION_MISSING); 210 | for (RegionChunkIterator it(ri.toRegionIdx()); !it.end; it.advance()) 211 | chunktable.setDiskState(it.current, ChunkSet::CHUNK_MISSING); 212 | return -1; 213 | } 214 | 215 | // okay, we actually have to read the region from disk, if it's there 216 | readRegionFile(ri); 217 | 218 | // check whether the read succeeded; try to extract the chunk if so 219 | state = regiontable.getDiskState(ri); 220 | if (state == RegionSet::REGION_CORRUPTED) 221 | { 222 | stats.corrupt++; 223 | return -1; 224 | } 225 | if (state == RegionSet::REGION_MISSING) 226 | { 227 | if (req) 228 | stats.reqmissing++; 229 | else 230 | stats.missing++; 231 | return -1; 232 | } 233 | // since we've actually just done a read, the region should now be in a real cache entry, not the readbuf 234 | if (state != RegionSet::REGION_CACHED || entries[e].ri != ri) 235 | { 236 | cerr << "grievous region cache failure!" << endl; 237 | cerr << "[" << ri.x << "," << ri.z << "] [" << entries[e].ri.x << "," << entries[e].ri.z << "]" << endl; 238 | exit(-1); 239 | } 240 | stats.read++; 241 | anvil = entries[e].regionfile.anvil; 242 | return entries[e].regionfile.decompressChunk(ci.toChunkIdx(), buf); 243 | } 244 | 245 | void RegionCache::readRegionFile(const PosRegionIdx& ri) 246 | { 247 | // forget the data in the readbuf 248 | if (readbuf.ri.valid()) 249 | regiontable.setDiskState(readbuf.ri, RegionSet::REGION_UNKNOWN); 250 | readbuf.ri = PosRegionIdx(-1,-1); 251 | 252 | // read the region file from disk, if it's there 253 | int result = readbuf.regionfile.loadFromFile(ri.toRegionIdx(), inputpath); 254 | if (result == -1) 255 | { 256 | regiontable.setDiskState(ri, RegionSet::REGION_MISSING); 257 | for (RegionChunkIterator it(ri.toRegionIdx()); !it.end; it.advance()) 258 | chunktable.setDiskState(it.current, ChunkSet::CHUNK_MISSING); 259 | return; 260 | } 261 | if (result == -2) 262 | { 263 | regiontable.setDiskState(ri, RegionSet::REGION_CORRUPTED); 264 | for (RegionChunkIterator it(ri.toRegionIdx()); !it.end; it.advance()) 265 | chunktable.setDiskState(it.current, ChunkSet::CHUNK_MISSING); 266 | return; 267 | } 268 | 269 | // read was successful; evict current tenant of chunk's cache slot (swap it into the readbuf) 270 | int e = getEntryNum(ri); 271 | entries[e].regionfile.swap(readbuf.regionfile); 272 | swap(entries[e].ri, readbuf.ri); 273 | // mark the entry as vaild and the region as cached 274 | entries[e].ri = ri; 275 | regiontable.setDiskState(ri, RegionSet::REGION_CACHED); 276 | } 277 | -------------------------------------------------------------------------------- /rgba.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2010, 2011 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #include 19 | #include 20 | 21 | #include "rgba.h" 22 | #include "utils.h" 23 | 24 | using namespace std; 25 | 26 | # ifndef UINT64_C 27 | # if __WORDSIZE == 64 28 | # define UINT64_C(c) c ## UL 29 | # else 30 | # define UINT64_C(c) c ## ULL 31 | # endif 32 | # endif 33 | 34 | 35 | RGBAPixel makeRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) 36 | { 37 | return (a << 24) | (b << 16) | (g << 8) | r; 38 | } 39 | 40 | void setAlpha(RGBAPixel& p, int a) 41 | { 42 | p &= 0xffffff; 43 | p |= (a & 0xff) << 24; 44 | } 45 | 46 | void setBlue(RGBAPixel& p, int b) 47 | { 48 | p &= 0xff00ffff; 49 | p |= (b & 0xff) << 16; 50 | } 51 | 52 | void setGreen(RGBAPixel& p, int g) 53 | { 54 | p &= 0xffff00ff; 55 | p |= (g & 0xff) << 8; 56 | } 57 | 58 | void setRed(RGBAPixel& p, int r) 59 | { 60 | p &= 0xffffff00; 61 | p |= r & 0xff; 62 | } 63 | 64 | 65 | void RGBAImage::create(int32_t ww, int32_t hh) 66 | { 67 | w = ww; 68 | h = hh; 69 | data.clear(); 70 | data.resize(w*h, 0); 71 | } 72 | 73 | 74 | 75 | struct fcloser 76 | { 77 | FILE *f; 78 | fcloser(FILE *ff) : f(ff) {} 79 | ~fcloser() {fclose(f);} 80 | }; 81 | 82 | struct PNGReadCleaner 83 | { 84 | png_structp png; 85 | png_infop info; 86 | png_infop endinfo; 87 | PNGReadCleaner() : png(NULL), info(NULL), endinfo(NULL) {} 88 | ~PNGReadCleaner() {png_destroy_read_struct(&png, &info, &endinfo);} 89 | }; 90 | 91 | struct PNGWriteCleaner 92 | { 93 | png_structp png; 94 | png_infop info; 95 | PNGWriteCleaner() : png(NULL), info(NULL) {} 96 | ~PNGWriteCleaner() {png_destroy_write_struct(&png, &info);} 97 | }; 98 | 99 | 100 | 101 | bool RGBAImage::readPNG(const string& filename) 102 | { 103 | FILE *f = fopen(filename.c_str(), "rb"); 104 | if (f == NULL) 105 | return false; 106 | fcloser fc(f); 107 | 108 | uint8_t header[8]; 109 | fread(header, 1, 8, f); 110 | if (0 != png_sig_cmp(header, 0, 8)) 111 | return false; 112 | 113 | PNGReadCleaner cleaner; 114 | 115 | png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 116 | if (png == NULL) 117 | return false; 118 | cleaner.png = png; 119 | 120 | png_infop info = png_create_info_struct(png); 121 | if (info == NULL) 122 | return false; 123 | cleaner.info = info; 124 | 125 | if (setjmp(png_jmpbuf(png))) 126 | return false; 127 | 128 | png_init_io(png, f); 129 | png_set_sig_bytes(png, 8); 130 | 131 | png_read_info(png, info); 132 | if (PNG_COLOR_TYPE_RGB_ALPHA != png_get_color_type(png, info) || 8 != png_get_bit_depth(png, info)) 133 | return false; 134 | w = png_get_image_width(png, info); 135 | h = png_get_image_height(png, info); 136 | data.resize(w*h); 137 | 138 | png_set_interlace_handling(png); 139 | png_read_update_info(png, info); 140 | 141 | png_bytep *rowPointers = new png_bytep[h]; 142 | arrayDeleter ad(rowPointers); 143 | RGBAPixel *p = &data[0]; 144 | for (int32_t i = 0; i < h; i++, p += w) 145 | rowPointers[i] = (png_bytep)p; 146 | 147 | if (isBigEndian()) 148 | { 149 | png_set_bgr(png); 150 | png_set_swap_alpha(png); 151 | } 152 | 153 | png_read_image(png, rowPointers); 154 | 155 | png_read_end(png, NULL); 156 | return true; 157 | } 158 | 159 | bool RGBAImage::writePNG(const string& filename) 160 | { 161 | FILE *f = fopen(filename.c_str(), "wb"); 162 | if (f == NULL) 163 | { 164 | // if the directory didn't exist, create it and try again 165 | if (errno == ENOENT) 166 | { 167 | makePath(filename.substr(0, filename.rfind('/'))); 168 | f = fopen(filename.c_str(), "wb"); 169 | } 170 | if (f == NULL) 171 | return false; 172 | } 173 | fcloser fc(f); 174 | 175 | PNGWriteCleaner cleaner; 176 | 177 | png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); 178 | if (png == NULL) 179 | return false; 180 | cleaner.png = png; 181 | 182 | png_infop info = png_create_info_struct(png); 183 | if (info == NULL) 184 | return false; 185 | cleaner.info = info; 186 | 187 | if (setjmp(png_jmpbuf(png))) 188 | return false; 189 | 190 | png_init_io(png, f); 191 | 192 | png_set_IHDR(png, info, w, h, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); 193 | 194 | png_bytep *rowPointers = new png_bytep[h]; 195 | arrayDeleter ad(rowPointers); 196 | RGBAPixel *p = &data[0]; 197 | for (int32_t i = 0; i < h; i++, p += w) 198 | rowPointers[i] = (png_bytep)p; 199 | 200 | png_set_rows(png, info, rowPointers); 201 | 202 | if (isBigEndian()) 203 | png_write_png(png, info, PNG_TRANSFORM_BGR | PNG_TRANSFORM_SWAP_ALPHA, NULL); 204 | else 205 | png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL); 206 | 207 | return true; 208 | } 209 | 210 | 211 | 212 | 213 | 214 | void fullblend(RGBAPixel& dest, const RGBAPixel& source) 215 | { 216 | // get sa and sainv in the range 1-256; this way, the possible results of blending 8-bit color channels sc and dc 217 | // (using sc*sa + dc*sainv) span the range 0x0000-0xffff, so we can just truncate and shift 218 | int64_t sa = ALPHA(source) + 1; 219 | int64_t sainv = 257 - sa; 220 | // compute the new RGB channels 221 | int64_t d = dest, s = source; 222 | d = ((d << 16) & UINT64_C(0xff00000000)) | ((d << 8) & 0xff0000) | (d & 0xff); 223 | s = ((s << 16) & UINT64_C(0xff00000000)) | ((s << 8) & 0xff0000) | (s & 0xff); 224 | int64_t newrgb = s*sa + d*sainv; 225 | // compute the new alpha channel 226 | int64_t dainv = 256 - ALPHA(dest); 227 | int64_t newa = sainv * dainv; // result is from 1-0x10000 228 | newa = (newa - 1) >> 8; // result is from 0-0xff 229 | newa = 255 - newa; // final result; if either input was 255, so is this, so opacity is preserved 230 | // combine everything and write it out 231 | dest = (newa << 24) | ((newrgb >> 24) & 0xff0000) | ((newrgb >> 16) & 0xff00) | ((newrgb >> 8) & 0xff); 232 | } 233 | 234 | // if destination pixel is already 100% opaque, no need to calculate its new alpha 235 | void opaqueblend(RGBAPixel& dest, const RGBAPixel& source) 236 | { 237 | // get sa and sainv in the range 1-256; this way, the possible results of blending 8-bit color channels sc and dc 238 | // (using sc*sa + dc*sainv) span the range 0x0000-0xffff, so we can just truncate and shift 239 | int64_t sa = ALPHA(source) + 1; 240 | int64_t sainv = 257 - sa; 241 | // compute the new RGB channels 242 | int64_t d = dest, s = source; 243 | d = ((d << 16) & UINT64_C(0xff00000000)) | ((d << 8) & 0xff0000) | (d & 0xff); 244 | s = ((s << 16) & UINT64_C(0xff00000000)) | ((s << 8) & 0xff0000) | (s & 0xff); 245 | int64_t newrgb = s*sa + d*sainv; 246 | // destination alpha remains 100%; combine everything and write it out 247 | dest = 0xff000000 | ((newrgb >> 24) & 0xff0000) | ((newrgb >> 16) & 0xff00) | ((newrgb >> 8) & 0xff); 248 | } 249 | 250 | void blend(RGBAPixel& dest, const RGBAPixel& source) 251 | { 252 | // if source is transparent, there's nothing to do 253 | if (source <= 0xffffff) 254 | return; 255 | // if source is opaque, or if destination is transparent, just copy it over 256 | else if (source >= 0xff000000 || dest <= 0xffffff) 257 | dest = source; 258 | // if source is translucent and dest is opaque, the color channels need to be blended, 259 | // but the new pixel will be opaque 260 | else if (dest >= 0xff000000) 261 | opaqueblend(dest, source); 262 | // both source and dest are translucent; we need the whole deal 263 | else 264 | fullblend(dest, source); 265 | } 266 | 267 | void alphablit(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, int32_t dxstart, int32_t dystart) 268 | { 269 | int32_t ybegin = max(0, max(-srect.y, -dystart)); 270 | int32_t yend = min(srect.h, min(source.h-srect.y, dest.h-dystart)); 271 | int32_t xbegin = max(0, max(-srect.x, -dxstart)); 272 | int32_t xend = min(srect.w, min(source.w-srect.x, dest.w-dxstart)); 273 | for (int32_t yoff = ybegin, sy = srect.y + ybegin, dy = dystart + ybegin; yoff < yend; yoff++, sy++, dy++) 274 | for (int32_t xoff = xbegin, sx = srect.x + xbegin, dx = dxstart + xbegin; xoff < xend; xoff++, sx++, dx++) 275 | blend(dest(dx,dy), source(sx,sy)); 276 | } 277 | 278 | void reduceHalf(RGBAImage& dest, const ImageRect& drect, const RGBAImage& source) 279 | { 280 | if (source.w != drect.w*2 || source.h != drect.h*2) 281 | return; 282 | for (int32_t dy = drect.y, sy = 0; sy < source.h; dy++, sy += 2) 283 | for (int32_t dx = drect.x, sx = 0; sx < source.w; dx++, sx += 2) 284 | { 285 | RGBAPixel p1 = (source(sx, sy) >> 2) & 0x3f3f3f3f; 286 | RGBAPixel p2 = (source(sx+1, sy) >> 2) & 0x3f3f3f3f; 287 | RGBAPixel p3 = (source(sx, sy+1) >> 2) & 0x3f3f3f3f; 288 | RGBAPixel p4 = (source(sx+1, sy+1) >> 2) & 0x3f3f3f3f; 289 | dest(dx, dy) = p1 + p2 + p3 + p4; 290 | } 291 | } 292 | 293 | 294 | 295 | 296 | //!!!!!!!!! replace this with something non-idiotic? (it does surprisingly well, though!) 297 | void resize(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, const ImageRect& drect) 298 | { 299 | for (int y = drect.y; y < drect.y + drect.h; y++) 300 | { 301 | int yoff = interpolate(y - drect.y, drect.h, srect.h); 302 | for (int x = drect.x; x < drect.x + drect.w; x++) 303 | { 304 | int xoff = interpolate(x - drect.x, drect.w, srect.w); 305 | dest(x, y) = source(srect.x + xoff, srect.y + yoff); 306 | } 307 | } 308 | } 309 | 310 | void darken(RGBAPixel& dest, double r, double g, double b) 311 | { 312 | uint8_t newr = (uint8_t)(r * (double)(RED(dest))); 313 | uint8_t newg = (uint8_t)(g * (double)(GREEN(dest))); 314 | uint8_t newb = (uint8_t)(b * (double)(BLUE(dest))); 315 | dest = makeRGBA(newr, newg, newb, ALPHA(dest)); 316 | } 317 | 318 | void darken(RGBAImage& img, const ImageRect& rect, double r, double g, double b) 319 | { 320 | for (int y = rect.y; y < rect.y + rect.h; y++) 321 | for (int x = rect.x; x < rect.x + rect.w; x++) 322 | darken(img(x, y), r, g, b); 323 | } 324 | 325 | void blit(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, int32_t dxstart, int32_t dystart) 326 | { 327 | int32_t ybegin = max(0, max(-srect.y, -dystart)); 328 | int32_t yend = min(srect.h, min(source.h-srect.y, dest.h-dystart)); 329 | int32_t xbegin = max(0, max(-srect.x, -dxstart)); 330 | int32_t xend = min(srect.w, min(source.w-srect.x, dest.w-dxstart)); 331 | for (int32_t yoff = ybegin, sy = srect.y + ybegin, dy = dystart + ybegin; yoff < yend; yoff++, sy++, dy++) 332 | for (int32_t xoff = xbegin, sx = srect.x + xbegin, dx = dxstart + xbegin; xoff < xend; xoff++, sx++, dx++) 333 | dest(dx,dy) = source(sx,sy); 334 | } 335 | 336 | void flipX(RGBAImage& img, const ImageRect& rect) 337 | { 338 | for (int y = rect.y; y < rect.y + rect.h; y++) 339 | for (int x1 = rect.x, x2 = rect.x + rect.w - 1; x1 < rect.x + rect.w/2; x1++, x2--) 340 | swap(img(x1, y), img(x2, y)); 341 | } 342 | -------------------------------------------------------------------------------- /map.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2010-2012 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #include 19 | #include 20 | 21 | #include "map.h" 22 | #include "utils.h" 23 | 24 | using namespace std; 25 | 26 | 27 | 28 | bool MapParams::valid() const 29 | { 30 | return B >= 2 && B <= 16 && T >= 1 && T <= 16; 31 | } 32 | 33 | bool MapParams::validZoom() const 34 | { 35 | return baseZoom >= 0 && baseZoom <= 30; 36 | } 37 | 38 | bool MapParams::validYRange() const 39 | { 40 | return minY <= maxY && minY >= 0 && maxY <= 255; 41 | } 42 | 43 | 44 | bool buildParamMap(const vector& lines, map& params) 45 | { 46 | for (vector::const_iterator it = lines.begin(); it != lines.end(); it++) 47 | { 48 | vector tokens = tokenize(*it, ' '); 49 | if (tokens.size() != 2) 50 | return false; 51 | params.insert(make_pair(tokens[0], tokens[1])); 52 | } 53 | return true; 54 | } 55 | 56 | bool readParam(const map& params, const string& key, int& value) 57 | { 58 | map::const_iterator it = params.find(key); 59 | if (it == params.end()) 60 | return false; 61 | return fromstring(it->second, value); 62 | } 63 | 64 | bool MapParams::readFile(const string& outputpath) 65 | { 66 | string filename = outputpath + "/pigmap.params"; 67 | vector lines; 68 | map params; 69 | if (!readLines(filename, lines) || !buildParamMap(lines, params)) 70 | return false; 71 | if (!readParam(params, "B", B) || !readParam(params, "T", T) || !readParam(params, "baseZoom", baseZoom)) 72 | return false; 73 | userMinY = readParam(params, "userMinY", minY); 74 | userMaxY = readParam(params, "userMaxY", maxY); 75 | return valid() && validZoom(); 76 | } 77 | 78 | void MapParams::writeFile(const string& outputpath) const 79 | { 80 | string filename = outputpath + "/pigmap.params"; 81 | ofstream outfile(filename.c_str()); 82 | outfile << "B " << B << endl << "T " << T << endl << "baseZoom " << baseZoom << endl; 83 | if (userMinY) 84 | outfile << "userMinY " << minY << endl; 85 | if (userMaxY) 86 | outfile << "userMaxY " << maxY << endl; 87 | } 88 | 89 | 90 | 91 | 92 | 93 | 94 | Pixel operator+(const Pixel& p1, const Pixel& p2) {Pixel p = p1; return p += p2;} 95 | Pixel operator-(const Pixel& p1, const Pixel& p2) {Pixel p = p1; return p -= p2;} 96 | 97 | TileIdx Pixel::getTile(const MapParams& mp) const 98 | { 99 | int64_t xx = x + 2*mp.B, yy = y + mp.tileSize() - 17*mp.B; 100 | return TileIdx(floordiv(xx, mp.tileSize()), floordiv(yy, mp.tileSize())); 101 | } 102 | 103 | 104 | bool BBox::includes(const Pixel& p) const {return p.x >= topLeft.x && p.x < bottomRight.x && p.y >= topLeft.y && p.y < bottomRight.y;} 105 | bool BBox::overlaps(const BBox& bb) const 106 | { 107 | if (bb.topLeft.x >= bottomRight.x || bb.topLeft.y >= bottomRight.y || bb.bottomRight.x <= topLeft.x || bb.bottomRight.y <= topLeft.y) 108 | return false; 109 | return true; 110 | } 111 | 112 | 113 | BlockIdx operator+(const BlockIdx& bi1, const BlockIdx& bi2) {BlockIdx bi = bi1; return bi += bi2;} 114 | BlockIdx operator-(const BlockIdx& bi1, const BlockIdx& bi2) {BlockIdx bi = bi1; return bi -= bi2;} 115 | 116 | bool BlockIdx::occludes(const BlockIdx& bi) const 117 | { 118 | int64_t dx = bi.x - x, dz = bi.z - z, dy = bi.y - y; 119 | // we cannot occlude anyone to the N, W, or U of us 120 | if (dx < 0 || dz > 0 || dy > 0) 121 | return false; 122 | // see if the other block's center is 0 or 1 steps away from ours on the triangular grid 123 | // (the actual grid size doesn't matter; just use a dummy size of 2x1) 124 | int64_t imgxdiff = dx*2 + dz*2; 125 | int64_t imgydiff = -dx + dz - dy*2; 126 | return imgxdiff <= 2 && imgydiff <= 2; 127 | } 128 | 129 | ChunkIdx BlockIdx::getChunkIdx() const 130 | { 131 | return ChunkIdx(floordiv16(x), floordiv16(z)); 132 | } 133 | 134 | BlockIdx BlockIdx::topBlock(const Pixel& p, const MapParams& mp) 135 | { 136 | // x = 2Bbx + 2Bbz 137 | // 2Bbx = x - 2Bbz 138 | // bx = x/2B - bz 139 | // y = -Bbx +Bbz -2Bmaxy 140 | // Bbz = y + Bbx + 2Bmaxy 141 | // bz = y/B + bx + 2maxy 142 | // bx = x/2B - y/B - bx - 2maxy 143 | // 2bx = x/2B - y/B - 2maxy 144 | // bx = x/4B - y/2B - maxy <---- 145 | // bz = y/B + x/4B - y/2B - maxy + 2maxy 146 | // bz = x/4B + y/2B + maxy <---- 147 | return BlockIdx((p.x - 2*p.y)/(4*mp.B) - mp.maxY, (p.x + 2*p.y)/(4*mp.B) + mp.maxY, mp.maxY); 148 | } 149 | 150 | 151 | 152 | 153 | string ChunkIdx::toFileName() const 154 | { 155 | return "c." + toBase36(x) + "." + toBase36(z) + ".dat"; 156 | } 157 | string ChunkIdx::toFilePath() const 158 | { 159 | return toBase36(mod64pos(x)) + "/" + toBase36(mod64pos(z)) + "/" + toFileName(); 160 | } 161 | 162 | bool ChunkIdx::fromFilePath(const std::string& filename, ChunkIdx& result) 163 | { 164 | string::size_type pos3 = filename.rfind('.'); 165 | string::size_type pos2 = filename.rfind('.', pos3 - 1); 166 | string::size_type pos = filename.rfind('.', pos2 - 1); 167 | // must have three dots, must have only "dat" after last dot, must have only "c" 168 | // and possibly some directories before the first dot 169 | if (pos == string::npos || pos2 == string::npos || pos3 == string::npos || 170 | filename.compare(pos3, filename.size() - pos3, ".dat") != 0 || 171 | pos < 1 || filename.compare(pos - 1, 1, "c") != 0 || 172 | (pos > 1 && filename[pos - 2] != '/')) 173 | return false; 174 | return fromBase36(filename, pos + 1, pos2 - pos - 1, result.x) 175 | && fromBase36(filename, pos2 + 1, pos3 - pos2 - 1, result.z); 176 | } 177 | 178 | RegionIdx ChunkIdx::getRegionIdx() const 179 | { 180 | return RegionIdx(floordiv(x, 32), floordiv(z, 32)); 181 | } 182 | 183 | vector ChunkIdx::getTiles(const MapParams& mp) const 184 | { 185 | BBox bbchunk = getBBox(mp); 186 | vector tiles; 187 | 188 | // get tile of NED corner 189 | TileIdx tibase = nedCorner(mp).getCenter(mp).getTile(mp); 190 | tiles.push_back(tibase); 191 | 192 | // grab as many tiles down as we need 193 | TileIdx tidown = tibase + TileIdx(0,1); 194 | while (tidown.getBBox(mp).overlaps(bbchunk)) 195 | { 196 | tiles.push_back(tidown); 197 | tidown += TileIdx(0,1); 198 | } 199 | 200 | // grab as many tiles up as we need 201 | TileIdx tiup = tibase - TileIdx(0,1); 202 | while (tiup.getBBox(mp).overlaps(bbchunk)) 203 | { 204 | tiles.push_back(tiup); 205 | tiup -= TileIdx(0,1); 206 | } 207 | 208 | // we may also need the tiles to the right of all the ones we have so far 209 | TileIdx tiright = tibase + TileIdx(1,0); 210 | if (tiright.getBBox(mp).overlaps(bbchunk)) 211 | { 212 | vector::size_type oldsize = tiles.size(); 213 | for (vector::size_type i = 0; i < oldsize; i++) 214 | tiles.push_back(tiles[i] + TileIdx(1,0)); 215 | } 216 | 217 | return tiles; 218 | } 219 | 220 | ChunkIdx operator+(const ChunkIdx& ci1, const ChunkIdx& ci2) {ChunkIdx ci = ci1; return ci += ci2;} 221 | ChunkIdx operator-(const ChunkIdx& ci1, const ChunkIdx& ci2) {ChunkIdx ci = ci1; return ci -= ci2;} 222 | 223 | 224 | 225 | string RegionIdx::toOldFileName() const 226 | { 227 | return "r." + tostring(x) + "." + tostring(z) + ".mcr"; 228 | } 229 | 230 | string RegionIdx::toAnvilFileName() const 231 | { 232 | return "r." + tostring(x) + "." + tostring(z) + ".mca"; 233 | } 234 | 235 | bool RegionIdx::fromFilePath(const std::string& filename, RegionIdx& result) 236 | { 237 | string::size_type pos3 = filename.rfind('.'); 238 | string::size_type pos2 = filename.rfind('.', pos3 - 1); 239 | string::size_type pos = filename.rfind('.', pos2 - 1); 240 | // must have three dots, must have only "mcr" or "mca" after last dot, must have only "r" 241 | // and possibly some directories before the first dot 242 | if (pos == string::npos || pos2 == string::npos || pos3 == string::npos || 243 | (filename.compare(pos3, filename.size() - pos3, ".mcr") != 0 && filename.compare(pos3, filename.size() - pos3, ".mca") != 0) || 244 | pos < 1 || filename.compare(pos - 1, 1, "r") != 0 || 245 | (pos > 1 && filename[pos - 2] != '/')) 246 | return false; 247 | return fromstring(filename.substr(pos + 1, pos2 - pos - 1), result.x) 248 | && fromstring(filename.substr(pos2 + 1, pos3 - pos2 - 1), result.z); 249 | } 250 | 251 | 252 | TileIdx operator+(const TileIdx& t1, const TileIdx& t2) {TileIdx t = t1; return t += t2;} 253 | TileIdx operator-(const TileIdx& t1, const TileIdx& t2) {TileIdx t = t1; return t -= t2;} 254 | 255 | bool TileIdx::valid(const MapParams& mp) const 256 | { 257 | if (mp.baseZoom == 0) 258 | return x == 0 && y == 0; 259 | int64_t max = (1 << mp.baseZoom); 260 | int64_t offset = max/2; 261 | int64_t gx = x + offset, gy = y + offset; 262 | return gx >= 0 && gx < max && gy >= 0 && gy < max; 263 | } 264 | 265 | string TileIdx::toFilePath(const MapParams& mp) const 266 | { 267 | if (!valid(mp)) 268 | return string(); 269 | if (mp.baseZoom == 0) 270 | return "base.png"; 271 | int64_t offset = (1 << (mp.baseZoom-1)); 272 | int64_t gx = x + offset, gy = y + offset; 273 | string s; 274 | for (int zoom = mp.baseZoom-1; zoom >= 0; zoom--) 275 | { 276 | int64_t xbit = (gx >> zoom) & 0x1; 277 | int64_t ybit = (gy >> zoom) & 0x1; 278 | s += tostring(xbit + 2*ybit) + "/"; 279 | } 280 | s.resize(s.size() - 1); // drop final slash 281 | s += ".png"; 282 | return s; 283 | } 284 | 285 | BBox TileIdx::getBBox(const MapParams& mp) const 286 | { 287 | Pixel bco = baseChunk(mp).originBlock().getCenter(mp); 288 | Pixel tl = bco + Pixel(-2*mp.B, 17*mp.B - mp.tileSize()); 289 | return BBox(tl, tl + Pixel(mp.tileSize(), mp.tileSize())); 290 | } 291 | 292 | ZoomTileIdx TileIdx::toZoomTileIdx(const MapParams& mp) const 293 | { 294 | // adjust by offset 295 | int64_t max = (1 << mp.baseZoom); 296 | int64_t offset = max/2; 297 | return ZoomTileIdx(x + offset, y + offset, mp.baseZoom); 298 | } 299 | 300 | 301 | 302 | bool ZoomTileIdx::valid() const 303 | { 304 | int64_t max = (1 << zoom); 305 | return x >= 0 && x < max && y >= 0 && y < max && zoom >= 0; 306 | } 307 | 308 | string ZoomTileIdx::toFilePath() const 309 | { 310 | if (!valid()) 311 | return string(); 312 | if (zoom == 0) 313 | return "base.png"; 314 | string s; 315 | for (int z = zoom-1; z >= 0; z--) 316 | { 317 | int64_t xbit = (x >> z) & 0x1; 318 | int64_t ybit = (y >> z) & 0x1; 319 | s += tostring(xbit + 2*ybit) + "/"; 320 | } 321 | s.resize(s.size() - 1); // drop final slash 322 | s += ".png"; 323 | return s; 324 | } 325 | 326 | TileIdx ZoomTileIdx::toTileIdx(const MapParams& mp) const 327 | { 328 | // scale coords up to base zoom 329 | int64_t newx = x, newy = y; 330 | int shift = mp.baseZoom - zoom; 331 | newx <<= shift; 332 | newy <<= shift; 333 | // adjust by offset to get TileIdx 334 | int64_t max = (1 << mp.baseZoom); 335 | int64_t offset = max/2; 336 | return TileIdx(newx - offset, newy - offset); 337 | } 338 | 339 | ZoomTileIdx ZoomTileIdx::toZoom(int z) const 340 | { 341 | if (z > zoom) 342 | { 343 | int shift = z - zoom; 344 | return ZoomTileIdx(x << shift, y << shift, z); 345 | } 346 | int shift = zoom - z; 347 | return ZoomTileIdx(x >> shift, y >> shift, z); 348 | } 349 | -------------------------------------------------------------------------------- /tables.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2010, 2011 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #include 19 | 20 | #include "tables.h" 21 | 22 | using namespace std; 23 | 24 | 25 | 26 | void ChunkGroup::setRequired(const PosChunkIdx& ci) 27 | { 28 | int csi = chunkSetIdx(ci); 29 | if (chunksets[csi] == NULL) 30 | chunksets[csi] = new ChunkSet; 31 | chunksets[csi]->setRequired(ci); 32 | } 33 | 34 | void ChunkGroup::setDiskState(const PosChunkIdx& ci, int state) 35 | { 36 | int csi = chunkSetIdx(ci); 37 | if (chunksets[csi] == NULL) 38 | chunksets[csi] = new ChunkSet; 39 | chunksets[csi]->setDiskState(ci, state); 40 | } 41 | 42 | 43 | 44 | 45 | PosChunkIdx ChunkTable::toPosChunkIdx(int cgi, int csi, int bi) 46 | { 47 | PosChunkIdx ci(0,0); 48 | ci.x += (cgi % CTLEVEL3SIZE) * CTLEVEL1SIZE * CTLEVEL2SIZE; 49 | ci.z += (cgi / CTLEVEL3SIZE) * CTLEVEL1SIZE * CTLEVEL2SIZE; 50 | ci.x += (csi % CTLEVEL2SIZE) * CTLEVEL1SIZE; 51 | ci.z += (csi / CTLEVEL2SIZE) * CTLEVEL1SIZE; 52 | ci.x += ((bi / CTDATASIZE) % CTLEVEL1SIZE); 53 | ci.z += ((bi / CTDATASIZE) / CTLEVEL1SIZE); 54 | return ci; 55 | } 56 | 57 | void ChunkTable::setRequired(const PosChunkIdx& ci) 58 | { 59 | int cgi = chunkGroupIdx(ci); 60 | if (chunkgroups[cgi] == NULL) 61 | chunkgroups[cgi] = new ChunkGroup; 62 | chunkgroups[cgi]->setRequired(ci); 63 | } 64 | 65 | void ChunkTable::setDiskState(const PosChunkIdx& ci, int state) 66 | { 67 | int cgi = chunkGroupIdx(ci); 68 | if (chunkgroups[cgi] == NULL) 69 | chunkgroups[cgi] = new ChunkGroup; 70 | chunkgroups[cgi]->setDiskState(ci, state); 71 | } 72 | 73 | void ChunkTable::copyFrom(const ChunkTable& ctable) 74 | { 75 | for (int cgi = 0; cgi < CTLEVEL3SIZE*CTLEVEL3SIZE; cgi++) 76 | { 77 | if (ctable.chunkgroups[cgi] != NULL) 78 | { 79 | chunkgroups[cgi] = new ChunkGroup; 80 | for (int csi = 0; csi < CTLEVEL2SIZE*CTLEVEL2SIZE; csi++) 81 | { 82 | if (ctable.chunkgroups[cgi]->chunksets[csi] != NULL) 83 | { 84 | chunkgroups[cgi]->chunksets[csi] = new ChunkSet(*(ctable.chunkgroups[cgi]->chunksets[csi])); 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | 92 | 93 | RequiredChunkIterator::RequiredChunkIterator(ChunkTable& ctable) : chunktable(ctable), current(-1,-1) 94 | { 95 | // if the very first chunk is required, use it 96 | cgi = csi = bi = 0; 97 | current = ChunkTable::toPosChunkIdx(cgi, csi, bi); 98 | if (chunktable.isRequired(current)) 99 | { 100 | end = false; 101 | return; 102 | } 103 | // ...otherwise, advance to the next one after it 104 | advance(); 105 | } 106 | 107 | void RequiredChunkIterator::advance() 108 | { 109 | bi += CTDATASIZE; 110 | for (; cgi < CTLEVEL3SIZE*CTLEVEL3SIZE; cgi++) 111 | { 112 | ChunkGroup *cg = chunktable.chunkgroups[cgi]; 113 | if (cg == NULL) 114 | continue; 115 | for (; csi < CTLEVEL2SIZE*CTLEVEL2SIZE; csi++) 116 | { 117 | ChunkSet *cs = cg->chunksets[csi]; 118 | if (cs == NULL) 119 | continue; 120 | for (; bi < CTLEVEL1SIZE*CTLEVEL1SIZE*CTDATASIZE; bi += CTDATASIZE) 121 | { 122 | if (cs->bits[bi]) 123 | { 124 | end = false; 125 | current = chunktable.toPosChunkIdx(cgi, csi, bi); 126 | return; 127 | } 128 | } 129 | bi = 0; 130 | } 131 | csi = 0; 132 | bi = 0; 133 | } 134 | end = true; 135 | } 136 | 137 | 138 | 139 | 140 | 141 | 142 | bool TileGroup::setRequired(const PosTileIdx& ti) 143 | { 144 | int tsi = tileSetIdx(ti); 145 | if (tilesets[tsi] == NULL) 146 | tilesets[tsi] = new TileSet; 147 | bool prevset = tilesets[tsi]->setRequired(ti); 148 | if (!prevset) 149 | reqcount++; 150 | return prevset; 151 | } 152 | 153 | void TileGroup::setDrawn(const PosTileIdx& ti) 154 | { 155 | int tsi = tileSetIdx(ti); 156 | if (tilesets[tsi] == NULL) 157 | tilesets[tsi] = new TileSet; 158 | tilesets[tsi]->setDrawn(ti); 159 | } 160 | 161 | PosTileIdx TileTable::toPosTileIdx(int tgi, int tsi, int bi) 162 | { 163 | PosTileIdx ti(0,0); 164 | ti.x += (tgi % TTLEVEL3SIZE) * TTLEVEL1SIZE * TTLEVEL2SIZE; 165 | ti.y += (tgi / TTLEVEL3SIZE) * TTLEVEL1SIZE * TTLEVEL2SIZE; 166 | ti.x += (tsi % TTLEVEL2SIZE) * TTLEVEL1SIZE; 167 | ti.y += (tsi / TTLEVEL2SIZE) * TTLEVEL1SIZE; 168 | ti.x += ((bi / TTDATASIZE) % TTLEVEL1SIZE); 169 | ti.y += ((bi / TTDATASIZE) / TTLEVEL1SIZE); 170 | return ti; 171 | } 172 | 173 | bool TileTable::setRequired(const PosTileIdx& ti) 174 | { 175 | int tgi = tileGroupIdx(ti); 176 | if (tilegroups[tgi] == NULL) 177 | tilegroups[tgi] = new TileGroup; 178 | bool prevset = tilegroups[tgi]->setRequired(ti); 179 | if (!prevset) 180 | reqcount++; 181 | return prevset; 182 | } 183 | 184 | void TileTable::setDrawn(const PosTileIdx& ti) 185 | { 186 | int tgi = tileGroupIdx(ti); 187 | if (tilegroups[tgi] == NULL) 188 | tilegroups[tgi] = new TileGroup; 189 | tilegroups[tgi]->setDrawn(ti); 190 | } 191 | 192 | bool TileTable::reject(const ZoomTileIdx& zti, const MapParams& mp) const 193 | { 194 | // if this zoom tile includes more than one TileGroup, we can't reject early 195 | if (zti.zoom < mp.baseZoom - TTLEVEL1BITS - TTLEVEL2BITS) 196 | return false; 197 | // zoom tiles anywhere except level 0 have the property of not crossing TileSet/TileGroup 198 | // boundaries--either they're entirely inside a set/group, or they contain entire 199 | // sets/groups--but for 0, that's not the case, so we'd have to check multiple sets/groups; 200 | // instead, we just don't bother trying, since the tile at level 0 is going to have to be 201 | // drawn anyway 202 | if (zti.zoom == 0) 203 | return false; 204 | TileIdx ti = zti.toTileIdx(mp); 205 | // if this zoom tile is contained within a TileSet, see if the set is NULL 206 | if (zti.zoom >= mp.baseZoom - TTLEVEL1BITS) 207 | return getTileSet(ti) == NULL; 208 | // otherwise, the tile is within a TileGroup, but covers more than one TileSet; see if the TileGroup is NULL 209 | return getTileGroup(ti) == NULL; 210 | } 211 | 212 | int64_t TileTable::getNumRequired(const ZoomTileIdx& zti, const MapParams& mp) const 213 | { 214 | // if this is the very top level, we already know the answer 215 | if (zti.zoom == 0) 216 | return reqcount; 217 | // if this zoom tile is smaller than a TileSet, get its TileSet and check the tiles individually 218 | if (zti.zoom > mp.baseZoom - TTLEVEL1BITS) 219 | { 220 | TileIdx topleft = zti.toTileIdx(mp); 221 | TileSet *ts = getTileSet(topleft); 222 | if (ts == NULL) 223 | return 0; 224 | int64_t count = 0; 225 | int64_t size = 1 << (mp.baseZoom - zti.zoom); 226 | for (int64_t x = 0; x < size; x++) 227 | for (int64_t y = 0; y < size; y++) 228 | if (ts->isRequired(topleft + TileIdx(x, y))) 229 | count++; 230 | return count; 231 | } 232 | // if >= TileSet size, but < TileGroup size, get the TileGroup and check the sets individually 233 | if (zti.zoom > mp.baseZoom - TTLEVEL1BITS - TTLEVEL2BITS) 234 | { 235 | TileIdx topleft = zti.toTileIdx(mp); 236 | TileGroup *tg = getTileGroup(topleft); 237 | if (tg == NULL) 238 | return 0; 239 | int64_t count = 0; 240 | int64_t size = 1 << (mp.baseZoom - TTLEVEL1BITS - zti.zoom); 241 | for (int64_t x = 0; x < size; x++) 242 | for (int64_t y = 0; y < size; y++) 243 | { 244 | TileSet *ts = tg->getTileSet(topleft + TileIdx(x << TTLEVEL1BITS, y << TTLEVEL1BITS)); 245 | if (ts != NULL) 246 | count += ts->bits.count(); 247 | } 248 | return count; 249 | } 250 | // if >= TileGroup size, check the TileGroups individually 251 | TileIdx topleft = zti.toTileIdx(mp); 252 | int64_t count = 0; 253 | int64_t size = 1 << (mp.baseZoom - TTLEVEL1BITS - TTLEVEL2BITS - zti.zoom); 254 | for (int64_t x = 0; x < size; x++) 255 | for (int64_t y = 0; y < size; y++) 256 | { 257 | TileGroup *tg = getTileGroup(topleft + TileIdx(x << (TTLEVEL1BITS + TTLEVEL2BITS), y << (TTLEVEL1BITS + TTLEVEL2BITS))); 258 | if (tg != NULL) 259 | count += tg->reqcount; 260 | } 261 | return count; 262 | } 263 | 264 | void TileTable::copyFrom(const TileTable& ttable) 265 | { 266 | for (int tgi = 0; tgi < TTLEVEL3SIZE*TTLEVEL3SIZE; tgi++) 267 | { 268 | if (ttable.tilegroups[tgi] != NULL) 269 | { 270 | tilegroups[tgi] = new TileGroup; 271 | for (int tsi = 0; tsi < TTLEVEL2SIZE*TTLEVEL2SIZE; tsi++) 272 | { 273 | if (ttable.tilegroups[tgi]->tilesets[tsi] != NULL) 274 | { 275 | tilegroups[tgi]->tilesets[tsi] = new TileSet(*(ttable.tilegroups[tgi]->tilesets[tsi])); 276 | } 277 | } 278 | } 279 | } 280 | } 281 | 282 | 283 | 284 | RequiredTileIterator::RequiredTileIterator(TileTable& ttable) : tiletable(ttable), current(-1,-1) 285 | { 286 | // if the very first tile is required, use it 287 | ztgi = ztsi = zbi = 0; 288 | current = TileTable::toPosTileIdx(fromZOrder(ztgi, TTLEVEL3SIZE), fromZOrder(ztsi, TTLEVEL2SIZE), fromZOrder(zbi, TTLEVEL1SIZE)*TTDATASIZE); 289 | if (tiletable.isRequired(current)) 290 | { 291 | end = false; 292 | return; 293 | } 294 | // ...otherwise, advance to the next one after it 295 | advance(); 296 | } 297 | 298 | void RequiredTileIterator::advance() 299 | { 300 | zbi++; 301 | for (; ztgi < TTLEVEL3SIZE*TTLEVEL3SIZE; ztgi++) 302 | { 303 | int tgi = fromZOrder(ztgi, TTLEVEL3SIZE); 304 | TileGroup *tg = tiletable.tilegroups[tgi]; 305 | if (tg == NULL) 306 | continue; 307 | for (; ztsi < TTLEVEL2SIZE*TTLEVEL2SIZE; ztsi++) 308 | { 309 | int tsi = fromZOrder(ztsi, TTLEVEL2SIZE); 310 | TileSet *ts = tg->tilesets[tsi]; 311 | if (ts == NULL) 312 | continue; 313 | for (; zbi < TTLEVEL1SIZE*TTLEVEL1SIZE; zbi++) 314 | { 315 | int bi = fromZOrder(zbi, TTLEVEL1SIZE); 316 | if (ts->bits[bi*TTDATASIZE]) 317 | { 318 | end = false; 319 | current = tiletable.toPosTileIdx(tgi, tsi, bi*TTDATASIZE); 320 | return; 321 | } 322 | } 323 | zbi = 0; 324 | } 325 | ztsi = 0; 326 | zbi = 0; 327 | } 328 | end = true; 329 | } 330 | 331 | 332 | 333 | ZoomTileIdx getZoomTile(int tgi, const MapParams& mp) 334 | { 335 | TileIdx ti = TileTable::toPosTileIdx(tgi, 0, 0).toTileIdx(); 336 | ZoomTileIdx zti = ti.toZoomTileIdx(mp); 337 | return zti.toZoom(mp.baseZoom - TTLEVEL1BITS - TTLEVEL2BITS); 338 | } 339 | 340 | TileGroupIterator::TileGroupIterator(TileTable& ttable, const MapParams& mparams) 341 | : tiletable(ttable), mp(mparams), zti(-1,-1,-1) 342 | { 343 | // if the very first TileGroup is non-NULL, use it 344 | tgi = 0; 345 | zti = getZoomTile(tgi, mp); 346 | end = false; 347 | if (tiletable.tilegroups[tgi] != NULL) 348 | return; 349 | // ...otherwise, advance to the next one 350 | advance(); 351 | } 352 | 353 | void TileGroupIterator::advance() 354 | { 355 | tgi++; 356 | for (; tgi < TTLEVEL3SIZE*TTLEVEL3SIZE; tgi++) 357 | { 358 | if (tiletable.tilegroups[tgi] != NULL) 359 | { 360 | zti = getZoomTile(tgi, mp); 361 | return; 362 | } 363 | } 364 | end = true; 365 | } 366 | 367 | 368 | 369 | 370 | 371 | void RegionGroup::setRequired(const PosRegionIdx& ri) 372 | { 373 | int rsi = regionSetIdx(ri); 374 | if (regionsets[rsi] == NULL) 375 | regionsets[rsi] = new RegionSet; 376 | regionsets[rsi]->setRequired(ri); 377 | } 378 | 379 | void RegionGroup::setDiskState(const PosRegionIdx& ri, int state) 380 | { 381 | int rsi = regionSetIdx(ri); 382 | if (regionsets[rsi] == NULL) 383 | regionsets[rsi] = new RegionSet; 384 | regionsets[rsi]->setDiskState(ri, state); 385 | } 386 | 387 | PosRegionIdx RegionTable::toPosRegionIdx(int rgi, int rsi, int bi) 388 | { 389 | PosRegionIdx ri(0,0); 390 | ri.x += (rgi % RTLEVEL3SIZE) * RTLEVEL1SIZE * RTLEVEL2SIZE; 391 | ri.z += (rgi / RTLEVEL3SIZE) * RTLEVEL1SIZE * RTLEVEL2SIZE; 392 | ri.x += (rsi % RTLEVEL2SIZE) * RTLEVEL1SIZE; 393 | ri.z += (rsi / RTLEVEL2SIZE) * RTLEVEL1SIZE; 394 | ri.x += ((bi / RTDATASIZE) % RTLEVEL1SIZE); 395 | ri.z += ((bi / RTDATASIZE) / RTLEVEL1SIZE); 396 | return ri; 397 | } 398 | 399 | void RegionTable::setRequired(const PosRegionIdx& ri) 400 | { 401 | int rgi = regionGroupIdx(ri); 402 | if (regiongroups[rgi] == NULL) 403 | regiongroups[rgi] = new RegionGroup; 404 | regiongroups[rgi]->setRequired(ri); 405 | } 406 | 407 | void RegionTable::setDiskState(const PosRegionIdx& ri, int state) 408 | { 409 | int rgi = regionGroupIdx(ri); 410 | if (regiongroups[rgi] == NULL) 411 | regiongroups[rgi] = new RegionGroup; 412 | regiongroups[rgi]->setDiskState(ri, state); 413 | } 414 | 415 | void RegionTable::copyFrom(const RegionTable& rtable) 416 | { 417 | for (int rgi = 0; rgi < RTLEVEL3SIZE*RTLEVEL3SIZE; rgi++) 418 | { 419 | if (rtable.regiongroups[rgi] != NULL) 420 | { 421 | regiongroups[rgi] = new RegionGroup; 422 | for (int rsi = 0; rsi < RTLEVEL2SIZE*RTLEVEL2SIZE; rsi++) 423 | { 424 | if (rtable.regiongroups[rgi]->regionsets[rsi] != NULL) 425 | { 426 | regiongroups[rgi]->regionsets[rsi] = new RegionSet(*(rtable.regiongroups[rgi]->regionsets[rsi])); 427 | } 428 | } 429 | } 430 | } 431 | } 432 | -------------------------------------------------------------------------------- /chunk.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2010-2012 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "chunk.h" 25 | #include "utils.h" 26 | 27 | using namespace std; 28 | 29 | 30 | 31 | //--------------------------------------------------------------------------------------------------- 32 | 33 | 34 | bool ChunkData::loadFromOldFile(const vector& filebuf) 35 | { 36 | anvil = false; 37 | // the hell with parsing this whole godforsaken NBT format; just look for the arrays we need 38 | uint8_t idsTag[13] = {7, 0, 6, 'B', 'l', 'o', 'c', 'k', 's', 0, 0, 128, 0}; 39 | uint8_t dataTag[11] = {7, 0, 4, 'D', 'a', 't', 'a', 0, 0, 64, 0}; 40 | bool foundIDs = false, foundData = false; 41 | for (vector::const_iterator it = filebuf.begin(); it != filebuf.end(); it++) 42 | { 43 | if (*it != 7) 44 | continue; 45 | if (!foundIDs && it + 13 + 32768 <= filebuf.end() && equal(it, it + 13, idsTag)) 46 | { 47 | copy(it + 13, it + 13 + 32768, blockIDs); 48 | it += 13 + 32768 - 1; // one less because of the loop we're in 49 | foundIDs = true; 50 | } 51 | else if (!foundData && it + 11 + 16384 <= filebuf.end() && equal(it, it + 11, dataTag)) 52 | { 53 | copy(it + 11, it + 11 + 16384, blockData); 54 | it += 11 + 16384 - 1; // one less because of the loop we're in 55 | foundData = true; 56 | } 57 | if (foundIDs && foundData) 58 | return true; 59 | } 60 | return false; 61 | } 62 | 63 | 64 | //--------------------------------------------------------------------------------------------------- 65 | 66 | 67 | // quasi-NBT-parsing stuff for Anvil format: doesn't actually bother trying to read the whole thing, 68 | // just skips through the data looking for what we're interested in 69 | #define TAG_END 0 70 | #define TAG_BYTE 1 71 | #define TAG_SHORT 2 72 | #define TAG_INT 3 73 | #define TAG_LONG 4 74 | #define TAG_FLOAT 5 75 | #define TAG_DOUBLE 6 76 | #define TAG_BYTE_ARRAY 7 77 | #define TAG_STRING 8 78 | #define TAG_LIST 9 79 | #define TAG_COMPOUND 10 80 | #define TAG_INT_ARRAY 11 81 | 82 | // although tag names are UTF8, we'll just pretend they're ASCII--we don't really care about how the 83 | // actual string data breaks down into characters, as long as we know where the end of the string is 84 | void parseTypeAndName(const uint8_t*& ptr, uint8_t& type, string& name) 85 | { 86 | type = *ptr; 87 | ptr++; 88 | if (type != TAG_END) 89 | { 90 | uint16_t len = fromBigEndian(*((uint16_t*)ptr)); 91 | name.resize(len); 92 | copy(ptr + 2, ptr + 2 + len, name.begin()); 93 | ptr += 2 + len; 94 | } 95 | } 96 | 97 | // structure for locating the block data for a 16x16x16 section--the compound tags on the "Sections" list will pass 98 | // this down to their immediate children, so they can fill in pointers to their payloads if appropriate 99 | // ...after the whole structure is parsed, the block data will be copied into the ChunkData 100 | // (note that we can't read the block data immediately upon finding it, because we have to know the Y value 101 | // for the section first, and the tags may appear in any order) 102 | struct chunkSection 103 | { 104 | int y; // or -1 for "not found yet" 105 | const uint8_t *blockIDs; // pointer into the file buffer, or NULL for "not found yet" 106 | const uint8_t *blockData; // pointer into the file buffer, or NULL for "not found yet" 107 | const uint8_t *blockAdd; // pointer into the file buffer, or NULL for "not found" (this one may not be present at all) 108 | 109 | chunkSection() : y(-1), blockIDs(NULL), blockData(NULL), blockAdd(NULL) {} 110 | bool complete() const {return y >= 0 && y < 16 && blockIDs != NULL && blockData != NULL;} 111 | 112 | void extract(ChunkData& chunkdata) const 113 | { 114 | copy(blockIDs, blockIDs + 4096, chunkdata.blockIDs + (y * 4096)); 115 | copy(blockData, blockData + 2048, chunkdata.blockData + (y * 2048)); 116 | if (blockAdd != NULL) 117 | copy(blockAdd, blockAdd + 2048, chunkdata.blockAdd + (y * 2048)); 118 | } 119 | }; 120 | 121 | bool isSection(const vector& names) 122 | { 123 | return names.size() == 4 && 124 | names[3] == "" && 125 | names[2] == "Sections" && 126 | names[1] == "Level" && 127 | names[0] == ""; 128 | } 129 | 130 | // if section != NULL, then the immediate parent of this tag is one of the compound tags in the "Sections" 131 | // list, so the block data tags will fill in their locations 132 | bool parsePayload(const uint8_t*& ptr, uint8_t type, vector& names, chunkSection *section, vector& completedSections) 133 | { 134 | switch (type) 135 | { 136 | case TAG_END: 137 | { 138 | return true; 139 | } 140 | case TAG_BYTE: 141 | { 142 | if (section != NULL && names.back() == "Y") 143 | section->y = *ptr; 144 | ptr++; 145 | return true; 146 | } 147 | case TAG_SHORT: 148 | { 149 | ptr += 2; 150 | return true; 151 | } 152 | case TAG_INT: 153 | case TAG_FLOAT: 154 | { 155 | ptr += 4; 156 | return true; 157 | } 158 | case TAG_LONG: 159 | case TAG_DOUBLE: 160 | { 161 | ptr += 8; 162 | return true; 163 | } 164 | case TAG_BYTE_ARRAY: 165 | { 166 | uint32_t len = fromBigEndian(*((uint32_t*)ptr)); 167 | ptr += 4; 168 | if (section != NULL) 169 | { 170 | if (names.back() == "Blocks" && len == 4096) 171 | section->blockIDs = ptr; 172 | else if (names.back() == "Data" && len == 2048) 173 | section->blockData = ptr; 174 | else if (names.back() == "Add" && len == 2048) 175 | section->blockAdd = ptr; 176 | } 177 | ptr += len; 178 | return true; 179 | } 180 | case TAG_INT_ARRAY: 181 | { 182 | uint32_t len = fromBigEndian(*((uint32_t*)ptr)); 183 | ptr += 4 + len*4; 184 | return true; 185 | } 186 | case TAG_STRING: 187 | { 188 | uint16_t len = fromBigEndian(*((uint16_t*)ptr)); 189 | ptr += 2 + len; 190 | return true; 191 | } 192 | case TAG_LIST: 193 | { 194 | uint8_t listtype = *ptr; 195 | ptr++; 196 | uint32_t len = fromBigEndian(*((uint32_t*)ptr)); 197 | ptr += 4; 198 | stackPusher sp(names, ""); 199 | for (uint32_t i = 0; i < len; i++) 200 | if (!parsePayload(ptr, listtype, names, NULL, completedSections)) 201 | return false; 202 | return true; 203 | } 204 | case TAG_COMPOUND: 205 | { 206 | chunkSection section; 207 | chunkSection *sectionPtr = isSection(names) ? §ion : NULL; 208 | 209 | uint8_t nexttype; 210 | string nextname; 211 | parseTypeAndName(ptr, nexttype, nextname); 212 | while (nexttype != TAG_END) 213 | { 214 | stackPusher sp(names, nextname); 215 | if (!parsePayload(ptr, nexttype, names, sectionPtr, completedSections)) 216 | return false; 217 | parseTypeAndName(ptr, nexttype, nextname); 218 | } 219 | 220 | if (sectionPtr != NULL) 221 | { 222 | if (section.complete()) 223 | completedSections.push_back(section); 224 | else 225 | { 226 | cerr << "incomplete chunk section!" << endl; 227 | return false; 228 | } 229 | } 230 | 231 | return true; 232 | } 233 | default: 234 | { 235 | // unknown tag--since we have no idea how large it is, we must abort 236 | cerr << "unknown NBT tag: type " << type << endl; 237 | return false; 238 | } 239 | } 240 | return false; // shouldn't be able to reach here 241 | } 242 | 243 | bool ChunkData::loadFromAnvilFile(const vector& filebuf) 244 | { 245 | anvil = true; 246 | fill(blockIDs, blockIDs + 65536, 0); 247 | fill(blockAdd, blockAdd + 32768, 0); 248 | fill(blockData, blockData + 32768, 0); 249 | 250 | const uint8_t *ptr = &(filebuf[0]); 251 | uint8_t type; 252 | string name; 253 | parseTypeAndName(ptr, type, name); 254 | if (type != TAG_COMPOUND || !name.empty()) 255 | { 256 | cerr << "unrecognized NBT chunk file: top tag has type " << (int)type << " and name " << name << endl; 257 | return false; 258 | } 259 | 260 | vector names(1, name); 261 | vector completedSections; 262 | if (!parsePayload(ptr, type, names, NULL, completedSections)) 263 | return false; 264 | 265 | for (vector::const_iterator it = completedSections.begin(); it != completedSections.end(); it++) 266 | it->extract(*this); 267 | 268 | return true; 269 | } 270 | 271 | 272 | //--------------------------------------------------------------------------------------------------- 273 | 274 | 275 | ChunkCacheStats& ChunkCacheStats::operator+=(const ChunkCacheStats& ccs) 276 | { 277 | hits += ccs.hits; 278 | misses += ccs.misses; 279 | read += ccs.read; 280 | skipped += ccs.skipped; 281 | missing += ccs.missing; 282 | reqmissing += ccs.reqmissing; 283 | corrupt += ccs.corrupt; 284 | return *this; 285 | } 286 | 287 | ChunkData* ChunkCache::getData(const PosChunkIdx& ci) 288 | { 289 | int e = getEntryNum(ci); 290 | int state = chunktable.getDiskState(ci); 291 | 292 | if (state == ChunkSet::CHUNK_UNKNOWN) 293 | stats.misses++; 294 | else 295 | stats.hits++; 296 | 297 | // if we've already tried and failed to read the chunk, don't try again 298 | if (state == ChunkSet::CHUNK_CORRUPTED || state == ChunkSet::CHUNK_MISSING) 299 | return &blankdata; 300 | 301 | // if the chunk is in the cache, return it 302 | if (state == ChunkSet::CHUNK_CACHED) 303 | { 304 | if (entries[e].ci != ci) 305 | { 306 | cerr << "grievous chunk cache failure!" << endl; 307 | cerr << "[" << ci.x << "," << ci.z << "] [" << entries[e].ci.x << "," << entries[e].ci.z << "]" << endl; 308 | exit(-1); 309 | } 310 | return &entries[e].data; 311 | } 312 | 313 | // if this is a full render and the chunk is not required, we already know it doesn't exist 314 | bool req = chunktable.isRequired(ci); 315 | if (fullrender && !req) 316 | { 317 | stats.skipped++; 318 | chunktable.setDiskState(ci, ChunkSet::CHUNK_MISSING); 319 | return &blankdata; 320 | } 321 | 322 | // okay, we actually have to read the chunk from disk 323 | if (regionformat) 324 | readFromRegionCache(ci); 325 | else 326 | readChunkFile(ci); 327 | 328 | // check whether the read succeeded; return the data if so 329 | state = chunktable.getDiskState(ci); 330 | if (state == ChunkSet::CHUNK_CORRUPTED) 331 | { 332 | stats.corrupt++; 333 | return &blankdata; 334 | } 335 | if (state == ChunkSet::CHUNK_MISSING) 336 | { 337 | if (req) 338 | stats.reqmissing++; 339 | else 340 | stats.missing++; 341 | return &blankdata; 342 | } 343 | if (state != ChunkSet::CHUNK_CACHED || entries[e].ci != ci) 344 | { 345 | cerr << "grievous chunk cache failure!" << endl; 346 | cerr << "[" << ci.x << "," << ci.z << "] [" << entries[e].ci.x << "," << entries[e].ci.z << "]" << endl; 347 | exit(-1); 348 | } 349 | stats.read++; 350 | return &entries[e].data; 351 | } 352 | 353 | void ChunkCache::readChunkFile(const PosChunkIdx& ci) 354 | { 355 | // read the gzip file from disk, if it's there 356 | string filename = inputpath + "/" + ci.toChunkIdx().toFilePath(); 357 | int result = readGzFile(filename, readbuf); 358 | if (result == -1) 359 | { 360 | chunktable.setDiskState(ci, ChunkSet::CHUNK_MISSING); 361 | return; 362 | } 363 | if (result == -2) 364 | { 365 | chunktable.setDiskState(ci, ChunkSet::CHUNK_CORRUPTED); 366 | return; 367 | } 368 | 369 | // gzip read was successful; extract the data we need from the chunk 370 | // and put it in the cache 371 | parseReadBuf(ci, false); 372 | } 373 | 374 | void ChunkCache::readFromRegionCache(const PosChunkIdx& ci) 375 | { 376 | // try to decompress the chunk data 377 | bool anvil; 378 | int result = regioncache.getDecompressedChunk(ci, readbuf, anvil); 379 | if (result == -1) 380 | { 381 | chunktable.setDiskState(ci, ChunkSet::CHUNK_MISSING); 382 | return; 383 | } 384 | if (result == -2) 385 | { 386 | chunktable.setDiskState(ci, ChunkSet::CHUNK_CORRUPTED); 387 | return; 388 | } 389 | 390 | // decompression was successful; extract the data we need from the chunk 391 | // and put it in the cache 392 | parseReadBuf(ci, anvil); 393 | } 394 | 395 | void ChunkCache::parseReadBuf(const PosChunkIdx& ci, bool anvil) 396 | { 397 | // evict current tenant of chunk's cache slot 398 | int e = getEntryNum(ci); 399 | if (entries[e].ci.valid()) 400 | chunktable.setDiskState(entries[e].ci, ChunkSet::CHUNK_UNKNOWN); 401 | entries[e].ci = PosChunkIdx(-1,-1); 402 | // ...and put this chunk's data into the slot, assuming the data can actually be parsed 403 | bool result = anvil ? entries[e].data.loadFromAnvilFile(readbuf) : entries[e].data.loadFromOldFile(readbuf); 404 | if (result) 405 | { 406 | entries[e].ci = ci; 407 | chunktable.setDiskState(ci, ChunkSet::CHUNK_CACHED); 408 | } 409 | else 410 | chunktable.setDiskState(ci, ChunkSet::CHUNK_CORRUPTED); 411 | } 412 | -------------------------------------------------------------------------------- /utils.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2010, 2011 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "utils.h" 29 | 30 | #if USE_MALLINFO 31 | #include 32 | #endif 33 | 34 | using namespace std; 35 | 36 | 37 | //!!!!!!! do this more carefully, like actually checking return values, etc. 38 | void makePath(const string& path) 39 | { 40 | if (path.empty()) 41 | return; 42 | string::size_type pos = path.find('/'); 43 | mkdir(path.substr(0, pos).c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 44 | while (pos != string::npos) 45 | { 46 | pos = path.find('/', pos+1); 47 | mkdir(path.substr(0, pos).c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); 48 | } 49 | } 50 | 51 | //!!!!!! same here 52 | void renameFile(const string& oldpath, const string& newpath) 53 | { 54 | if (oldpath.empty() || newpath.empty()) 55 | return; 56 | rename(oldpath.c_str(), newpath.c_str()); 57 | } 58 | 59 | void copyFile(const string& oldpath, const string& newpath) 60 | { 61 | ifstream infile(oldpath.c_str()); 62 | ofstream outfile(newpath.c_str()); 63 | outfile << infile.rdbuf(); 64 | } 65 | 66 | bool readLines(const string& filename, vector& lines) 67 | { 68 | ifstream infile(filename.c_str()); 69 | if (infile.fail()) 70 | return false; 71 | string line; 72 | while (!getline(infile, line).eof()) 73 | lines.push_back(line); 74 | return true; 75 | } 76 | 77 | 78 | void listEntries(const string& dirpath, vector& entries) 79 | { 80 | DIR *dir = opendir(dirpath.c_str()); 81 | if (dir == NULL) 82 | return; 83 | dirent *de = readdir(dir); 84 | while (de != NULL) 85 | { 86 | string e(de->d_name); 87 | if (e != "." && e != "..") 88 | entries.push_back(dirpath + "/" + e); 89 | de = readdir(dir); 90 | } 91 | closedir(dir); 92 | } 93 | 94 | bool dirExists(const string& dirpath) 95 | { 96 | DIR *dir = opendir(dirpath.c_str()); 97 | if (dir == NULL) 98 | return false; 99 | closedir(dir); 100 | return true; 101 | } 102 | 103 | uint64_t getHeapUsage() 104 | { 105 | #if USE_MALLINFO 106 | struct mallinfo minfo = mallinfo(); 107 | return minfo.uordblks + minfo.hblkhd; 108 | #else 109 | return 0; 110 | #endif 111 | } 112 | 113 | 114 | struct gzCloser 115 | { 116 | gzFile gzfile; 117 | gzCloser(gzFile gzf) : gzfile(gzf) {} 118 | ~gzCloser() {gzclose(gzfile);} 119 | }; 120 | 121 | int readGzFile(const string& filename, vector& data) 122 | { 123 | gzFile gzf = gzopen(filename.c_str(), "rb"); 124 | if (gzf == NULL) 125 | { 126 | if (errno == ENOENT) 127 | return -1; 128 | return -2; 129 | } 130 | gzCloser gc(gzf); 131 | // start by resizing vector to entire capacity; we'll shrink back down to the 132 | // proper size later 133 | data.resize(data.capacity()); 134 | if (data.empty()) 135 | { 136 | data.resize(131072); 137 | data.resize(data.capacity()); // just in case extra space was allocated 138 | } 139 | // read as much as we can 140 | vector::size_type pos = 0; 141 | unsigned requestSize = data.size() - pos; // this is plain old unsigned to match the zlib call 142 | int bytesRead = gzread(gzf, &data[pos], requestSize); 143 | if (bytesRead == -1) 144 | return -2; 145 | pos += bytesRead; 146 | while (bytesRead == requestSize) 147 | { 148 | // if there's still more, reallocate and read more 149 | data.resize(data.size() * 2); 150 | data.resize(data.capacity()); // just in case extra space was allocated 151 | requestSize = data.size() - pos; 152 | bytesRead = gzread(gzf, &data[pos], requestSize); 153 | if (bytesRead == -1) 154 | return -2; 155 | pos += bytesRead; 156 | } 157 | // resize buffer back down to the end of the actual data 158 | data.resize(pos); 159 | return 0; 160 | } 161 | 162 | struct inflateEnder 163 | { 164 | z_stream *zstr; 165 | inflateEnder(z_stream *zs) : zstr(zs) {} 166 | ~inflateEnder() {inflateEnd(zstr);} 167 | }; 168 | 169 | bool readGzOrZlib(uint8_t *inbuf, size_t size, vector& data) 170 | { 171 | // start by resizing vector to entire capacity; we'll shrink back down to the 172 | // proper size later 173 | data.resize(data.capacity()); 174 | if (data.empty()) 175 | { 176 | data.resize(131072); 177 | data.resize(data.capacity()); // just in case extra space was allocated 178 | } 179 | // initialize zlib stream 180 | z_stream zstr; 181 | zstr.next_in = inbuf; 182 | zstr.avail_in = size; 183 | zstr.next_out = &(data[0]); 184 | zstr.avail_out = data.size(); 185 | zstr.zalloc = Z_NULL; 186 | zstr.zfree = Z_NULL; 187 | int result = inflateInit2(&zstr, 15 + 32); // adding 32 to window size means "detect both gzip and zlib" 188 | if (result != Z_OK) 189 | return false; 190 | inflateEnder ie(&zstr); 191 | // read as much as we can 192 | result = inflate(&zstr, Z_SYNC_FLUSH); 193 | while (result != Z_STREAM_END) 194 | { 195 | // if we failed for some reason other than not having enough room to read into, abort 196 | if (result != Z_OK) 197 | return false; 198 | // reallocate and read more 199 | ptrdiff_t diff = zstr.next_out - &(data[0]); 200 | size_t addedsize = data.size(); 201 | data.resize(data.size() + addedsize); 202 | data.resize(data.capacity()); // just in case more was allocated 203 | zstr.next_out = &(data[0]) + diff; 204 | zstr.avail_out += addedsize; 205 | result = inflate(&zstr, Z_SYNC_FLUSH); 206 | } 207 | // resize buffer back down to end of the actual data 208 | data.resize(zstr.total_out); 209 | return true; 210 | } 211 | 212 | 213 | 214 | uint32_t fromBigEndian(uint32_t i) 215 | { 216 | uint8_t *b = (uint8_t*)(&i); 217 | return (*b << 24) | (*(b+1) << 16) | (*(b+2) << 8) | (*(b+3)); 218 | } 219 | 220 | uint16_t fromBigEndian(uint16_t i) 221 | { 222 | uint8_t *b = (uint8_t*)(&i); 223 | return (*b << 8) | (*(b+1)); 224 | } 225 | 226 | bool isBigEndian() 227 | { 228 | uint32_t i = 0xff000000; 229 | uint8_t *b = (uint8_t*)(&i); 230 | return *b == 0xff; 231 | } 232 | 233 | void swapEndian(uint32_t& i) 234 | { 235 | uint8_t *b = (uint8_t*)(&i); 236 | swap(b[0], b[3]); 237 | swap(b[1], b[2]); 238 | } 239 | 240 | 241 | 242 | int64_t floordiv(int64_t a, int64_t b) 243 | { 244 | if (b < 0) 245 | { 246 | a = -a; 247 | b = -b; 248 | } 249 | if (a < 0) 250 | return (a - b + 1) / b; 251 | return a / b; 252 | } 253 | 254 | int64_t ceildiv(int64_t a, int64_t b) 255 | { 256 | if (b < 0) 257 | { 258 | a = -a; 259 | b = -b; 260 | } 261 | if (a > 0) 262 | return (a + b - 1) / b; 263 | return a / b; 264 | } 265 | 266 | int64_t mod64pos(int64_t a) 267 | { 268 | if (a >= 0) 269 | return a % 64; 270 | int64_t m = a % 64; 271 | return (m == 0) ? 0 : (64 + m); 272 | } 273 | 274 | int64_t interpolate(int64_t i, int64_t destrange, int64_t srcrange) 275 | { 276 | double f = (double)i / (double)(destrange - 1); 277 | f = f * (double)(srcrange - 1); 278 | int64_t j = (int64_t)f; 279 | return (f - (double)j >= 0.5) ? j+1 : j; 280 | } 281 | 282 | 283 | 284 | // technically, these use "upside-down-N-order", not Z-order--that is, the Y-coord is incremented 285 | // first, not the X-coord--because that way, no special way to detect the end of the array is 286 | // needed; advancing past the final valid element leads to the index one past the end of the 287 | // array, as usual 288 | 289 | uint32_t toZOrder(uint32_t i, const uint32_t SIZE) 290 | { 291 | // get x and y coords 292 | uint32_t x = i % SIZE, y = i / SIZE; 293 | // interleave bits; this (public domain) code taken from Sean Eron Anderson's website 294 | // ...this assumes that x and y are <= 0xffff; this is safe because if they weren't, 295 | // SIZE would have to be > 0x10000, so 32 bits wouldn't have been enough to hold an 296 | // index into a SIZExSIZE array 297 | x = (x | (x << 8)) & 0xff00ff; 298 | x = (x | (x << 4)) & 0xf0f0f0f; 299 | x = (x | (x << 2)) & 0x33333333; 300 | x = (x | (x << 1)) & 0x55555555; 301 | y = (y | (y << 8)) & 0xff00ff; 302 | y = (y | (y << 4)) & 0xf0f0f0f; 303 | y = (y | (y << 2)) & 0x33333333; 304 | y = (y | (y << 1)) & 0x55555555; 305 | return (x << 1) | y; 306 | } 307 | 308 | uint32_t fromZOrder(uint32_t i, const uint32_t SIZE) 309 | { 310 | // de-interleave 311 | uint32_t x = (i >> 1) & 0x55555555; 312 | x = (x | (x >> 1)) & 0x33333333; 313 | x = (x | (x >> 2)) & 0xf0f0f0f; 314 | x = (x | (x >> 4)) & 0xff00ff; 315 | x = (x | (x >> 8)) & 0xffff; 316 | uint32_t y = i & 0x55555555; 317 | y = (y | (y >> 1)) & 0x33333333; 318 | y = (y | (y >> 2)) & 0xf0f0f0f; 319 | y = (y | (y >> 4)) & 0xff00ff; 320 | y = (y | (y >> 8)) & 0xffff; 321 | // convert to row-major 322 | return y*SIZE + x; 323 | 324 | } 325 | 326 | 327 | 328 | bool fromBase36(const string& s, string::size_type pos, string::size_type n, int64_t& result) 329 | { 330 | if (s.empty()) 331 | return false; 332 | if (n == string::npos) 333 | n = s.size(); 334 | string::size_type i = pos; 335 | int64_t sign = 1; 336 | if (s[i] == '-') 337 | { 338 | sign = -1; 339 | i++; 340 | } 341 | int64_t total = 0; 342 | while (i != pos + n) 343 | { 344 | total *= 36; 345 | if (s[i] >= '0' && s[i] <= '9') 346 | total += s[i] - '0'; 347 | else if (s[i] >= 'a' && s[i] <= 'z') 348 | total += s[i] - 'a' + 10; 349 | else if (s[i] >= 'A' && s[i] <= 'Z') 350 | total += s[i] - 'A' + 10; 351 | else 352 | return false; 353 | i++; 354 | } 355 | result = total * sign; 356 | return true; 357 | } 358 | 359 | int64_t fromBase36(const string& s) 360 | { 361 | int64_t result; 362 | if (fromBase36(s, 0, string::npos, result)) 363 | return result; 364 | return 0; 365 | } 366 | 367 | string toBase36(int64_t i) 368 | { 369 | bool neg = false; 370 | if (i < 0) 371 | { 372 | neg = true; 373 | i = -i; 374 | } 375 | string s; 376 | while (i > 0) 377 | { 378 | int64_t d = i % 36; 379 | if (d < 10) 380 | s += ('0' + d); 381 | else 382 | s += ('a' + d - 10); 383 | i /= 36; 384 | } 385 | if (s.empty()) 386 | return "0"; 387 | if (neg) 388 | s += '-'; 389 | reverse(s.begin(), s.end()); 390 | return s; 391 | } 392 | 393 | 394 | string tostring(int i) 395 | { 396 | ostringstream ss; 397 | ss << i; 398 | return ss.str(); 399 | } 400 | 401 | string tostring(int64_t i) 402 | { 403 | ostringstream ss; 404 | ss << i; 405 | return ss.str(); 406 | } 407 | 408 | bool fromstring(const string& s, int64_t& result) 409 | { 410 | istringstream ss(s); 411 | ss >> result; 412 | return !ss.fail(); 413 | } 414 | 415 | bool fromstring(const string& s, int& result) 416 | { 417 | istringstream ss(s); 418 | ss >> result; 419 | return !ss.fail(); 420 | } 421 | 422 | bool replace(string& text, const string& oldstr, const string& newstr) 423 | { 424 | string::size_type pos = text.find(oldstr); 425 | if (pos == string::npos) 426 | return false; 427 | while (pos != string::npos) 428 | { 429 | text.replace(pos, oldstr.size(), newstr); 430 | pos = text.find(oldstr, pos + 1); 431 | } 432 | return true; 433 | } 434 | 435 | vector tokenize(const string& instr, char separator) 436 | { 437 | vector tokens; 438 | istringstream stream(instr); 439 | string token; 440 | while (getline(stream, token, separator)) 441 | tokens.push_back(token); 442 | return tokens; 443 | } 444 | 445 | 446 | 447 | pair schedule(const vector& costs, vector& assignments, int threads) 448 | { 449 | // simple scheduler: go through the costs in descending order, assigning 450 | // each to the thread with the lowest cost so far 451 | 452 | // sort costs 453 | vector > sortedcosts; // first is cost, second is index in original vector 454 | for (int i = 0; i < costs.size(); i++) 455 | sortedcosts.push_back(make_pair(costs[i], i)); 456 | sort(sortedcosts.begin(), sortedcosts.end(), greater >()); 457 | 458 | vector totals(threads, 0); 459 | assignments.resize(costs.size(), -1); 460 | 461 | // go through sorted costs 462 | int next = 0; 463 | for (vector >::const_iterator it = sortedcosts.begin(); it != sortedcosts.end(); it++) 464 | { 465 | // assign to waiting (min-cost) thread 466 | assignments[it->second] = next; 467 | totals[next] += it->first; 468 | // find the new min-cost thread 469 | for (int i = 0; i < threads; i++) 470 | if (totals[i] < totals[next]) 471 | next = i; 472 | } 473 | 474 | // compute error fraction 475 | int mintotal = totals[0], maxtotal = totals[0]; 476 | for (int i = 1; i < threads; i++) 477 | { 478 | if (totals[i] < mintotal) 479 | mintotal = totals[i]; 480 | else if (totals[i] > maxtotal) 481 | maxtotal = totals[i]; 482 | } 483 | return make_pair(maxtotal - mintotal, (double)(maxtotal - mintotal) / (double)maxtotal); 484 | } 485 | 486 | 487 | 488 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | pigmap is a Minecraft map renderer, designed to be fast enough for very large worlds (like 2 | the Aporkalypse Now server, hence the name). It was inspired by Minecraft Overviewer and does 3 | essentially the same thing, just faster and with fewer fancy options. Features include: 4 | 5 | -isometric projection; level of detail on max zoom is user-settable 6 | -Google Maps tile output 7 | -custom tilesets (supply your own terrain.png) 8 | -ability to manually edit the isometric block images generated from terrain.png, 9 | for further beautification 10 | -incremental map updates, but you must supply your own list of modified chunks (most 11 | likely via rsync or something similar) 12 | -multithreading 13 | -support for Alpha (chunk-based), Beta (.mcr), or Anvil (.mca) world formats 14 | -ability to restrict rendering to a certain range of Y-values (heights) 15 | 16 | pigmap is free under the GPL (please see the file COPYING). I probably won't add too many 17 | more features beyond whatever the Aporkalypse might need in the future, but hopefully it will 18 | be useful to someone. 19 | 20 | Michael J. Nelson, mike@thinkingpart.com ("equalpants" in Minecraft) 21 | 22 | The files template.html and style.css contain code from Minecraft Overviewer (copyright 23 | Andrew Brown and contributors). 24 | 25 | The official pigmap repository is at: 26 | https://github.com/equalpants/pigmap 27 | 28 | --------------------------------------------------------------------------------------------------- 29 | 30 | Supported platforms are Linux and cygwin. OS X should also be fine, but I haven't tried it 31 | myself. (I've heard reports of it working, though.) 32 | 33 | Dependencies are zlib, libpng, pthreads, and some very basic system stuff: getopt, mkdir, 34 | dirent.h. If your platform has a separate development package for libpng (called libpng-dev, 35 | libpng-devel, or something like that), you'll need that one, too. 36 | 37 | Use supplied makefile to build with g++. 38 | 39 | --------------------------------------------------------------------------------------------------- 40 | 41 | Change log (important stuff only): 42 | 43 | 1.2 44 | -new blocks up to Minecraft 1.3 45 | -chest.png, etc. now required 46 | -some speed improvements 47 | -block IDs >= 256 (from mods) now ignored rather than drawn as random blocks 48 | -memory requirements increased--should now be just a little lower than pre-Anvil 49 | versions (roughly ~250-300 MB per thread) 50 | 51 | 1.1.2 52 | -added inverted slabs 53 | 54 | 1.1.1 55 | -fixed a stupid bug preventing incremental updates from working 56 | 57 | 1.1 58 | -Anvil format support 59 | -added -y and -Y params to specify minimum/maximum Y-coords for rendering 60 | -memory requirements are about 15-20% lower due to better caching 61 | -new blocks up to Minecraft 1.2.4: circle stone brick, new sandstone types, redstone lamps, 62 | new plank colors, upside-down stairs 63 | -some display fixes for older blocks whose behavior has changed: fences, doors 64 | 65 | 1.0.1 66 | -new blocks: jungle logs/leaves/saplings 67 | 68 | 1.0 69 | -NOTE: users of jcornwellshiel's fork will need to regenerate their blocks-B.png 70 | -new blocks all the way up through Minecraft 1.0 71 | -endportal.png added; must be placed in images directory alongside terrain.png 72 | -fixed some ugliness in the rendering of cross-shaped item blocks (saplings, etc.) 73 | -improved rendering of large block sizes (B > 8) 74 | 75 | 0.7.4 76 | -new blocks for Minecraft Beta 1.6: tall grass, ferns, dead shrubs, trapdoors 77 | 78 | 0.7.3 79 | -new blocks for Minecraft Beta 1.4 and 1.5: locked chests, boosters, detectors, 80 | pine/birch saplings, webs 81 | 82 | 0.7.2 83 | -support for big-endian platforms (like PowerPC) 84 | 85 | 0.7.1 86 | -birch and pine leaves 87 | -more error/logging messages 88 | 89 | 0.7 90 | -Minecraft Beta 1.3 support: new region file format, beds, repeaters, slabs 91 | 92 | 0.6.1 93 | -support for HD texture packs 94 | 95 | 0.6 96 | -new block types for Minecraft Beta 1.2: lapis, cake, etc. 97 | -remaining undrawn blocks from previous versions now drawn: fire, buttons, levers, ascending 98 | cart tracks 99 | -fire.png added; must be placed in images directory alongside terrain.png 100 | -some block image offsets changed; see note below if you have manually edited your blocks-B.png 101 | -some drawing improvements; may want to redo all block images from terrain.png 102 | 103 | 0.5.1 104 | -speed improvements 105 | -previously, we would abort if the world was too absurdly large to deal with--i.e. if there were 106 | some chunks whose coordinates were well into the billions--but apparently there is a Minecraft 107 | bug that can create such chunks way off in space, even if the world is normally-sized, so now 108 | we just ignore any chunks that are too far out to handle 109 | 110 | 0.5 111 | -initial version 112 | 113 | --------------------------------------------------------------------------------------------------- 114 | 115 | Usage examples: 116 | 117 | full render: 118 | 119 | pigmap -B 6 -T 1 -Z 10 -i input/World1 -o output/World1 -g images -h 3 120 | 121 | ...builds a map with parameters B = 6, T = 1, baseZoom = 10, reading world data from the path 122 | "input/World1", writing tiles out to the path "output/World1", reading terrain images from the path 123 | "images", and using 3 threads. 124 | 125 | incremental update: 126 | 127 | pigmap -i input/World1 -o output/World1 -r regionlist -h 3 -x 128 | 129 | ...updates an existing map by redrawing any tiles touched by regions listed in the file "regionlist", 130 | with the input and output dirs as before. Terrain images are read from the path ".", and 3 threads 131 | are used. Map parameters are read from the existing map, and if the existing baseZoom is too small, 132 | it will be incremented. 133 | 134 | --------------------------------------------------------------------------------------------------- 135 | 136 | Error messages are written to stderr; normal output to stdout. There isn't much (read: any) of a 137 | progress indicator at the moment, but there are some statistics upon completion. 138 | 139 | --------------------------------------------------------------------------------------------------- 140 | 141 | Explanation of command-line options: 142 | 143 | 144 | 1. Params for both full renders and incremental updates: 145 | 146 | a. input path, output path (-i, -o) 147 | 148 | The input path should be the top directory of the world data (i.e. where level.dat is). The tile 149 | output path should be the top of the Google Maps directory structure (i.e. where base.png is). Tile 150 | images will be written to the output path, along with a file "pigmap.params" which remembers what 151 | parameters the map was drawn with. For incremental updates, the output path must exist already, and 152 | must contain the pigmap.params file. 153 | 154 | Three world formats are supported: the current Anvil format (with .mca region files), the .mcr 155 | region format that preceded it, and the even older chunk-based format. If the input path contains 156 | more than one format, then only the newer format will be used. 157 | 158 | b. [optional (kind of)] HTML source path (-m) 159 | 160 | This is where pigmap expects to find the files "template.html" and "style.css", which it will 161 | use to construct a bare-bones HTML page for viewing the map. These files are not optional, but 162 | the -m parameter itself may be omitted, in which case "." is used as the HTML source path. 163 | 164 | The map-viewing page is called "pigmap-default.html" and is written to the output path. It doesn't have 165 | any fancy capabilities (just plain old markers); it's meant only to demonstrate the JavaScript for 166 | converting Minecraft coords to Google Maps latitude/longitude. This file is rewritten with every 167 | incremental update; make a copy if you need to add fancier abilities to it. 168 | 169 | c. [optional (kind of)] image path (-g) 170 | 171 | This is where pigmap expects to find either a copy of your tileset (terrain.png, etc.), or a copy of 172 | blocks-B.png (substituting the actual numeric value of B; see below for definition of B), a 173 | pigmap-generated file that contains the isometric renderings of each block. These files are not 174 | optional, but the -g parameter itself may be omitted, in which case "." is used as the image path. 175 | 176 | Each blocks-B.png comes with a corresponding blocks-B.version, which remembers how many block images 177 | are stored in blocks-B.png. (As more blocks are added to the game, the version will change.) 178 | 179 | The selection of block images works thusly: 180 | 181 | -If blocks-B.png does not exist, then the tileset you provide will be used to create it. 182 | -If blocks-B.png exists but has an old version, your tileset will be used to fill in any missing 183 | (i.e. new) blocks, but the existing portions of blocks-B.png will be preserved. 184 | -If blocks-B.png exists and is up-to-date, its block images will be used. 185 | 186 | This means that pigmap will need your tileset the first time it runs, so it can generate blocks-B.png; 187 | subsequent runs will use the existing blocks-B.png, which can be manually edited if insufficiently 188 | pretty, or for special effects, etc. 189 | 190 | The following images are required to generate blocks-B.png: 191 | -terrain.png, chest.png, largechest.png, enderchest.png: these files come from your tileset 192 | or minecraft.jar 193 | -fire.png, endportal.png: these files are included with pigmap 194 | All of these must be RGBA png files (not just RGB). 195 | 196 | High-res tilesets are supported. The textures in terrain.png, fire.png, and endportal.png can be 197 | any size, as long as they remain square. The textures in the chest pngs can be scaled up, but their 198 | size must be an integer multiple of the original size. 199 | 200 | d. [optional] number of threads (-h) 201 | 202 | Defaults to 1. Each thread requires around 250-300 MB of RAM (they work in different areas of the 203 | map and keep separate caches of chunk data). Returns from extra threads may diminish quickly as the 204 | disk becomes a bottleneck. 205 | 206 | 207 | 2. Params for full renders only: 208 | 209 | a. map parameters B, T, [optional] baseZoom (-B, -T, -Z): 210 | 211 | B is an integer >= 2 which controls the size (in pixels) of the blocks in the base zoom level. The 212 | map projection lays out blocks on a hexagonal grid generated from the distances [+/-2B, +/-B] and 213 | [0, +/-2B], and each individual block's bounding box is 4B x 4B. 214 | 215 | T is the tile multiplier, an integer >= 1 which controls how many chunks wide a tile is. 216 | 217 | The size of each generated tile is 64BT x 64BT. 218 | 219 | (To match Minecraft Overviewer's highest zoom level, use B=6 and T=1, for a tile size of 384 x 384. 220 | To match its second-highest zoom, use B=3 and T=2.) 221 | 222 | baseZoom is the Google Maps zoom level that the base tiles should be placed on. This is important 223 | because Google Maps allows only 2^Z by 2^Z tiles for each zoom level Z; if baseZoom is not high enough, 224 | some tiles will not fit on the Google Map. (Out-of-bounds tiles will be noticed at the start of a 225 | render, and the render aborted.) 226 | 227 | If baseZoom is omitted, it will be set to the lowest value that can fit all the required tiles. 228 | 229 | b. [optional] minimum/maximum Y-coords (-y, -Y) 230 | 231 | By default, pigmap will draw all blocks present in the world data, but these parameters can be 232 | used to restrict rendering to blocks within a particular height range. -y is the minimum Y-coord 233 | which will be drawn, and -Y is the maximum. 234 | 235 | Note that these values, if used, will persist through incremental updates of the map (like B and T 236 | do), even if the actual height limit in the game changes in the future. So if you want the map to 237 | always render all the way to the top, then *don't use* -Y, as opposed to using it but passing in 238 | the *current* height limit. 239 | 240 | 241 | 3. Params for incremental updates only: 242 | 243 | a. regionlist file (-r) or chunklist file (-c) 244 | 245 | The name of a text file containing the filenames of regions or chunks that should be updated, one per 246 | line. Supplied filenames can have a relative or absolute path or can just be the file name; only the 247 | name will be examined. 248 | 249 | If the input world is in region format (either .mca or .mcr regions), then a regionlist must be used, 250 | not a chunklist. However, the extensions on the filenames in a regionlist do not matter--only the 251 | coordinates from the filenames will be considered, and pigmap will always read the newest available 252 | region. 253 | 254 | The current format of the input world does *not* have to match the format used in the previous map 255 | render--an incremental update will work fine even if the world data has been converted since the 256 | last render. (Of course it would be trickier to get a meaningful regionlist in such a case--to 257 | distinguish the regions which have actually changed from those which have merely been converted-- 258 | but if you have such a list, then pigmap can use it.) 259 | 260 | b. [optional] expand map if necessary (-x) 261 | 262 | This is useful for frequently-updated maps: eventually, as the world expands outwards, it will become 263 | too large for the current baseZoom, and an incremental update will fail. If -x has been passed, then 264 | if an incremental update fails due to out-of-bounds tiles, the map's baseZoom will be increased by 265 | 1, and the update retried. (The baseZoom increase is *not* undone if the second attempt also fails.) 266 | 267 | Note that increasing a map's baseZoom is quick: all the tiles are simply moved one level deeper in 268 | the hierarchy, and the top two zoom levels redrawn. 269 | 270 | --------------------------------------------------------------------------------------------------- 271 | 272 | What happens in a full render: the world data is scanned, and every chunk that exists on disk is noted. 273 | All tiles that include any chunks are rendered and saved; any tiles that might exist already in the 274 | output path are overwritten. 275 | 276 | What happens in an incremental update: the chunklist is read and its chunks are considered "required"; 277 | the world data is not scanned. (However, the input path must still contain the non-required chunks; 278 | that is, you can't just use a partial copy of a world. This is because each tile touches multiple 279 | chunks; to render a required chunk, data from its non-required neighbors may be necessary.) All tiles 280 | that include required chunks are rendered and saved. Base tiles that exist already in the output path 281 | are overwritten, but those at lower zoom levels are merely modified--only the changed portions 282 | are redrawn. 283 | 284 | --------------------------------------------------------------------------------------------------- 285 | 286 | Special note for those who choose to manually edit blocks-B.png: 287 | 288 | When new block types are introduced, their block images are added on to the end of blocks-B.png, 289 | and the existing contents are preserved, so you don't need to worry about previous modifications 290 | getting destroyed. However, sometimes it may be necessary to remove existing block images and 291 | replace them with new versions; when this happens, the old block image offset will no longer be 292 | used, and a new block image will be added to the end of the file. 293 | 294 | Example: prior to pigmap 0.6, the furnace block images were located at offsets 68, 69, and 149-152. 295 | Starting with 0.6, they are instead located at 183-188. (This is because the furnace graphics 296 | were changed in Minecraft Beta 1.2.) So if you modified the furnace block images while using a 297 | pre-0.6 version of pigmap, your modified images can still be found at their old locations in your 298 | blocks-B.png, but they must be copied to the new locations. 299 | 300 | Complete list of block image moves: 301 | 302 | --- pigmap 1.2 --- 303 | gravel: 21->483 304 | chests: 54->484, 177->485, 297->486, [173-176]->[487,490], [298-301]->[491-494] 305 | 306 | --- pigmap 1.0 --- 307 | beds: 232->281, 233->282, 234->283, 235->284, 236->285, 237->286, 238->287, 239->288 308 | cake: 228->289 309 | 310 | --- pigmap 0.6 --- 311 | furnaces: 68->183, 69->186, 149->184, 150->185, 151->187, 152->188 312 | fire: 48->189 313 | buttons: 123->190, 124->191, 125->192, 126->193 314 | levers: 104->194, 105->195, 106->196, 107->197, 108->198, 109->199 315 | ascending tracks: 88->200, 89->201, 90->202, 91->203 -------------------------------------------------------------------------------- /world.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2011 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #include 19 | #include 20 | #include 21 | 22 | #include "world.h" 23 | #include "region.h" 24 | 25 | using namespace std; 26 | 27 | 28 | 29 | 30 | bool detectRegionFormat(const string& inputdir) 31 | { 32 | return dirExists(inputdir + "/region"); 33 | } 34 | 35 | 36 | 37 | 38 | 39 | bool makeAllRegionsRequired(const string& topdir, ChunkTable& chunktable, TileTable& tiletable, RegionTable& regiontable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount, int64_t& reqregioncount) 40 | { 41 | bool findBaseZoom = mp.baseZoom == -1; 42 | // if finding the baseZoom, we'll just start from 0 and increase it whenever we hit a tile that's out of bounds 43 | if (findBaseZoom) 44 | mp.baseZoom = 0; 45 | reqregioncount = 0; 46 | // get all files in the region directory 47 | RegionFileReader rfreader; 48 | vector regionpaths; 49 | listEntries(topdir + "/region", regionpaths); 50 | for (vector::const_iterator it = regionpaths.begin(); it != regionpaths.end(); it++) 51 | { 52 | RegionIdx ri(0,0); 53 | // if this is a proper region filename, use it 54 | if (RegionIdx::fromFilePath(*it, ri)) 55 | { 56 | PosRegionIdx pri(ri); 57 | if (!pri.valid()) 58 | { 59 | cerr << "ignoring extremely-distant region " << *it << " (world may be corrupt)" << endl; 60 | continue; 61 | } 62 | // we might have found this region already, if the world data contains both .mca and .mcr files 63 | if (regiontable.isRequired(pri)) 64 | continue; 65 | // get the chunks that currently exist in this region; if there aren't any, ignore it 66 | vector chunks; 67 | if (0 != rfreader.getContainedChunks(ri, string84(topdir), chunks)) 68 | { 69 | cerr << "can't open region " << *it << " to list chunks" << endl; 70 | continue; 71 | } 72 | if (chunks.empty()) 73 | continue; 74 | // mark the region required 75 | regiontable.setRequired(pri); 76 | reqregioncount++; 77 | // go through the contained chunks 78 | for (vector::const_iterator chunk = chunks.begin(); chunk != chunks.end(); chunk++) 79 | { 80 | // mark the chunk required 81 | PosChunkIdx pci(*chunk); 82 | if (pci.valid()) 83 | { 84 | chunktable.setRequired(pci); 85 | reqchunkcount++; 86 | } 87 | else 88 | { 89 | cerr << "ignoring extremely-distant chunk " << chunk->toFileName() << " (world may be corrupt)" << endl; 90 | continue; 91 | } 92 | // get the tiles it touches and mark them required 93 | vector tiles = chunk->getTiles(mp); 94 | for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) 95 | { 96 | // first check if this tile fits in the TileTable, whose size is fixed 97 | PosTileIdx pti(*tile); 98 | if (pti.valid()) 99 | tiletable.setRequired(pti); 100 | else 101 | { 102 | cerr << "ignoring extremely-distant tile [" << tile->x << "," << tile->y << "]" << endl; 103 | cerr << "(world may be corrupt; is region " << *it << " supposed to exist?)" << endl; 104 | continue; 105 | } 106 | // now see if the tile fits on the Google map 107 | if (!tile->valid(mp)) 108 | { 109 | // if we're supposed to be finding baseZoom, then bump it up until this tile fits 110 | if (findBaseZoom) 111 | { 112 | while (!tile->valid(mp)) 113 | mp.baseZoom++; 114 | } 115 | // otherwise, abort 116 | else 117 | { 118 | cerr << "baseZoom too small! can't fit tile [" << tile->x << "," << tile->y << "]" << endl; 119 | return false; 120 | } 121 | } 122 | } 123 | } 124 | } 125 | } 126 | reqtilecount = tiletable.reqcount; 127 | if (findBaseZoom) 128 | cout << "baseZoom set to " << mp.baseZoom << endl; 129 | return true; 130 | } 131 | 132 | int readRegionlist(const string& regionlist, const string& inputdir, ChunkTable& chunktable, TileTable& tiletable, RegionTable& regiontable, const MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount, int64_t& reqregioncount) 133 | { 134 | ifstream infile(regionlist.c_str()); 135 | if (infile.fail()) 136 | { 137 | cerr << "couldn't open regionlist " << regionlist << endl; 138 | return -2; 139 | } 140 | reqregioncount = 0; 141 | RegionFileReader rfreader; 142 | while (!infile.eof() && !infile.fail()) 143 | { 144 | string regionfile; 145 | getline(infile, regionfile); 146 | if (regionfile.empty()) 147 | continue; 148 | RegionIdx ri(0,0); 149 | if (RegionIdx::fromFilePath(regionfile, ri)) 150 | { 151 | PosRegionIdx pri(ri); 152 | if (!pri.valid()) 153 | { 154 | cerr << "ignoring extremely-distant region " << regionfile << " (world may be corrupt)" << endl; 155 | continue; 156 | } 157 | if (regiontable.isRequired(pri)) 158 | continue; 159 | vector chunks; 160 | if (0 != rfreader.getContainedChunks(ri, string84(inputdir), chunks)) 161 | { 162 | cerr << "can't open region " << regionfile << " to list chunks" << endl; 163 | continue; 164 | } 165 | if (chunks.empty()) 166 | continue; 167 | regiontable.setRequired(pri); 168 | reqregioncount++; 169 | for (vector::const_iterator chunk = chunks.begin(); chunk != chunks.end(); chunk++) 170 | { 171 | PosChunkIdx pci(*chunk); 172 | if (pci.valid()) 173 | { 174 | chunktable.setRequired(pci); 175 | reqchunkcount++; 176 | } 177 | else 178 | { 179 | cerr << "ignoring extremely-distant chunk " << chunk->toFileName() << " (world may be corrupt)" << endl; 180 | continue; 181 | } 182 | vector tiles = chunk->getTiles(mp); 183 | for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) 184 | { 185 | PosTileIdx pti(*tile); 186 | if (pti.valid()) 187 | tiletable.setRequired(pti); 188 | else 189 | { 190 | cerr << "ignoring extremely-distant tile [" << tile->x << "," << tile->y << "]" << endl; 191 | cerr << "(world may be corrupt; is region " << regionfile << " supposed to exist?)" << endl; 192 | continue; 193 | } 194 | if (!tile->valid(mp)) 195 | { 196 | cerr << "baseZoom too small! can't fit tile [" << tile->x << "," << tile->y << "]" << endl; 197 | return -1; 198 | } 199 | } 200 | } 201 | } 202 | } 203 | reqtilecount = tiletable.reqcount; 204 | return 0; 205 | } 206 | 207 | 208 | 209 | 210 | 211 | const char *chunkdirs[64] = {"/0", "/1", "/2", "/3", "/4", "/5", "/6", "/7", "/8", "/9", "/a", "/b", "/c", "/d", "/e", "/f", 212 | "/g", "/h", "/i", "/j", "/k", "/l", "/m", "/n", "/o", "/p", "/q", "/r", "/s", "/t", "/u", "/v", 213 | "/w", "/x", "/y", "/z", "/10", "/11", "/12", "/13", "/14", "/15", "/16", "/17", "/18", "/19", "/1a", "/1b", 214 | "/1c", "/1d", "/1e", "/1f", "/1g", "/1h", "/1i", "/1j", "/1k", "/1l", "/1m", "/1n", "/1o", "/1p", "/1q", "/1r",}; 215 | 216 | bool makeAllChunksRequired(const string& topdir, ChunkTable& chunktable, TileTable& tiletable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount) 217 | { 218 | bool findBaseZoom = mp.baseZoom == -1; 219 | // if finding the baseZoom, we'll just start from 0 and increase it whenever we hit a tile that's out of bounds 220 | if (findBaseZoom) 221 | mp.baseZoom = 0; 222 | reqchunkcount = 0; 223 | // go through each world subdirectory 224 | for (int x = 0; x < 64; x++) 225 | for (int z = 0; z < 64; z++) 226 | { 227 | // get all files in the subdirectory 228 | vector chunkpaths; 229 | string path = topdir + chunkdirs[x] + chunkdirs[z]; 230 | listEntries(path, chunkpaths); 231 | for (vector::const_iterator it = chunkpaths.begin(); it != chunkpaths.end(); it++) 232 | { 233 | ChunkIdx ci(0,0); 234 | // if this is a proper chunk filename, use it 235 | if (ChunkIdx::fromFilePath(*it, ci)) 236 | { 237 | // mark the chunk required 238 | PosChunkIdx pci(ci); 239 | if (pci.valid()) 240 | { 241 | chunktable.setRequired(pci); 242 | reqchunkcount++; 243 | } 244 | else 245 | { 246 | cerr << "ignoring extremely-distant chunk " << ci.toFileName() << " (world may be corrupt)" << endl; 247 | continue; 248 | } 249 | // get the tiles it touches and mark them required 250 | vector tiles = ci.getTiles(mp); 251 | for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) 252 | { 253 | // first check if this tile fits in the TileTable, whose size is fixed 254 | PosTileIdx pti(*tile); 255 | if (pti.valid()) 256 | tiletable.setRequired(pti); 257 | else 258 | { 259 | cerr << "ignoring extremely-distant tile [" << tile->x << "," << tile->y << "]" << endl; 260 | cerr << "(world may be corrupt; is chunk " << ci.toFileName() << " supposed to exist?)" << endl; 261 | continue; 262 | } 263 | // now see if the tile fits on the Google map 264 | if (!tile->valid(mp)) 265 | { 266 | // if we're supposed to be finding baseZoom, then bump it up until this tile fits 267 | if (findBaseZoom) 268 | { 269 | while (!tile->valid(mp)) 270 | mp.baseZoom++; 271 | } 272 | // otherwise, abort 273 | else 274 | { 275 | cerr << "baseZoom too small! can't fit tile [" << tile->x << "," << tile->y << "]" << endl; 276 | return false; 277 | } 278 | } 279 | } 280 | } 281 | } 282 | } 283 | reqtilecount = tiletable.reqcount; 284 | if (findBaseZoom) 285 | cout << "baseZoom set to " << mp.baseZoom << endl; 286 | return true; 287 | } 288 | 289 | int readChunklist(const string& chunklist, ChunkTable& chunktable, TileTable& tiletable, const MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount) 290 | { 291 | ifstream infile(chunklist.c_str()); 292 | if (infile.fail()) 293 | { 294 | cerr << "couldn't open chunklist " << chunklist << endl; 295 | return -2; 296 | } 297 | reqchunkcount = 0; 298 | while (!infile.eof() && !infile.fail()) 299 | { 300 | string chunkfile; 301 | getline(infile, chunkfile); 302 | if (chunkfile.empty()) 303 | continue; 304 | ChunkIdx ci(0,0); 305 | if (ChunkIdx::fromFilePath(chunkfile, ci)) 306 | { 307 | PosChunkIdx pci(ci); 308 | if (pci.valid()) 309 | { 310 | chunktable.setRequired(pci); 311 | reqchunkcount++; 312 | } 313 | else 314 | { 315 | cerr << "ignoring extremely-distant chunk " << ci.toFileName() << " (world may be corrupt)" << endl; 316 | continue; 317 | } 318 | vector tiles = ci.getTiles(mp); 319 | for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) 320 | { 321 | PosTileIdx pti(*tile); 322 | if (pti.valid()) 323 | tiletable.setRequired(pti); 324 | else 325 | { 326 | cerr << "ignoring extremely-distant tile [" << tile->x << "," << tile->y << "]" << endl; 327 | cerr << "(world may be corrupt; is chunk " << ci.toFileName() << " supposed to exist?)" << endl; 328 | continue; 329 | } 330 | if (!tile->valid(mp)) 331 | { 332 | cerr << "baseZoom too small! can't fit tile [" << tile->x << "," << tile->y << "]" << endl; 333 | return -1; 334 | } 335 | } 336 | } 337 | } 338 | reqtilecount = tiletable.reqcount; 339 | return 0; 340 | } 341 | 342 | void makeTestWorld(int size, ChunkTable& chunktable, TileTable& tiletable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount) 343 | { 344 | bool findBaseZoom = mp.baseZoom == -1; 345 | // if finding the baseZoom, we'll just start from 0 and increase it whenever we hit a tile that's out of bounds 346 | if (findBaseZoom) 347 | mp.baseZoom = 0; 348 | reqchunkcount = 0; 349 | // we'll start by putting 95% of the chunks in a solid block at the center 350 | int size2 = (int)(sqrt((double)size * 0.95) / 2.0); 351 | ChunkIdx ci(0,0); 352 | for (ci.x = -size2; ci.x < size2; ci.x++) 353 | for (ci.z = -size2; ci.z < size2; ci.z++) 354 | { 355 | chunktable.setRequired(ci); 356 | reqchunkcount++; 357 | vector tiles = ci.getTiles(mp); 358 | for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) 359 | { 360 | tiletable.setRequired(*tile); 361 | while (findBaseZoom && !tile->valid(mp)) 362 | mp.baseZoom++; 363 | } 364 | } 365 | // now add some circles of required chunks with radii up to four times the (minimum) radius of the 366 | // center block 367 | for (int m = 2; m <= 4; m++) 368 | { 369 | double rad = (double)size2 * (double)m; 370 | for (double t = -3.14159; t < 3.14159; t += 0.002) 371 | { 372 | ChunkIdx ci((int)(cos(t) * rad), (int)(sin(t) * rad)); 373 | chunktable.setRequired(ci); 374 | reqchunkcount++; 375 | vector tiles = ci.getTiles(mp); 376 | for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) 377 | { 378 | tiletable.setRequired(*tile); 379 | while (findBaseZoom && !tile->valid(mp)) 380 | mp.baseZoom++; 381 | } 382 | } 383 | } 384 | // now add some spokes going from the center out to the circle 385 | int irad = size2 * 4; 386 | for (ci.x = 0, ci.z = -irad; ci.z < irad; ci.z++) 387 | { 388 | chunktable.setRequired(ci); 389 | reqchunkcount++; 390 | vector tiles = ci.getTiles(mp); 391 | for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) 392 | { 393 | tiletable.setRequired(*tile); 394 | while (findBaseZoom && !tile->valid(mp)) 395 | mp.baseZoom++; 396 | } 397 | } 398 | for (ci.x = -irad, ci.z = 0; ci.x < irad; ci.x++) 399 | { 400 | chunktable.setRequired(ci); 401 | reqchunkcount++; 402 | vector tiles = ci.getTiles(mp); 403 | for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) 404 | { 405 | tiletable.setRequired(*tile); 406 | while (findBaseZoom && !tile->valid(mp)) 407 | mp.baseZoom++; 408 | } 409 | } 410 | for (ci.x = -irad, ci.z = -irad; ci.z < irad; ci.x++, ci.z++) 411 | { 412 | chunktable.setRequired(ci); 413 | reqchunkcount++; 414 | vector tiles = ci.getTiles(mp); 415 | for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) 416 | { 417 | tiletable.setRequired(*tile); 418 | while (findBaseZoom && !tile->valid(mp)) 419 | mp.baseZoom++; 420 | } 421 | } 422 | for (ci.x = irad, ci.z = -irad; ci.z < irad; ci.x--, ci.z++) 423 | { 424 | chunktable.setRequired(ci); 425 | reqchunkcount++; 426 | vector tiles = ci.getTiles(mp); 427 | for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) 428 | { 429 | tiletable.setRequired(*tile); 430 | while (findBaseZoom && !tile->valid(mp)) 431 | mp.baseZoom++; 432 | } 433 | } 434 | reqtilecount = tiletable.reqcount; 435 | if (findBaseZoom) 436 | cout << "baseZoom set to " << mp.baseZoom << endl; 437 | } 438 | 439 | 440 | 441 | 442 | 443 | // used only for testing 444 | void findAllChunks(const string& topdir, vector& chunkpaths) 445 | { 446 | for (int x = 0; x < 64; x++) 447 | for (int z = 0; z < 64; z++) 448 | { 449 | string path = topdir + chunkdirs[x] + chunkdirs[z]; 450 | listEntries(path, chunkpaths); 451 | } 452 | } 453 | 454 | -------------------------------------------------------------------------------- /map.h: -------------------------------------------------------------------------------- 1 | // Copyright 2010, 2011 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #ifndef MAP_H 19 | #define MAP_H 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | // IMPORTANT NOTE: 26 | // This program was written before the location of the sun moved in Minecraft Beta 1.9 or so, 27 | // therefore all of the N/S/E/W directions here are now wrong--rotated 90 degrees from what they 28 | // should be. For example, the positive X direction used to be South, and is called South here, 29 | // but is now East in the game (as of Minecraft 1.0, anyway). 30 | // I decided to leave the old direction names here, because it would be pretty easy to mess 31 | // something up trying to go through and change everything. Apologies for the confusion! 32 | 33 | // Minecraft coord system: 34 | // 35 | // +x = S +z = W +y = U 36 | // -x = N -z = E -y = D 37 | 38 | // the block size is a parameter B >= 2 39 | // 40 | // BlockIdx delta image coord delta 41 | // [bx,bz,by] [x,y] 42 | //--------------------------------------------- 43 | // [-1,0,0] (N) [-2B,B] 44 | // [1,0,0] (S) [2B,-B] 45 | // [0,-1,0] (E) [-2B,-B] 46 | // [0,1,0] (W) [2B,B] 47 | // [0,0,-1] (D) [0,2B] 48 | // [0,0,1] (U) [0,-2B] 49 | // 50 | // block size endpoint-exclusive bounding box (from block center) 51 | // B [-2B,-2B] to [2B,2B] (size 4Bx4B) 52 | // 53 | // [2B 2B 0 ] [bx] [x] 54 | // [-B B -2B] * [bz] = [y] 55 | // [by] 56 | // 57 | // so the absolute pixel coords of the center of block [bx,bz,by] are [2*B*bx + 2*B*bz, -B*bx + B*bz - 2*B*by] 58 | // 59 | // the pixels that correspond to block centers form a hexagonal grid: 60 | // x % 2B = 0 61 | // y % 2B = 0 (if x % 4B = 0) 62 | // y % 2B = B (if x % 4B = 2B) 63 | // 64 | // example, with B = 1: 65 | // 66 | // X...X...X... 67 | // ..X...X...X. 68 | // X...X...X... 69 | // ..X...X...X. 70 | // X...X...X... 71 | // ..X...X...X. 72 | 73 | // a chunk is 16x16x128 blocks (chunk/region format) or 16x16x256 blocks (Anvil format), but we can 74 | // also specify the min and max Y values we want, and consider a chunk to be 16x16x(MAXY-MINY+1), 75 | // where MINY and MAXY are inclusive--e.g. for an entire Anvil chunk, MINY=0 and MAXY=255 76 | // 77 | // each chunk covers a hexagonal area of the image; the corners of the hexagon (in clockwise order) 78 | // are the farthest blocks NED, NEU, SEU, SWU, SWD, NWD 79 | // 80 | // SEU 81 | // / . \ 82 | // / . \ 83 | // NEU . SWU 84 | // |\ . /| 85 | // | \ . / | 86 | // | NWU | 87 | // | | | 88 | // | (SED) | 89 | // | . | . | 90 | // |. | .| 91 | // NED | SWD 92 | // \ | / 93 | // \ | / 94 | // NWD 95 | // 96 | // in block coordinates, let the origin of the chunk be the block at [0,0,0] relative to the chunk--i.e. 97 | // the NED corner of the full chunk (as opposed to the slice of it defined by MINY and MAXY) 98 | // 99 | // relative to the origin block of the chunk, the corner blocks are: 100 | // NED NEU SEU SWU SWD NWD 101 | // [0,0,MINY] [0,0,MAXY] [15,0,MAXY] [15,15,MAXY] [15,15,MINY] [0,15,MINY] 102 | // 103 | // ...and the image-coord centers of these blocks, relative to the image-coord center of the origin block, are: 104 | // NED NEU SEU SWU SWD NWD 105 | // [0,-2B*MINY] [0,-2B*MAXY] [30B,-15B-2B*MAXY] [60B,-2B*MAXY] [60B,-2B*MINY] [30B,15B-2B*MINY] 106 | // 107 | // ...and the distances to the origin blocks of the neighboring chunks are: 108 | // N E S W 109 | // [-32B,16B] [-32B,-16B] [32B,-16B] [32B,16B] 110 | // NE SE SW NW 111 | // [-64B,0] [0,-32B] [64B,0] [0,32B] 112 | // 113 | // ...and the endpoint-exclusive bounding box of a chunk, from the center of the origin block, is: 114 | // [-2B,-17B-2B*MAXY] to [62B,17B-2B*MINY] (size 64B x 34B-2B*(MAXY-MINY)) 115 | // 116 | // the center of the origin block of chunk [0,0] is the origin for the absolute pixel 117 | // coord system, so the center of the origin block of chunk [cx,cz] is [32*B*cx + 32*B*cz, -16B*cx + 16*B*cz] 118 | 119 | // tile size must be = T * 64B for some T (so it covers the width of at least one chunk) 120 | // ...each tile has a base chunk; the tile's bounding box shares its bottom-left corner with 121 | // its base chunk's full bounding box (so the base chunk is contained within the tile on the 122 | // left, right, and bottom, but may extend past the top of the tile) 123 | // 124 | // TileIdx delta ChunkIdx delta 125 | // [tx,ty] [cx,cz] 126 | // ----------------------------------------------- 127 | // [1,0] [T,T] 128 | // [0,1] [-2T,2T] 129 | // 130 | // TileIdx: 131 | // [tx,ty] in tile coords 132 | // ChunkIdx of base chunk: 133 | // [T*tx - 2*T*ty, T*tx + 2*T*ty] in chunk coords 134 | // center of base chunk's origin block: 135 | // [64*B*T*tx, 64*B*T*ty] in absolute pixels 136 | // base chunk's endpoint-exclusive bounding box: 137 | // [64*B*T*tx - 2*B, 64*B*T*ty - 17*B-2*B*MAXY] to [64*B*T*tx + 62*B, 64*B*T*ty + 17*B-2*B*MINY] in absolute pixels 138 | // tile's endpoint-exclusive bounding box: 139 | // [64*B*T*tx - 2*B, 64*B*T*ty + 17*B - 64*B*T] to [64*B*T*tx - 2*B + 64*B*T, 64*B*T*ty + 17*B] in absolute pixels 140 | // 141 | // to compute the TileIdx [tx,ty] that includes a pixel [x,y]: 142 | // 1. let x' = x + 2B, y' = y + 64BT - 17B 143 | // 2. tx = floor(x' / 64BT) 144 | // 3. ty = floor(y' / 64BT) 145 | // ...where floor(a / b) represents floored division, i.e. the result you'd get by performing the real-number division a / b 146 | // and then taking the floor 147 | 148 | // the tiles required to draw a chunk can be determined by finding the tile that the origin block's center 149 | // is in, then checking the tiles below, right, and above that one, looking for bounding box intersections 150 | 151 | // the set of chunks required to draw a tile can be constructed thusly: 152 | // set #1: start with the base chunk and the chunk directly SE of it 153 | // set #2: if T > 1, add all the chunks that can be reached by moving up to T-1 steps SW 154 | // and/or up to T-1 double steps SE from the chunks in set #1 (this is the set of 155 | // chunks that would be in set #1 for *some* tile, if T was = 1) 156 | // set #3: add all chunks that are immediate N, E, S, or W neighbors of chunks in set #2 157 | // (these are the chunks whose bottom layer of blocks is partially within the tile) 158 | // set #4: add all chunks that are up to 8 steps NW of a chunk in set #3 (these are the chunks 159 | // *some* layer of which is partially within the tile) 160 | 161 | 162 | struct ChunkIdx; 163 | struct RegionIdx; 164 | struct TileIdx; 165 | struct ZoomTileIdx; 166 | 167 | struct MapParams 168 | { 169 | int B; // block size; must be >= 2 170 | int T; // tile multiplier; must be >= 1 171 | // Google Maps zoom level of the base tiles; maximum map size is 2^baseZoom by 2^baseZoom tiles 172 | int baseZoom; 173 | 174 | // MINY and MAXY values used during rendering; these are either specified by the user, or filled 175 | // in with Minecraft's limits (0-255) 176 | int minY, maxY; 177 | 178 | // whether or not the MINY/MAXY values are user-provided or defaults; default values are not stored 179 | // in pigmap.params 180 | bool userMinY, userMaxY; 181 | 182 | MapParams(int b, int t, int bz) : B(b), T(t), baseZoom(bz), minY(0), maxY(255), userMinY(false), userMaxY(false) {} 183 | MapParams() : B(0), T(0), baseZoom(0), minY(0), maxY(255), userMinY(false), userMaxY(false) {} 184 | 185 | int tileSize() const {return 64*B*T;} 186 | 187 | bool valid() const; // see if B and T are okay 188 | bool validZoom() const; // see if baseZoom is okay 189 | bool validYRange() const; // see if MINY/MAXY are okay 190 | 191 | // read/write the file "pigmap.params" in the output path (i.e. the top-level map directory) 192 | bool readFile(const std::string& outputpath); // also validates stored values 193 | void writeFile(const std::string& outputpath) const; 194 | }; 195 | 196 | 197 | struct Pixel 198 | { 199 | int64_t x, y; 200 | 201 | Pixel(int64_t xx, int64_t yy) : x(xx), y(yy) {} 202 | 203 | Pixel& operator+=(const Pixel& p) {x += p.x; y += p.y; return *this;} 204 | Pixel& operator-=(const Pixel& p) {x -= p.x; y -= p.y; return *this;} 205 | bool operator==(const Pixel& p) const {return x == p.x && y == p.y;} 206 | bool operator!=(const Pixel& p) const {return !operator==(p);} 207 | 208 | TileIdx getTile(const MapParams& mp) const; 209 | }; 210 | 211 | Pixel operator+(const Pixel& p1, const Pixel& p2); 212 | Pixel operator-(const Pixel& p1, const Pixel& p2); 213 | 214 | // endpoint-exclusive bounding box (right and bottom edges not included) 215 | struct BBox 216 | { 217 | Pixel topLeft, bottomRight; 218 | 219 | BBox(const Pixel& tl, const Pixel& br) : topLeft(tl), bottomRight(br) {} 220 | 221 | Pixel bottomLeft() const {return Pixel(topLeft.x, bottomRight.y);} 222 | Pixel topRight() const {return Pixel(bottomRight.x, topLeft.y);} 223 | 224 | bool includes(const Pixel& p) const; 225 | bool overlaps(const BBox& bb) const; 226 | }; 227 | 228 | struct BlockIdx 229 | { 230 | int64_t x, z, y; 231 | 232 | BlockIdx(int64_t xx, int64_t zz, int64_t yy) : x(xx), z(zz), y(yy) {} 233 | 234 | bool occludes(const BlockIdx& bi) const; 235 | bool isOccludedBy(const BlockIdx& bi) const {return bi.occludes(*this);} 236 | 237 | Pixel getCenter(const MapParams& mp) const {return Pixel(2*mp.B*(x+z), mp.B*(z-x-2*y));} 238 | BBox getBBox(const MapParams& mp) const {Pixel c = getCenter(mp); return BBox(c - Pixel(2*mp.B,2*mp.B), c + Pixel(2*mp.B,2*mp.B));} 239 | ChunkIdx getChunkIdx() const; 240 | 241 | // there are many blocks that project to each pixel on the map (one for each Y-value); 242 | // this returns the topmost, assuming that the pixel is properly aligned on the block-center grid 243 | static BlockIdx topBlock(const Pixel& p, const MapParams& mp); 244 | 245 | BlockIdx& operator+=(const BlockIdx& bi) {x += bi.x; z += bi.z; y += bi.y; return *this;} 246 | BlockIdx& operator-=(const BlockIdx& bi) {x -= bi.x; z -= bi.z; y -= bi.y; return *this;} 247 | bool operator==(const BlockIdx& bi) const {return x == bi.x && z == bi.z && y == bi.y;} 248 | bool operator!=(const BlockIdx& bi) const {return !operator==(bi);} 249 | }; 250 | 251 | BlockIdx operator+(const BlockIdx& bi1, const BlockIdx& bi2); 252 | BlockIdx operator-(const BlockIdx& bi1, const BlockIdx& bi2); 253 | 254 | struct ChunkIdx 255 | { 256 | int64_t x, z; 257 | 258 | ChunkIdx(int64_t xx, int64_t zz) : x(xx), z(zz) {} 259 | 260 | // just the filename (e.g. "c.0.0.dat") 261 | std::string toFileName() const; 262 | // the relative path from the top level of world data (e.g. "0/0/c.0.0.dat") 263 | std::string toFilePath() const; 264 | 265 | // see if a path is a valid chunk file and return its ChunkIdx if so 266 | // ...can be plain filename, relative path, or absolute path; the chunk coords 267 | // depend only on the filename 268 | static bool fromFilePath(const std::string& filename, ChunkIdx& result); 269 | 270 | BlockIdx originBlock() const {return BlockIdx(x*16, z*16, 0);} 271 | BlockIdx nedCorner(const MapParams& mp) const {return BlockIdx(x*16, z*16, mp.minY);} 272 | BBox getBBox(const MapParams& mp) const {Pixel c = originBlock().getCenter(mp); return BBox(c - Pixel(2*mp.B,(17+2*mp.maxY)*mp.B), c + Pixel(62*mp.B,(17-2*mp.minY)*mp.B));} 273 | RegionIdx getRegionIdx() const; 274 | 275 | std::vector getTiles(const MapParams& mp) const; 276 | 277 | ChunkIdx& operator+=(const ChunkIdx& ci) {x += ci.x; z += ci.z; return *this;} 278 | ChunkIdx& operator-=(const ChunkIdx& ci) {x -= ci.x; z -= ci.z; return *this;} 279 | bool operator==(const ChunkIdx& ci) const {return x == ci.x && z == ci.z;} 280 | bool operator!=(const ChunkIdx& ci) const {return !operator==(ci);} 281 | }; 282 | 283 | ChunkIdx operator+(const ChunkIdx& ci1, const ChunkIdx& ci2); 284 | ChunkIdx operator-(const ChunkIdx& ci1, const ChunkIdx& ci2); 285 | 286 | struct RegionIdx 287 | { 288 | int64_t x, z; 289 | 290 | RegionIdx(int64_t xx, int64_t zz) : x(xx), z(zz) {} 291 | 292 | // just the filename (e.g. "r.-1.2.mcr" for old-style, "r.-1.2.mca" for Anvil) 293 | std::string toOldFileName() const; 294 | std::string toAnvilFileName() const; 295 | 296 | // see if a path is a valid region file and return its RegionIdx if so 297 | // ...can be plain filename, relative path, or absolute path; region coords 298 | // depend only on the filename 299 | // ...can be either an old (".mcr") or Anvil (".mca") filename 300 | static bool fromFilePath(const std::string& filename, RegionIdx& result); 301 | 302 | ChunkIdx baseChunk() const {return ChunkIdx(x*32, z*32);} // NE corner 303 | 304 | bool operator==(const RegionIdx& ri) const {return x == ri.x && z == ri.z;} 305 | bool operator!=(const RegionIdx& ri) const {return !operator==(ri);} 306 | }; 307 | 308 | // these guys represent tiles at the base zoom level 309 | struct TileIdx 310 | { 311 | // these are not the same coords used by Google Maps, since their coords are all positive; 312 | // our tile [0,0] maps to their tile [2^(baseZoom-1),2^(baseZoom-1)], etc. 313 | int64_t x, y; 314 | 315 | TileIdx(int64_t xx, int64_t yy) : x(xx), y(yy) {} 316 | 317 | // Google Maps limit is 2^Z by 2^Z tiles per zoom level Z; check whether this TileIdx is 318 | // within the allowed range for baseZoom 319 | bool valid(const MapParams& mp) const; 320 | // get Google Maps filepath (e.g. "0/3/2/0/0/1/2.png"), or empty string for invalid tile 321 | std::string toFilePath(const MapParams& mp) const; 322 | 323 | ChunkIdx baseChunk(const MapParams& mp) const {return ChunkIdx(mp.T*(x-2*y), mp.T*(x+2*y));} 324 | BBox getBBox(const MapParams& mp) const; 325 | ZoomTileIdx toZoomTileIdx(const MapParams& mp) const; 326 | 327 | TileIdx& operator+=(const TileIdx& t) {x += t.x; y += t.y; return *this;} 328 | TileIdx& operator-=(const TileIdx& t) {x -= t.x; y -= t.y; return *this;} 329 | bool operator==(const TileIdx& t) const {return t.x == x && t.y == y;} 330 | bool operator!=(const TileIdx& t) const {return !operator==(t);} 331 | }; 332 | 333 | TileIdx operator+(const TileIdx& t1, const TileIdx& t2); 334 | TileIdx operator-(const TileIdx& t1, const TileIdx& t2); 335 | 336 | // these guys represent tiles at the other zoom levels 337 | struct ZoomTileIdx 338 | { 339 | int64_t x, y; // Google Maps coords--each coord goes from 0 to 2^zoom 340 | int zoom; // Google Maps zoom level--0 for top level (base.png), etc. 341 | 342 | ZoomTileIdx(int64_t xx, int64_t yy, int z) : x(xx), y(yy), zoom(z) {} 343 | 344 | bool valid() const; 345 | std::string toFilePath() const; 346 | 347 | // get the top-left base tile contained in this tile 348 | TileIdx toTileIdx(const MapParams& mp) const; 349 | 350 | // if z > zoom, gets the top-left tile of those at level z that this tile includes; 351 | // if z < zoom, gets the tile at level z that includes this tile 352 | ZoomTileIdx toZoom(int z) const; 353 | 354 | // no operator+; addition shouldn't be defined for tiles with different zoom levels 355 | ZoomTileIdx add(int dx, int dy) const {return ZoomTileIdx(x + dx, y + dy, zoom);} 356 | }; 357 | 358 | #endif // MAP_H -------------------------------------------------------------------------------- /tables.h: -------------------------------------------------------------------------------- 1 | // Copyright 2010, 2011 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #ifndef TABLES_H 19 | #define TABLES_H 20 | 21 | #include 22 | #include 23 | 24 | #include "map.h" 25 | #include "utils.h" 26 | 27 | 28 | 29 | #define CTDATASIZE 3 30 | 31 | #define CTLEVEL1BITS 5 32 | #define CTLEVEL2BITS 5 33 | #define CTLEVEL3BITS 8 34 | 35 | #define CTLEVEL1SIZE (1 << CTLEVEL1BITS) 36 | #define CTLEVEL2SIZE (1 << CTLEVEL2BITS) 37 | #define CTLEVEL3SIZE (1 << CTLEVEL3BITS) 38 | #define CTTOTALSIZE (CTLEVEL1SIZE * CTLEVEL2SIZE * CTLEVEL3SIZE) 39 | 40 | #define CTLEVEL1MASK (CTLEVEL1SIZE - 1) 41 | #define CTLEVEL2MASK ((CTLEVEL2SIZE - 1) << CTLEVEL1BITS) 42 | #define CTLEVEL3MASK (((CTLEVEL3SIZE - 1) << CTLEVEL1BITS) << CTLEVEL2BITS) 43 | 44 | #define CTGETLEVEL1(a) (a & CTLEVEL1MASK) 45 | #define CTGETLEVEL2(a) ((a & CTLEVEL2MASK) >> CTLEVEL1BITS) 46 | #define CTGETLEVEL3(a) (((a & CTLEVEL3MASK) >> CTLEVEL2BITS) >> CTLEVEL1BITS) 47 | 48 | // variation of ChunkIdx for use with the ChunkTable: translates so that all coords are positive 49 | // ...can also be used to check for the map being too big 50 | struct PosChunkIdx 51 | { 52 | int64_t x, z; 53 | 54 | PosChunkIdx(int64_t xx, int64_t zz) : x(xx), z(zz) {} 55 | PosChunkIdx(const ChunkIdx& ci) : x(ci.x + CTTOTALSIZE/2), z(ci.z + CTTOTALSIZE/2) {} 56 | ChunkIdx toChunkIdx() const {return ChunkIdx(x - CTTOTALSIZE/2, z - CTTOTALSIZE/2);} 57 | bool valid() const {return x >= 0 && x < CTTOTALSIZE && z >= 0 && z < CTTOTALSIZE;} 58 | 59 | bool operator==(const PosChunkIdx& ci) const {return x == ci.x && z == ci.z;} 60 | bool operator!=(const PosChunkIdx& ci) const {return !operator==(ci);} 61 | }; 62 | 63 | // structure to hold information about a 32x32 set of chunks: for each chunk, whether it needs to be drawn, 64 | // whether it's even present on disk, etc. 65 | struct ChunkSet 66 | { 67 | // each chunk gets 3 bits: 68 | // -first bit is 1 for required (must be drawn), 0 for not required 69 | // -last two bits describe state of chunk on disk: 70 | // 00: have not tried to find chunk on disk yet 71 | // 01: have successfully read chunk from disk (i.e. it should be in the cache, if we still need it) 72 | // 10: chunk does not exist on disk 73 | // 11: chunk file is corrupted 74 | static const int CHUNK_UNKNOWN = 0; 75 | static const int CHUNK_CACHED = 1; 76 | static const int CHUNK_MISSING = 2; 77 | static const int CHUNK_CORRUPTED = 3; 78 | std::bitset bits; 79 | 80 | size_t bitIdx(const PosChunkIdx& ci) const {return (CTGETLEVEL1(ci.z) * CTLEVEL1SIZE + CTGETLEVEL1(ci.x)) * CTDATASIZE;} 81 | 82 | void setRequired(const PosChunkIdx& ci) {bits.set(bitIdx(ci));} 83 | void setDiskState(const PosChunkIdx& ci, int state) {size_t bi = bitIdx(ci); bits[bi+1] = state & 0x2; bits[bi+2] = state & 0x1;} 84 | }; 85 | 86 | // first level of indirection: information about a 32x32 group of ChunkSets, and hence a 1024x1024 set of chunks 87 | struct ChunkGroup 88 | { 89 | // pointers to ChunkSets with the data, or NULL for sets that aren't used 90 | ChunkSet *chunksets[CTLEVEL2SIZE*CTLEVEL2SIZE]; 91 | 92 | ChunkGroup() {for (int i = 0; i < CTLEVEL2SIZE*CTLEVEL2SIZE; i++) chunksets[i] = NULL;} 93 | ~ChunkGroup() {for (int i = 0; i < CTLEVEL2SIZE*CTLEVEL2SIZE; i++) if (chunksets[i] != NULL) delete chunksets[i];} 94 | 95 | int chunkSetIdx(const PosChunkIdx& ci) const {return CTGETLEVEL2(ci.z) * CTLEVEL2SIZE + CTGETLEVEL2(ci.x);} 96 | ChunkSet* getChunkSet(const PosChunkIdx& ci) const {return chunksets[chunkSetIdx(ci)];} 97 | 98 | void setRequired(const PosChunkIdx& ci); 99 | void setDiskState(const PosChunkIdx& ci, int state); 100 | }; 101 | 102 | // second (and final) level of indirection: 256x256 groups, so 262144x262144 possible chunks 103 | struct ChunkTable : private nocopy 104 | { 105 | ChunkGroup *chunkgroups[CTLEVEL3SIZE*CTLEVEL3SIZE]; 106 | 107 | ChunkTable() {for (int i = 0; i < CTLEVEL3SIZE*CTLEVEL3SIZE; i++) chunkgroups[i] = NULL;} 108 | ~ChunkTable() {for (int i = 0; i < CTLEVEL3SIZE*CTLEVEL3SIZE; i++) if (chunkgroups[i] != NULL) delete chunkgroups[i];} 109 | 110 | int chunkGroupIdx(const PosChunkIdx& ci) const {return CTGETLEVEL3(ci.z) * CTLEVEL3SIZE + CTGETLEVEL3(ci.x);} 111 | ChunkGroup* getChunkGroup(const PosChunkIdx& ci) const {return chunkgroups[chunkGroupIdx(ci)];} 112 | ChunkSet* getChunkSet(const PosChunkIdx& ci) const {ChunkGroup *cg = getChunkGroup(ci); return (cg == NULL) ? NULL : cg->getChunkSet(ci);} 113 | 114 | // given indices into the ChunkGroups/ChunkSets/bitset, construct a PosChunkIdx 115 | static PosChunkIdx toPosChunkIdx(int cgi, int csi, int bi); 116 | 117 | bool isRequired(const PosChunkIdx& ci) const {ChunkSet *cs = getChunkSet(ci); return (cs == NULL) ? false : cs->bits[cs->bitIdx(ci)];} 118 | int getDiskState(const PosChunkIdx& ci) const {ChunkSet *cs = getChunkSet(ci); return (cs == NULL) ? 0 : ((cs->bits[cs->bitIdx(ci)+1] ? 0x2 : 0) | (cs->bits[cs->bitIdx(ci)+2] ? 0x1 : 0));} 119 | 120 | void setRequired(const PosChunkIdx& ci); 121 | void setDiskState(const PosChunkIdx& ci, int state); 122 | 123 | void copyFrom(const ChunkTable& ctable); 124 | }; 125 | 126 | 127 | // given a ChunkTable, iterates over the required chunks 128 | // ...this is obsolete and not used, except in some test functions 129 | struct RequiredChunkIterator 130 | { 131 | bool end; // true once we've reached the end 132 | PosChunkIdx current; // if end == false, holds the current chunk 133 | 134 | ChunkTable& chunktable; 135 | int cgi, csi, bi; 136 | 137 | // constructor initializes us to the first required chunk 138 | RequiredChunkIterator(ChunkTable& ctable); 139 | 140 | // move to the next required chunk, or to the end 141 | void advance(); 142 | }; 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | #define TTDATASIZE 2 154 | 155 | #define TTLEVEL1BITS 4 156 | #define TTLEVEL2BITS 4 157 | #define TTLEVEL3BITS 8 158 | 159 | #define TTLEVEL1SIZE (1 << TTLEVEL1BITS) 160 | #define TTLEVEL2SIZE (1 << TTLEVEL2BITS) 161 | #define TTLEVEL3SIZE (1 << TTLEVEL3BITS) 162 | #define TTTOTALSIZE (TTLEVEL1SIZE * TTLEVEL2SIZE * TTLEVEL3SIZE) 163 | 164 | #define TTLEVEL1MASK (TTLEVEL1SIZE - 1) 165 | #define TTLEVEL2MASK ((TTLEVEL2SIZE - 1) << TTLEVEL1BITS) 166 | #define TTLEVEL3MASK (((TTLEVEL3SIZE - 1) << TTLEVEL1BITS) << TTLEVEL2BITS) 167 | 168 | #define TTGETLEVEL1(a) (a & TTLEVEL1MASK) 169 | #define TTGETLEVEL2(a) ((a & TTLEVEL2MASK) >> TTLEVEL1BITS) 170 | #define TTGETLEVEL3(a) (((a & TTLEVEL3MASK) >> TTLEVEL2BITS) >> TTLEVEL1BITS) 171 | 172 | // variation of TileIdx for use with the TileTable: translates so that all coords are positive 173 | // ...can also be used to check for the map being too big 174 | struct PosTileIdx 175 | { 176 | int64_t x, y; 177 | 178 | PosTileIdx(int64_t xx, int64_t yy) : x(xx), y(yy) {} 179 | PosTileIdx(const TileIdx& ti) : x(ti.x + TTTOTALSIZE/2), y(ti.y + TTTOTALSIZE/2) {} 180 | TileIdx toTileIdx() const {return TileIdx(x - TTTOTALSIZE/2, y - TTTOTALSIZE/2);} 181 | bool valid() const {return x >= 0 && x < TTTOTALSIZE && y >= 0 && y < TTTOTALSIZE;} 182 | 183 | bool operator==(const PosTileIdx& ti) const {return x == ti.x && y == ti.y;} 184 | bool operator!=(const PosTileIdx& ti) const {return !operator==(ti);} 185 | }; 186 | 187 | // structure to hold information about a 16x16 set of tiles: for each tile, whether it's been drawn yet 188 | struct TileSet 189 | { 190 | // each tile gets two bits: first is whether it's required, second is whether it's been drawn 191 | std::bitset bits; 192 | 193 | size_t bitIdx(const PosTileIdx& ti) const {return (TTGETLEVEL1(ti.y) * TTLEVEL1SIZE + TTGETLEVEL1(ti.x)) * TTDATASIZE;} 194 | 195 | // assumes that ti actually belongs to this set 196 | bool isRequired(const PosTileIdx& ti) const {return bits[bitIdx(ti)];} 197 | 198 | // set tile's required bit and return previous state of bit 199 | bool setRequired(const PosTileIdx& ti) {size_t bi = bitIdx(ti); bool rv = bits[bi]; bits.set(bi); return rv;} 200 | void setDrawn(const PosTileIdx& ti) {bits.set(bitIdx(ti)+1);} 201 | }; 202 | 203 | // first level of indirection: information about a 256x256 set of tiles 204 | struct TileGroup 205 | { 206 | // pointers to TileSets with the data, or NULL for 16x16 sets that aren't used 207 | TileSet *tilesets[TTLEVEL2SIZE*TTLEVEL2SIZE]; 208 | 209 | // number of tiles in this group that have been set to required 210 | int64_t reqcount; 211 | 212 | TileGroup() : reqcount(0) {for (int i = 0; i < TTLEVEL2SIZE*TTLEVEL2SIZE; i++) tilesets[i] = NULL;} 213 | ~TileGroup() {for (int i = 0; i < TTLEVEL2SIZE*TTLEVEL2SIZE; i++) if (tilesets[i] != NULL) delete tilesets[i];} 214 | 215 | int tileSetIdx(const PosTileIdx& ti) const {return TTGETLEVEL2(ti.y) * TTLEVEL2SIZE + TTGETLEVEL2(ti.x);} 216 | TileSet* getTileSet(const PosTileIdx& ti) const {return tilesets[tileSetIdx(ti)];} 217 | 218 | bool setRequired(const PosTileIdx& ti); // set tile's required bit and return previous state of bit 219 | void setDrawn(const PosTileIdx& ti); 220 | }; 221 | 222 | // second (and final) level of indirection: a 65536x65536 set of tiles 223 | struct TileTable : private nocopy 224 | { 225 | TileGroup *tilegroups[TTLEVEL3SIZE*TTLEVEL3SIZE]; 226 | 227 | int64_t reqcount; 228 | 229 | TileTable() : reqcount(0) {for (int i = 0; i < TTLEVEL3SIZE*TTLEVEL3SIZE; i++) tilegroups[i] = NULL;} 230 | ~TileTable() {for (int i = 0; i < TTLEVEL3SIZE*TTLEVEL3SIZE; i++) if (tilegroups[i] != NULL) delete tilegroups[i];} 231 | 232 | int tileGroupIdx(const PosTileIdx& ti) const {return TTGETLEVEL3(ti.y) * TTLEVEL3SIZE + TTGETLEVEL3(ti.x);} 233 | TileGroup* getTileGroup(const PosTileIdx& ti) const {return tilegroups[tileGroupIdx(ti)];} 234 | TileSet* getTileSet(const PosTileIdx& ti) const {TileGroup *tg = getTileGroup(ti); return (tg == NULL) ? NULL : tg->getTileSet(ti);} 235 | 236 | // given indices into the TileGroups/TileSets/bitset, construct a PosTileIdx 237 | static PosTileIdx toPosTileIdx(int tgi, int tsi, int bi); 238 | 239 | bool isRequired(const PosTileIdx& ti) const {TileSet *ts = getTileSet(ti); return (ts == NULL) ? false : ts->bits[ts->bitIdx(ti)];} 240 | bool isDrawn(const PosTileIdx& ti) const {TileSet *ts = getTileSet(ti); return (ts == NULL) ? false : ts->bits[ts->bitIdx(ti)+1];} 241 | 242 | bool setRequired(const PosTileIdx& ti); // set tile's required bit and return previous state of bit 243 | void setDrawn(const PosTileIdx& ti); 244 | 245 | // see if an entire zoom tile can be rejected because its TileGroup or TileSet is NULL 246 | bool reject(const ZoomTileIdx& zti, const MapParams& mp) const; 247 | 248 | // get the total number of base tiles required to draw a zoom tile 249 | int64_t getNumRequired(const ZoomTileIdx& zti, const MapParams& mp) const; 250 | 251 | void copyFrom(const TileTable& ttable); 252 | }; 253 | 254 | 255 | 256 | // given a TileTable, iterates over the required tiles 257 | struct RequiredTileIterator 258 | { 259 | bool end; // true once we've reached the end 260 | PosTileIdx current; // if end == false, holds the current tile 261 | 262 | TileTable& tiletable; 263 | // these guys are Z-order indices and must be converted to row-major when accessing the TileTable 264 | int ztgi, ztsi, zbi; 265 | 266 | // constructor initializes us to the first required tile 267 | RequiredTileIterator(TileTable& ttable); 268 | 269 | // move in Z-order to the next required tile, or to the end 270 | void advance(); 271 | }; 272 | 273 | // given a TileTable, iterates over the non-NULL TileGroups 274 | struct TileGroupIterator 275 | { 276 | bool end; // true once we've reached the end 277 | int tgi; // if end == false, holds the current index into TileTable::tilegroups 278 | ZoomTileIdx zti; // if end == false, holds the zoom tile corresponding to the current TileGroup 279 | 280 | TileTable& tiletable; 281 | MapParams mp; 282 | 283 | // constructor initializes to first non-NULL TileGroup 284 | TileGroupIterator(TileTable& ttable, const MapParams& mparams); 285 | 286 | // move to the next non-NULL TileGroup, or to the end 287 | void advance(); 288 | }; 289 | 290 | 291 | 292 | 293 | #define RTDATASIZE 3 294 | 295 | #define RTLEVEL1BITS 4 296 | #define RTLEVEL2BITS 4 297 | #define RTLEVEL3BITS 6 298 | 299 | #define RTLEVEL1SIZE (1 << RTLEVEL1BITS) 300 | #define RTLEVEL2SIZE (1 << RTLEVEL2BITS) 301 | #define RTLEVEL3SIZE (1 << RTLEVEL3BITS) 302 | #define RTTOTALSIZE (RTLEVEL1SIZE * RTLEVEL2SIZE * RTLEVEL3SIZE) 303 | 304 | #define RTLEVEL1MASK (RTLEVEL1SIZE - 1) 305 | #define RTLEVEL2MASK ((RTLEVEL2SIZE - 1) << RTLEVEL1BITS) 306 | #define RTLEVEL3MASK (((RTLEVEL3SIZE - 1) << RTLEVEL1BITS) << RTLEVEL2BITS) 307 | 308 | #define RTGETLEVEL1(a) (a & RTLEVEL1MASK) 309 | #define RTGETLEVEL2(a) ((a & RTLEVEL2MASK) >> RTLEVEL1BITS) 310 | #define RTGETLEVEL3(a) (((a & RTLEVEL3MASK) >> RTLEVEL2BITS) >> RTLEVEL1BITS) 311 | 312 | struct PosRegionIdx 313 | { 314 | int64_t x, z; 315 | 316 | PosRegionIdx(int64_t xx, int64_t zz) : x(xx), z(zz) {} 317 | PosRegionIdx(const RegionIdx& ri) : x(ri.x + RTTOTALSIZE/2), z(ri.z + RTTOTALSIZE/2) {} 318 | RegionIdx toRegionIdx() const {return RegionIdx(x - RTTOTALSIZE/2, z - RTTOTALSIZE/2);} 319 | bool valid() const {return x >= 0 && x < RTTOTALSIZE && z >= 0 && z < RTTOTALSIZE;} 320 | 321 | bool operator==(const PosRegionIdx& ri) const {return x == ri.x && z == ri.z;} 322 | bool operator!=(const PosRegionIdx& ri) const {return !operator==(ri);} 323 | }; 324 | 325 | struct RegionSet 326 | { 327 | // each region gets 3 bits: 328 | // -first bit is 1 for required (must be drawn), 0 for not required 329 | // -last two bits describe state of region on disk: 330 | // 00: have not tried to find region on disk yet 331 | // 01: have successfully read region from disk (i.e. it should be in the cache, if we still need it) 332 | // 10: region does not exist on disk 333 | // 11: region file is corrupted 334 | static const int REGION_UNKNOWN = 0; 335 | static const int REGION_CACHED = 1; 336 | static const int REGION_MISSING = 2; 337 | static const int REGION_CORRUPTED = 3; 338 | std::bitset bits; 339 | 340 | size_t bitIdx(const PosRegionIdx& ri) const {return (RTGETLEVEL1(ri.z) * RTLEVEL1SIZE + RTGETLEVEL1(ri.x)) * RTDATASIZE;} 341 | 342 | void setRequired(const PosRegionIdx& ri) {bits.set(bitIdx(ri));} 343 | void setDiskState(const PosRegionIdx& ri, int state) {size_t bi = bitIdx(ri); bits[bi+1] = state & 0x2; bits[bi+2] = state & 0x1;} 344 | }; 345 | 346 | struct RegionGroup 347 | { 348 | RegionSet *regionsets[RTLEVEL2SIZE*RTLEVEL2SIZE]; 349 | 350 | RegionGroup() {for (int i = 0; i < RTLEVEL2SIZE*RTLEVEL2SIZE; i++) regionsets[i] = NULL;} 351 | ~RegionGroup() {for (int i = 0; i < RTLEVEL2SIZE*RTLEVEL2SIZE; i++) if (regionsets[i] != NULL) delete regionsets[i];} 352 | 353 | int regionSetIdx(const PosRegionIdx& ri) const {return RTGETLEVEL2(ri.z) * RTLEVEL2SIZE + RTGETLEVEL2(ri.x);} 354 | RegionSet* getRegionSet(const PosRegionIdx& ri) const {return regionsets[regionSetIdx(ri)];} 355 | 356 | void setRequired(const PosRegionIdx& ri); 357 | void setDiskState(const PosRegionIdx& ri, int state); 358 | }; 359 | 360 | struct RegionTable : private nocopy 361 | { 362 | RegionGroup *regiongroups[RTLEVEL3SIZE*RTLEVEL3SIZE]; 363 | 364 | RegionTable() {for (int i = 0; i < RTLEVEL3SIZE*RTLEVEL3SIZE; i++) regiongroups[i] = NULL;} 365 | ~RegionTable() {for (int i = 0; i < RTLEVEL3SIZE*RTLEVEL3SIZE; i++) if (regiongroups[i] != NULL) delete regiongroups[i];} 366 | 367 | int regionGroupIdx(const PosRegionIdx& ri) const {return RTGETLEVEL3(ri.z) * RTLEVEL3SIZE + RTGETLEVEL3(ri.x);} 368 | RegionGroup* getRegionGroup(const PosRegionIdx& ri) const {return regiongroups[regionGroupIdx(ri)];} 369 | RegionSet* getRegionSet(const PosRegionIdx& ri) const {RegionGroup *rg = getRegionGroup(ri); return (rg == NULL) ? NULL : rg->getRegionSet(ri);} 370 | 371 | // given indices into the RegionGroups/RegionSets/bitset, construct a PosRegionIdx 372 | static PosRegionIdx toPosRegionIdx(int rgi, int rsi, int bi); 373 | 374 | bool isRequired(const PosRegionIdx& ri) const {RegionSet *rs = getRegionSet(ri); return (rs == NULL) ? false : rs->bits[rs->bitIdx(ri)];} 375 | int getDiskState(const PosRegionIdx& ri) const {RegionSet *rs = getRegionSet(ri); return (rs == NULL) ? 0 : ((rs->bits[rs->bitIdx(ri)+1] ? 0x2 : 0) | (rs->bits[rs->bitIdx(ri)+2] ? 0x1 : 0));} 376 | 377 | void setRequired(const PosRegionIdx& ri); 378 | void setDiskState(const PosRegionIdx& ri, int state); 379 | 380 | void copyFrom(const RegionTable& rtable); 381 | }; 382 | 383 | 384 | 385 | #endif // TABLES_H -------------------------------------------------------------------------------- /blockimages.h: -------------------------------------------------------------------------------- 1 | // Copyright 2010-2012 Michael J. Nelson 2 | // 3 | // This file is part of pigmap. 4 | // 5 | // pigmap is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // pigmap is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with pigmap. If not, see . 17 | 18 | #ifndef BLOCKIMAGES_H 19 | #define BLOCKIMAGES_H 20 | 21 | #include 22 | 23 | #include "rgba.h" 24 | 25 | // IMPORTANT NOTE: 26 | // This program was written before the location of the sun moved in Minecraft Beta 1.9 or so, 27 | // therefore all of the N/S/E/W directions here are now wrong--rotated 90 degrees from what they 28 | // should be. For example, the positive X direction used to be South, and is called South here, 29 | // but is now East in the game (as of Minecraft 1.0, anyway). 30 | // I decided to leave the old direction names here, because it would be pretty easy to mess 31 | // something up trying to go through and change everything. Apologies for the confusion! 32 | 33 | // this structure holds the block images used to build the map; each block image is a hexagonal shape within 34 | // a 4Bx4B rectangle, with the unused area around it set to fully transparent 35 | // 36 | // example of hexagon shape for B = 3, where U represents pixels belonging to the U-facing side of the block, etc.: 37 | // 38 | // UU 39 | // UUUUUU 40 | // UUUUUUUUUU 41 | // NUUUUUUUUUUW 42 | // NNNUUUUUUWWW 43 | // NNNNNUUWWWWW 44 | // NNNNNNWWWWWW 45 | // NNNNNNWWWWWW 46 | // NNNNNNWWWWWW 47 | // NNNNNWWWWW 48 | // NNNWWW 49 | // NW 50 | // 51 | // when supplying your own block images, there's nothing to stop you from going "out of bounds" and having 52 | // non-transparent pixels outside the hexagon, but you'll just get a messed-up image, since the renderer 53 | // uses only the hexagon to determine visibility, etc. 54 | // 55 | // note that translucent blocks require the most work to render, simply because you can see what's behind them; 56 | // if every block in the world was translucent, for example, then every block would be considered visible 57 | // ...so if you're editing the block images for special purposes like X-ray vision, the fastest results are 58 | // obtained by making unwanted blocks fully transparent, not just translucent 59 | // ...also, any pixels in the block images with alphas < 10 will have their alphas set to 0, and similarly 60 | // any alphas > 245 will be set to 255; this is to prevent massive slowdown from accidental image-editing 61 | // cock-ups, like somehow setting the transparency of the whole image to 99% instead of 100%, etc. 62 | // 63 | // most block images are created by resizing the relevant terrain.png images from 16x16 to 2Bx2B, then painting 64 | // their columns onto the faces of the block image thusly (example is for B = 3 again): 65 | // 66 | // a f 67 | // abcdef ab abc def 68 | // abcdef aabbcd abcde bcdef 69 | // abcdef ---> aabbccddef or abcdef or abcdef 70 | // abcdef abccddeeff abcdef abcdef 71 | // abcdef cdeeff abcdef abcdef 72 | // abcdef ef bcdef abcde 73 | // def abc 74 | // f a 75 | 76 | struct BlockImages 77 | { 78 | // this image holds all the block images, in rows of 16 (so its width is 4B*16; height depends on number of rows) 79 | // ...the very first block image is a dummy one, fully transparent, for use with unrecognized blocks 80 | RGBAImage img; 81 | int rectsize; // size of block image bounding boxes 82 | 83 | // for every possible 12-bit block id/4-bit block data combination, this holds the offset into the image 84 | // (unrecognized id/data values are pointed at the dummy block image) 85 | // this doesn't handle some things like fences and double chests where the rendering doesn't depend solely 86 | // on the blockID/blockData; for those, the renderer just has to know the proper offsets on its own 87 | int blockOffsets[4096 * 16]; 88 | int getOffset(uint16_t blockID, uint8_t blockData) const {return blockOffsets[blockID * 16 + blockData];} 89 | 90 | // check whether a block image is opaque (this is a function of the block images computed from the terrain, 91 | // not of the actual block data; if a block image has 100% alpha everywhere, it's considered opaque) 92 | std::vector opacity; // size is NUMBLOCKIMAGES; indexed by offset 93 | bool isOpaque(int offset) const {return opacity[offset];} 94 | bool isOpaque(uint16_t blockID, uint8_t blockData) const {return opacity[getOffset(blockID, blockData)];} 95 | 96 | // ...and the same thing for complete transparency (0% alpha everywhere) 97 | std::vector transparency; // size is NUMBLOCKIMAGES; indexed by offset 98 | bool isTransparent(int offset) const {return transparency[offset];} 99 | bool isTransparent(uint16_t blockID, uint8_t blockData) const {return transparency[getOffset(blockID, blockData)];} 100 | 101 | // get the rectangle in img corresponding to an offset 102 | ImageRect getRect(int offset) const {return ImageRect((offset%16)*rectsize, (offset/16)*rectsize, rectsize, rectsize);} 103 | ImageRect getRect(uint16_t blockID, uint8_t blockData) const {return getRect(getOffset(blockID, blockData));} 104 | 105 | // attempt to create a BlockImages structure: look for blocks-B.png in the imgpath, where B is the block size 106 | // parameter; failing that, look for terrain.png and construct a new blocks-B.png from it; failing that, uh, fail 107 | bool create(int B, const std::string& imgpath); 108 | 109 | // set the offsets 110 | void setOffsets(); 111 | 112 | // fill in the opacity and transparency members 113 | void checkOpacityAndTransparency(int B); 114 | 115 | // scan the block images looking for not-quite-transparent or not-quite-opaque pixels; if they're close enough, 116 | // push them all the way 117 | void retouchAlphas(int B); 118 | 119 | // build block images from terrain.png, etc. 120 | bool construct(int B, const std::string& terrainfile, const std::string& firefile, const std::string& endportalfile, const std::string& chestfile, const std::string& largechestfile, const std::string& enderchestfile); 121 | }; 122 | 123 | // block image offsets: 124 | // 125 | // 0 dummy/air (transparent) 32 brown mushroom 64 wheat level 2 96 cobble stairs asc S 126 | // 1 stone 33 red mushroom 65 wheat level 1 97 cobble stairs asc N 127 | // 2 grass 34 gold block 66 wheat level 0 98 cobble stairs asc W 128 | // 3 dirt 35 iron block 67 farmland 99 cobble stairs asc E 129 | // 4 cobblestone 36 double stone slab 68 UNUSED 100 wall sign facing E 130 | // 5 planks 37 stone slab 69 UNUSED 101 wall sign facing W 131 | // 6 sapling 38 brick 70 sign facing N/S 102 wall sign facing N 132 | // 7 bedrock 39 TNT 71 sign facing NE/SW 103 wall sign facing S 133 | // 8 water full/falling 40 bookshelf 72 sign facing E/W 104 UNUSED 134 | // 9 water level 7 41 mossy cobblestone 73 sign facing SE/NW 105 UNUSED 135 | // 10 water level 6 42 obsidian 74 wood door S side 106 UNUSED 136 | // 11 water level 5 43 torch floor 75 wood door N side 107 UNUSED 137 | // 12 water level 4 44 torch pointing S 76 wood door W side 108 UNUSED 138 | // 13 water level 3 45 torch pointing N 77 wood door E side 109 UNUSED 139 | // 14 water level 2 46 torch pointing W 78 wood door top S 110 stone pressure plate 140 | // 15 water level 1 47 torch pointing E 79 wood door top N 111 iron door S side 141 | // 16 lava full/falling 48 UNUSED 80 wood door top W 112 iron door N side 142 | // 17 lava level 3 49 spawner 81 wood door top E 113 iron door W side 143 | // 18 lava level 2 50 wood stairs asc S 82 ladder E side 114 iron door E side 144 | // 19 lava level 1 51 wood stairs asc N 83 ladder W side 115 iron door top S 145 | // 20 sand 52 wood stairs asc W 84 ladder N side 116 iron door top N 146 | // 21 UNUSED 53 wood stairs asc E 85 ladder S side 117 iron door top W 147 | // 22 gold ore 54 UNUSED 86 track EW 118 iron door top E 148 | // 23 iron ore 55 redstone wire NSEW 87 track NS 119 wood pressure plate 149 | // 24 coal ore 56 diamond ore 88 UNUSED 120 redstone ore 150 | // 25 log 57 diamond block 89 UNUSED 121 red torch floor off 151 | // 26 leaves 58 workbench 90 UNUSED 122 red torch floor on 152 | // 27 sponge 59 wheat level 7 91 UNUSED 123 UNUSED 153 | // 28 glass 60 wheat level 6 92 track NE corner 124 UNUSED 154 | // 29 white wool 61 wheat level 5 93 track SE corner 125 UNUSED 155 | // 30 yellow flower 62 wheat level 4 94 track SW corner 126 UNUSED 156 | // 31 red rose 63 wheat level 3 95 track NW corner 127 snow 157 | // 158 | // 128 ice 160 fence NS 192 stone button facing W 224 dispenser N 159 | // 129 snow block 161 fence E 193 stone button facing E 225 dispenser E/S 160 | // 130 cactus 162 fence NE 194 wall lever facing S 226 sandstone 161 | // 131 clay 163 fence SE 195 wall lever facing N 227 note block 162 | // 132 reeds 164 fence NSE 196 wall lever facing W 228 UNUSED 163 | // 133 jukebox 165 fence W 197 wall lever facing E 229 sandstone slab 164 | // 134 fence post 166 fence NW 198 ground lever EW 230 wooden slab 165 | // 135 pumpkin facing W 167 fence SW 199 ground lever NS 231 cobble slab 166 | // 136 netherrack 168 fence NSW 200 track asc S 232 UNUSED 167 | // 137 soul sand 169 fence EW 201 track asc N 233 UNUSED 168 | // 138 glowstone 170 fence NEW 202 track asc E 234 UNUSED 169 | // 139 portal 171 fence SEW 203 track asc W 235 UNUSED 170 | // 140 jack-o-lantern W 172 fence NSEW 204 orange wool 236 UNUSED 171 | // 141 red torch S on 173 UNUSED 205 magenta wool 237 UNUSED 172 | // 142 red torch N on 174 UNUSED 206 light blue wool 238 UNUSED 173 | // 143 red torch E on 175 UNUSED 207 yellow wool 239 UNUSED 174 | // 144 red torch W on 176 UNUSED 208 lime wool 240 repeater on N 175 | // 145 red torch S off 177 UNUSED 209 pink wool 241 repeater on S 176 | // 146 red torch N off 178 water missing W 210 gray wool 242 repeater on E 177 | // 147 red torch E off 179 water missing N 211 light gray wool 243 repeater on W 178 | // 148 red torch W off 180 ice surface 212 cyan wool 244 repeater off N 179 | // 149 UNUSED 181 ice missing W 213 purple wool 245 repeater off S 180 | // 150 UNUSED 182 ice missing N 214 blue wool 246 repeater off E 181 | // 151 UNUSED 183 furnace W 215 brown wool 247 repeater off W 182 | // 152 UNUSED 184 furnace N 216 green wool 248 pine leaves 183 | // 153 pumpkin facing E/S 185 furnace E/S 217 red wool 249 birch leaves 184 | // 154 pumpkin facing N 186 lit furnace W 218 black wool 250 pine sapling 185 | // 155 jack-o-lantern E/S 187 lit furnace N 219 pine log 251 birch sapling 186 | // 156 jack-o-lantern N 188 lit furnace E/S 220 birch log 252 booster on EW 187 | // 157 water surface 189 fire 221 lapis ore 253 booster on NS 188 | // 158 fence N 190 stone button facing S 222 lapis block 254 booster on asc S 189 | // 159 fence S 191 stone button facing N 223 dispenser W 255 booster on asc N 190 | // 191 | // 256 booster on asc E 288 bed foot S 320 nether fence E 352 cauldron 1/3 full 192 | // 257 booster on asc W 289 cake 321 nether fence NE 353 cauldron 2/3 full 193 | // 258 booster off EW 290 melon 322 nether fence SE 354 cauldron full 194 | // 259 booster off NS 291 mycelium 323 nether fence NSE 355 iron bars NSEW 195 | // 260 booster off asc S 292 nether brick 324 nether fence W 356 iron bars NS 196 | // 261 booster off asc N 293 end stone 325 nether fence NW 357 iron bars NE 197 | // 262 booster off asc E 294 stone brick 326 nether fence SW 358 iron bars NW 198 | // 263 booster off asc W 295 mossy stone brick 327 nether fence NSW 359 iron bars SE 199 | // 264 detector EW 296 cracked stone brick 328 nether fence EW 360 iron bars SW 200 | // 265 detector NS 297 UNUSED 329 nether fence NEW 361 iron bars EW 201 | // 266 detector asc S 298 UNUSED 330 nether fence SEW 362 iron bars SEW 202 | // 267 detector asc N 299 UNUSED 331 nether fence NSEW 363 iron bars NEW 203 | // 268 detector asc E 300 UNUSED 332 nether fence post 364 iron bars NSW 204 | // 269 detector asc W 301 UNUSED 333 netherwart small 365 iron bars NSE 205 | // 270 locked chest facing W 302 brick slab 334 netherwart medium 366 glass pane NSEW 206 | // 271 locked chest facing N 303 stone brick slab 335 netherwart large 367 glass pane NS 207 | // 272 web 304 brick stairs asc S 336 mushroom flesh 368 glass pane NE 208 | // 273 tall grass 305 brick stairs asc N 337 red cap top only 369 glass pane NW 209 | // 274 fern 306 brick stairs asc W 338 red cap N 370 glass pane SE 210 | // 275 dead shrub 307 brick stairs asc E 339 red cap W 371 glass pane SW 211 | // 276 trapdoor closed 308 stone brick stairs S 340 red cap NW 372 glass pane EW 212 | // 277 trapdoor open W 309 stone brick stairs N 341 brown cap top only 373 glass pane SEW 213 | // 278 trapdoor open E 310 stone brick stairs W 342 brown cap N 374 glass pane NEW 214 | // 279 trapdoor open S 311 stone brick stairs E 343 brown cap W 375 glass pane NSW 215 | // 280 trapdoor open N 312 nether stairs asc S 344 brown cap NW 376 glass pane NSE 216 | // 281 bed head W 313 nether stairs asc N 345 mushroom stem 377 end portal 217 | // 282 bed head N 314 nether stairs asc W 346 fence gate EW 378 dragon egg 218 | // 283 bed head E 315 nether stairs asc E 347 fence gate NS 379 vines top only 219 | // 284 bed head S 316 lily pad 348 enchantment table 380 vines N 220 | // 285 bed foot W 317 nether fence N 349 end portal frame 381 vines S 221 | // 286 bed foot N 318 nether fence S 350 brewing stand 382 vines NS 222 | // 287 bed foot E 319 nether fence NS 351 cauldron empty 383 vines E 223 | // 224 | // 384 vines NE 416 closed sticky piston S 448 brick stairs inv W 480 ender chest facing N 225 | // 385 vines SE 417 closed sticky piston W 449 brick stairs inv E 481 ender chest facing E/S 226 | // 386 vines NSE 418 closed sticky piston E 450 stone brick stairs inv S 482 emerald block 227 | // 387 vines W 419 iron bars N 451 stone brick stairs inv N 483 gravel 228 | // 388 vines NW 420 iron bars S 452 stone brick stairs inv W 484 chest facing W 229 | // 389 vines SW 421 iron bars E 453 stone brick stairs inv E 485 chest facing N 230 | // 390 vines NSW 422 iron bars W 454 nether stairs inv S 486 chest facing E/S 231 | // 391 vines EW 423 glass pane N 455 nether stairs inv N 487 double chest N facing W 232 | // 392 vines NEW 424 glass pane S 456 nether stairs inv W 488 double chest S facing W 233 | // 393 vines SEW 425 glass pane E 457 nether stairs inv E 489 double chest E facing N 234 | // 394 vines NSEW 426 glass pane W 458 stone slab inv 490 double chest W facing N 235 | // 395 stem level 0 427 jungle log 459 sandstone slab inv 491 double chest N facing E 236 | // 396 stem level 1 428 jungle leaves 460 wooden slab inv 492 double chest S facing E 237 | // 397 stem level 2 429 jungle sapling 461 cobblestone slab inv 493 double chest E facing S 238 | // 398 stem level 3 430 circle stone brick 462 brick slab inv 494 double chest W facing S 239 | // 399 stem level 4 431 hieroglyphic sandstone 463 stone brick slab inv 495 pine stairs asc S 240 | // 400 stem level 5 432 smooth sandstone 464 pine slab 496 pine stairs asc N 241 | // 401 stem level 6 433 redstone lamp on 465 pine slab inv 497 pine stairs asc W 242 | // 402 stem level 7 434 redstone lamp off 466 birch slab 498 pine stairs asc E 243 | // 403 stem pointing N 435 pine planks 467 birch slab inv 499 pine stairs inv S 244 | // 404 stem pointing S 436 birch planks 468 jungle slab 500 pine stairs inv N 245 | // 405 stem pointing E 437 jungle planks 469 jungle slab inv 501 pine stairs inv W 246 | // 406 stem pointing W 438 wood stairs inv S 470 sandstone stairs asc S 502 pine stairs inv E 247 | // 407 closed piston D 439 wood stairs inv N 471 sandstone stairs asc N 503 birch stairs asc S 248 | // 408 closed piston U 440 wood stairs inv W 472 sandstone stairs asc W 504 birch stairs asc N 249 | // 409 closed piston N 441 wood stairs inv E 473 sandstone stairs asc E 505 birch stairs asc W 250 | // 410 closed piston S 442 cobble stairs inv S 474 sandstone stairs inv S 506 birch stairs asc E 251 | // 411 closed piston W 443 cobble stairs inv N 475 sandstone stairs inv N 507 birch stairs inv S 252 | // 412 closed piston E 444 cobble stairs inv W 476 sandstone stairs inv W 508 birch stairs inv N 253 | // 413 closed sticky piston D 445 cobble stairs inv E 477 sandstone stairs inv E 509 birch stairs inv W 254 | // 414 closed sticky piston U 446 brick stairs inv S 478 emerald ore 510 birch stairs inv E 255 | // 415 closed sticky piston N 447 brick stairs inv N 479 ender chest facing W 511 jungle stairs asc S 256 | // 257 | // 512 jungle stairs asc N 544 tripwire NS 258 | // 513 jungle stairs asc W 545 tripwire NE 259 | // 514 jungle stairs asc E 546 tripwire NW 260 | // 515 jungle stairs inv S 547 tripwire SE 261 | // 516 jungle stairs inv N 548 tripwire SW 262 | // 517 jungle stairs inv W 549 tripwire EW 263 | // 518 jungle stairs inv E 550 tripwire SEW 264 | // 519 cocoa level 0 stem N 551 tripwire NEW 265 | // 520 cocoa level 0 stem S 552 tripwire NSW 266 | // 521 cocoa level 0 stem E 553 tripwire NSE 267 | // 522 cocoa level 0 stem W 268 | // 523 cocoa level 1 stem N 269 | // 524 cocoa level 1 stem S 270 | // 525 cocoa level 1 stem E 271 | // 526 cocoa level 1 stem W 272 | // 527 cocoa level 2 stem N 273 | // 528 cocoa level 2 stem S 274 | // 529 cocoa level 2 stem E 275 | // 530 cocoa level 2 stem W 276 | // 531 log EW 277 | // 532 log NS 278 | // 533 pine log EW 279 | // 534 pine log NS 280 | // 535 birch log EW 281 | // 536 birch log NS 282 | // 537 jungle log EW 283 | // 538 jungle log NS 284 | // 539 tripwire hook S 285 | // 540 tripwire hook N 286 | // 541 tripwire hook W 287 | // 542 tripwire hook E 288 | // 543 tripwire NSEW 289 | 290 | #define NUMBLOCKIMAGES 554 291 | 292 | 293 | 294 | #endif // BLOCKIMAGES_H --------------------------------------------------------------------------------