├── 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