diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fae6a6e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.o +pigmap diff --git a/COPYING b/COPYING old mode 100755 new mode 100644 diff --git a/Makefile b/Makefile index 946daf5..2c3d824 100644 --- a/Makefile +++ b/Makefile @@ -1,29 +1,38 @@ -objects = pigmap.o blockimages.o chunk.o map.o render.o region.o rgba.o tables.o utils.o world.o - -pigmap : $(objects) - g++ $(objects) -o pigmap -l z -l png -l pthread -O3 - -pigmap.o : pigmap.cpp blockimages.h chunk.h map.h render.h rgba.h tables.h utils.h world.h - g++ -c pigmap.cpp -O3 -blockimages.o : blockimages.cpp blockimages.h rgba.h utils.h - g++ -c blockimages.cpp -O3 -chunk.o : chunk.cpp chunk.h map.h region.h tables.h utils.h - g++ -c chunk.cpp -O3 -map.o : map.cpp map.h utils.h - g++ -c map.cpp -O3 -render.o : render.cpp blockimages.h chunk.h map.h render.h rgba.h tables.h utils.h - g++ -c render.cpp -O3 -region.o : region.cpp map.h region.h tables.h utils.h - g++ -c region.cpp -O3 -rgba.o : rgba.cpp rgba.h utils.h - g++ -c rgba.cpp -O3 -tables.o : tables.cpp map.h tables.h utils.h - g++ -c tables.cpp -O3 -utils.o : utils.cpp utils.h - g++ -c utils.cpp -O3 -world.o : world.cpp map.h region.h tables.h world.h - g++ -c world.cpp -O3 - -clean : - rm -f *.o pigmap - \ No newline at end of file +objects = pigmap.o blockimages.o chunk.o map.o render.o region.o rgba.o tables.o utils.o world.o + +ifeq ($(mode),debug) + CFLAGS = -g -Wall -D_DEBUG +else ifeq ($(mode),profile) + CFLAGS = -Wall -O3 -DNDEBUG -pg +else ifeq ($(mode),coverage) + CFLAGS = -Wall -O3 -DNDEBUG -fprofile-arcs -ftest-coverage +else + CFLAGS = -Wall -O3 -DNDEBUG +endif + +pigmap : $(objects) + g++ $(objects) -o pigmap -l z -l png -l jpeg -l pthread $(CFLAGS) + +pigmap.o : pigmap.cpp blockimages.h chunk.h map.h render.h rgba.h tables.h utils.h world.h + g++ -c pigmap.cpp $(CFLAGS) +blockimages.o : blockimages.cpp blockimages.h rgba.h utils.h + g++ -c blockimages.cpp $(CFLAGS) -std=c++0x +chunk.o : chunk.cpp chunk.h map.h region.h tables.h utils.h + g++ -c chunk.cpp $(CFLAGS) +map.o : map.cpp map.h utils.h + g++ -c map.cpp $(CFLAGS) +render.o : render.cpp blockimages.h chunk.h map.h render.h rgba.h tables.h utils.h + g++ -c render.cpp $(CFLAGS) +region.o : region.cpp map.h region.h tables.h utils.h + g++ -c region.cpp $(CFLAGS) +rgba.o : rgba.cpp rgba.h utils.h + g++ -c rgba.cpp $(CFLAGS) +tables.o : tables.cpp map.h tables.h utils.h + g++ -c tables.cpp $(CFLAGS) +utils.o : utils.cpp utils.h + g++ -c utils.cpp $(CFLAGS) +world.o : world.cpp map.h region.h tables.h world.h + g++ -c world.cpp $(CFLAGS) + +clean : + rm -f *.o pigmap diff --git a/README b/README index 713de4f..51033ff 100644 --- a/README +++ b/README @@ -40,6 +40,24 @@ Use supplied makefile to build with g++. Change log (important stuff only): +%LATEST_VERSION% +-new block up to Minecraft 1.6 13w18c snapshot +-new textures should be added to image folder (or updated): + textures/blocks/blockCoal.png + textures/blocks/blockLapis.png + textures/blocks/clayHardened.png + textures/blocks/clayHardenedStained_[0-15].png + textures/blocks/hayBlock.png + textures/blocks/hayBlock_top.png + +1.5 +-fire.png, terrain.png, chest.png (and similar) are no longer required +-pigmap now works with upcoming Minecraft 1.5 textures +-folders /item, /textures/blocks from minecraft.jar or your texture pack are + required (in image path), including their contents +-new blocks up to 13w03a Minecraft snapshot (upcoming Minecraft 1.5) +-possibility to render custom blocks + 1.2 -new blocks up to Minecraft 1.3 -chest.png, etc. now required @@ -116,7 +134,7 @@ Usage examples: full render: -pigmap -B 6 -T 1 -Z 10 -i input/World1 -o output/World1 -g images -h 3 +pigmap -B 6 -T 1 -Z 10 -i input/World1 -o output/World1 -g images -t 3 ...builds a map with parameters B = 6, T = 1, baseZoom = 10, reading world data from the path "input/World1", writing tiles out to the path "output/World1", reading terrain images from the path @@ -124,7 +142,7 @@ pigmap -B 6 -T 1 -Z 10 -i input/World1 -o output/World1 -g images -h 3 incremental update: -pigmap -i input/World1 -o output/World1 -r regionlist -h 3 -x +pigmap -i input/World1 -o output/World1 -r regionlist -t 3 -x ...updates an existing map by redrawing any tiles touched by regions listed in the file "regionlist", with the input and output dirs as before. Terrain images are read from the path ".", and 3 threads @@ -168,7 +186,7 @@ incremental update; make a copy if you need to add fancier abilities to it. c. [optional (kind of)] image path (-g) -This is where pigmap expects to find either a copy of your tileset (terrain.png, etc.), or a copy of +This is where pigmap expects to find either a copy of your textures (texture/ folder of your minecraft.jar etc.), or a copy of blocks-B.png (substituting the actual numeric value of B; see below for definition of B), a pigmap-generated file that contains the isometric renderings of each block. These files are not optional, but the -g parameter itself may be omitted, in which case "." is used as the image path. @@ -187,22 +205,29 @@ This means that pigmap will need your tileset the first time it runs, so it can subsequent runs will use the existing blocks-B.png, which can be manually edited if insufficiently pretty, or for special effects, etc. -The following images are required to generate blocks-B.png: --terrain.png, chest.png, largechest.png, enderchest.png: these files come from your tileset - or minecraft.jar --fire.png, endportal.png: these files are included with pigmap -All of these must be RGBA png files (not just RGB). - -High-res tilesets are supported. The textures in terrain.png, fire.png, and endportal.png can be -any size, as long as they remain square. The textures in the chest pngs can be scaled up, but their -size must be an integer multiple of the original size. +The generate blocks-B.pngi, extract the item/ and textures/ folder of your minecraft.jar into the folder you have specified. +All of these must be RGBA png files (not just RGB). So check the alpha channels, if one file is not recognized. From minecraft version 1.5 it is known that the lava.png is missing an alpha channel. You can simply add one by using tools like GIMP. -d. [optional] number of threads (-h) +d. [optional] number of threads (-t) Defaults to 1. Each thread requires around 250-300 MB of RAM (they work in different areas of the map and keep separate caches of chunk data). Returns from extra threads may diminish quickly as the disk becomes a bottleneck. +e. [optional] output image file format (-f) + +Defaults to png. The output is either done as png files, jpeg files or both. Jpeg can be compressed to +smaller file sizes than png and may be preferrable for web use, but incremental rendering only reads +png files, since jpeg is a lossy format. The "both" option is supplied to deal with this situation - +both file formats will be written, jpeg used in the html map and png files can be kept for future +incremental renders. + +f. [optional] Jpeg quality (-j) + +Defaults to 75. A number between 1 and 100 that determines what quality level jpeg files are written. +Higher means better quality but larger file sizes. Has no effect (obviously) if the output file format +is not set to jpeg or both. + 2. Params for full renders only: @@ -312,4 +337,4 @@ furnaces: 68->183, 69->186, 149->184, 150->185, 151->187, 152->188 fire: 48->189 buttons: 123->190, 124->191, 125->192, 126->193 levers: 104->194, 105->195, 106->196, 107->197, 108->198, 109->199 -ascending tracks: 88->200, 89->201, 90->202, 91->203 \ No newline at end of file +ascending tracks: 88->200, 89->201, 90->202, 91->203 diff --git a/blockdescriptor.list b/blockdescriptor.list new file mode 100644 index 0000000..3fbdce0 --- /dev/null +++ b/blockdescriptor.list @@ -0,0 +1,214 @@ +# blockdescriptor.list +# +# block types: +# 0 SOLID (specify texture for all faces, all textures for west face, south face, top face; like any solid block) +# 1 SOLIDORIENTED (solid blocks, that can be oriented N,S,W,E but not U or D; specify list of bits for N,S,W,E, then textures for face, side, top; like dispensers, furnaces, pumpkins, jack 'o' lanterns) +# 2 SOLIDROTATED (solid blocks, that can be oriented in any direction; specify top(face), side, bottom(opposite to face) textures ;like pistons) +# 3 SOLIDDATA (list all textures in order of data bits; like wooden planks, wool, wooden double slabs) +# 4 SOLIDDATAFILL (like SOLIDDATA, but "tiles" existing data over remaining bits(if any); like leaves) +# 5 SOLIDDATATRUNK (specify pairs of textures for top and sides for each data bit; like sandstone, double slabs) +# 6 SOLIDDATATRUNKROTATED (specify bit spacing method (S - each orientation spaced based on group name, O - each orientation is stored in order of group appearance), then list of groups with flag (if this data group should be oriented), top texture, side texture; like wood logs) +# 7 SOLIDOBSTRUCTED (solid block with missing faces due to obstruction; like ice blocks) +# 8 SOLIDPARTIAL (list top cutoff, bottom cutoff, then Wface, Sface, Uface; cutoff is specified by number 0 - 16(which is ridiculous; like cake, enchanting table(getto version)) +# 8a SOLIDDATAPARTIAL (list top cutoff, bottom cutoff, then list textures for each data bit; like carpet) +# 9 SOLIDDATAPARTIALFILL (list Wface, Sface, Uface, then specify cutoffs for data bits in a sequence; like snow) +# 10 SOLIDTRANSPARENT (draws all faces, in combination with empty tile, interesting results can be achieved; specify texture for each block face - D, N, E, W, S, U; like fire) +# 11 SLABDATA (specify texture for all faces of slab for each data bit; like wooden slabs) +# 12 SLABDATATRUNK (specify pairs of textures for top and sides for each data bit; like slabs) +# 13 ITEMDATA (list textures for each data bit; like saplings, wheat, carrots, potatoes, tall grass, dead brush, reeds) +# 14 ITEMDATAORIENTED (list N,S,W,E orientation bit indexes, then list of textures per data orientation group; like cocoa pods) +# 15 ITEMDATAFILL (used for such items as sapling; behaves like ITEMDATA, but if list of textures is not full (up to 16), it will "tile" existing data over remaining bits) +# 16 MULTIITEMDATA (list textures for each data bit; like nether wart) +# 17 STAIR (list textures for: side, top OR side (if all faces has the same texture); stairs in all it's glory) +# 18 FENCE (specify base texture; like fence, nether brick fence) +# 19 WALLDATA (specify base texture for each data bit; like cobblestone wall, mossy cobblestone wall) +# 20 FENCEGATE (specify base texture for fence gate; like fence gate) +# 21 MUSHROOM (specify pore, stem, cap textures; like huge brown / red mushrooms) +# 22 CHEST (specify chest texture, large chest texture(don't specify it, if chest is not double chest); like chest, ender chest, trapped chest) +# 23 RAIL (specify rail texture, corner texture(for cornered tracks); like rails, detector rails) +# 24 RAILPOWERED (specify rail texture(off), rail texture(on); like powered rails, activator rails) +# 25 PANEDATA (specify base texture for each data bit; like iron bars, glass pane) +# 26 DOOR (specify textures for lower and bottom part of the classic 2x1 door; like wooden door, iron door) +# 27 TRAPDOOR (specify texture for trap door +# 28 TORCH (specify base texture of the torch; like torches, redstone torches) +# 29 ONWALLPARTIAL (specify list of bits indexes for N,S,W,E attachments, base texture, cuttoffs for N,S,W,E (tile orientation, not world orientation!); like ladders) +# 30 ONWALLPARTIALFILL (specify list of bits indexes for N,S,W,E attachments, base texture, cuttoffs for N,S,W,E (tile orientation, not world orientation!); like buttons, tripwire hooks) +# 31 WIRE (specify wire texture, wire crossing texture (if any); like redstone wire, tripwire) +# 32 BITANCHOR (specify face texture; draws faces anchored to different block side for each data bit combination; like vines) +# 33 STEM (specify straight stem texture, bent stem texture; like pumpkin stem, melon stem) +# 34 LEVER (specify lever base texture, lever handle texture; like lever) +# 35 SIGNPOST (specify sign base texture, sign pole texture; like sign post) +# 36 SPECIAL (handled by id, like brewing stand, dragon egg, beacon, stonewall, flowerpot, anvil, etc) +# +# You can use / as a texture name to use empty tile +# +# blockid blocktype * +1 SOLID stone +2 SOLID grass_side grass_side grass_top +3 SOLID dirt +4 SOLID stonebrick #cobblestone +5 SOLIDDATA wood wood_spruce wood_birch wood_jungle # oak / spruce / birch / jungle planks +6 ITEMDATAFILL sapling sapling_spruce sapling_birch sapling_jungle #sapling +7 SOLID bedrock +8 SPECIAL water +##9 - skip, it's water too +10 SPECIAL lava +##11 - skip, it's lava too +12 SOLID sand +13 SOLID gravel +14 SOLID oreGold +15 SOLID oreIron +16 SOLID oreCoal +17 SOLIDDATATRUNKROTATED S 1 tree_top tree_side 1 tree_top tree_spruce 1 tree_top tree_birch 1 tree_top tree_jungle # oak / spruce / birch / jungle wood +18 SOLIDDATAFILL leaves leaves_spruce leaves_birch leaves_jungle # oak / spruce / birch / jungle leaves +19 SOLID sponge +20 SOLID glass +21 SOLID oreLapis +22 SOLID blockLapis +23 SOLIDORIENTED 2 3 4 5 dispenser_front furnace_side furnace_top #dispenser +24 SOLIDDATATRUNK sandstone_top sandstone_side sandstone_top sandstone_carved sandstone_top sandstone_smooth #sandstone +25 SOLID musicBlock #note block +26 SPECIAL bed_feet_end bed_feet_side bed_feet_top bed_head_end bed_head_side bed_head_top #bed +27 RAILPOWERED goldenRail goldenRail_powered #powered rail +28 RAIL detectorRail #detector rail +29 SOLIDROTATED piston_top_sticky piston_side piston_bottom #sticky piston +30 ITEMDATA web #cobweb +31 ITEMDATA deadbush tallgrass fern #tall grass +32 ITEMDATA deadbush #dead bush +33 SOLIDROTATED piston_top piston_side piston_bottom #piston +##34 technical block +35 SOLIDDATA cloth_0 cloth_1 cloth_2 cloth_3 cloth_4 cloth_5 cloth_6 cloth_7 cloth_8 cloth_9 cloth_10 cloth_11 cloth_12 cloth_13 cloth_14 cloth_15 #wool +##36 technical block +37 ITEMDATA flower #dandelion +38 ITEMDATA rose #rose +39 ITEMDATA mushroom_brown +40 ITEMDATA mushroom_red +41 SOLID blockGold +42 SOLID blockIron +43 SOLIDDATATRUNK stoneslab_top stoneslab_side sandstone_top sandstone_side wood wood stonebrick stonebrick brick brick stonebricksmooth stonebricksmooth netherBrick netherBrick #double slabs +44 SLABDATATRUNK stoneslab_top stoneslab_side sandstone_top sandstone_side wood wood stonebrick stonebrick brick brick stonebricksmooth stonebricksmooth netherBrick netherBrick #slabs +45 SOLID brick +46 SOLID tnt_side tnt_side tnt_top #tnt +47 SOLID bookshelf bookshelf wood #bookshelf +48 SOLID stoneMoss +49 SOLID obsidian +50 TORCH torch +51 SOLIDTRANSPARENT / fire_0 fire_0 fire_0 fire_0 / #fire +52 SOLID mobSpawner +53 STAIR wood #oak wood stairs +54 CHEST /chest /largechest #chest +55 WIRE redstoneDust_line redstoneDust_cross #redstone wire +56 SOLID oreDiamond +57 SOLID blockDiamond +58 SOLID workbench_front workbench_side workbench_top #workbench +59 ITEMDATA crops_0 crops_1 crops_2 crops_3 crops_4 crops_5 crops_6 crops_7 #wheat +60 SOLID dirt dirt farmland_dry #farmland +61 SOLIDORIENTED 2 3 4 5 furnace_front furnace_side furnace_top #furnace +62 SOLIDORIENTED 2 3 4 5 furnace_front_lit furnace_side furnace_top #burning furnace +63 SIGNPOST wood wood #sign post +64 DOOR doorWood_lower doorWood_upper #wooden door +65 ONWALLPARTIALFILL 3 2 5 4 ladder 0 0 0 0 #ladder +66 RAIL rail rail_turn #rails +67 STAIR stonebrick #cobblestone stairs +68 ONWALLPARTIALFILL 3 2 5 4 wood 4 4 0 0 #wall sign +69 LEVER stonebrick lever +70 SOLIDPARTIAL 14 0 stone stone stone #stone pressure plate +71 DOOR doorIron_lower doorIron_upper #iron door +72 SOLIDPARTIAL 14 0 wood wood wood #wooden pressure plate +73 SOLID oreRedstone +74 SOLID oreRedstone #glowing +75 TORCH redtorch +76 TORCH redtorch_lit +77 ONWALLPARTIALFILL 3 4 1 2 stone 6 6 5 5 #stone button +78 SOLIDDATAPARTIALFILL snow snow snow 14 0 12 0 10 0 8 0 6 0 4 0 2 0 0 0 #snow +79 SOLIDOBSTRUCTED ice +80 SOLID snow +81 SOLID cactus_side cactus_side cactus_top #cactus +82 SOLID clay +83 ITEMDATA reeds #d'uh! +84 SOLID musicBlock musicBlock jukebox_top #jukebox +85 FENCE wood #fence +86 SOLIDORIENTED 2 0 1 3 pumpkin_face pumpkin_side pumpkin_top #pumpkin +87 SOLID hellrock #netherrack +88 SOLID hellsand #soul sand +89 SOLID lightgem #glowstone block +90 SOLID portal #nether portal +91 SOLIDORIENTED 2 0 1 3 pumpkin_jack pumpkin_side pumpkin_top #jack 'o' lantern +92 SOLIDPARTIAL 8 0 cake_side cake_side cake_top #cake +93 REPEATER repeater redtorch #repeater inactive +94 REPEATER repeater_lit redtorch_lit #repeater active +##95 locked chest isn't actually in a game +96 TRAPDOOR trapdoor +97 SOLIDDATA stone stonebrick stonebricksmooth # smooth / cobblestone / stone brick monster egg +98 SOLIDDATA stonebricksmooth stonebricksmooth_mossy stonebricksmooth_cracked stonebricksmooth_carved # normal / mossy / cracked / chiseled stone bricks +99 MUSHROOM mushroom_inside mushroom_skin_stem mushroom_skin_brown #huge brown mushroom +100 MUSHROOM mushroom_inside mushroom_skin_stem mushroom_skin_red #huge red mushroom +101 PANEDATA fenceIron #iron bars +102 PANEDATA glass #glass pane +103 SOLID melon_side melon_side melon_top #melon +104 STEM /stem_straight /stem_bent #pumpkin stem +105 STEM /stem_straight /stem_bent #melon stem +106 BITANCHOR vine #vines +107 FENCEGATE wood #fence gate +108 STAIR brick #brick stairs +109 STAIR stonebricksmooth #stone brick stairs +110 SOLID mycel_side mycel_side mycel_top #mycelium +111 SOLIDPARTIAL 16 0 / / waterlily #lily pad +112 SOLID netherBrick +113 FENCE netherBrick #nether brick fence +114 STAIR netherBrick #nether brick stairs +115 MULTIITEMDATA netherStalk_0 netherStalk_1 netherStalk_1 netherStalk_2 #nether wart +116 SOLIDPARTIAL 4 0 enchantment_side enchantment_side enchantment_top #enchantment table +117 SPECIAL brewingStand_base brewingStand #brewing stand +118 SPECIAL cauldron_side water #cauldron +119 SOLIDPARTIAL 4 0 / / endportal #end portal +120 SOLIDPARTIAL 3 0 endframe_side endframe_side endframe_top #end portal frame +121 SOLID whiteStone #endstone +122 SPECIAL dragonEgg #dragon egg +# 1.2 +123 SOLID redstoneLight +124 SOLID redstoneLight_lit +# 1.3 +125 SOLIDDATA wood wood_spruce wood_birch wood_jungle #wooden double slabs +126 SLABDATA wood wood_spruce wood_birch wood_jungle #wooden slabs +127 ITEMDATAORIENTED 0 2 3 1 cocoa_0 cocoa_1 cocoa_2 #cocoa pods +128 STAIR sandstone_side sandstone_top #sandstone stairs +129 SOLID oreEmerald +130 CHEST /enderchest #enderchest +131 ONWALLPARTIALFILL 0 2 3 1 tripWireSource 0 0 0 0 #tripwire hook +132 WIRE tripWire #tripwire +133 SOLID blockEmerald +134 STAIR wood_spruce #spruce wood stairs +135 STAIR wood_birch #birch wood stairs +136 STAIR wood_jungle #jungle wood stairs +# 1.4 +137 SOLID commandBlock +138 SPECIAL beacon obsidian 20 #beacon +139 WALLDATA stonebrick stoneMoss #cobblestone / mossy cobblestone wall +140 SPECIAL flowerPot dirt rose flower sapling sapling_spruce sapling_birch sapling_jungle mushroom_red mushroom_brown cactus_side deadbush fern #flower pot +141 MULTIITEMDATA carrots_0 carrots_0 carrots_1 carrots_1 carrots_2 carrots_2 carrots_2 carrots_3 #carrots +142 MULTIITEMDATA potatoes_0 potatoes_0 potatoes_1 potatoes_1 potatoes_2 potatoes_2 potatoes_2 potatoes_3 #potatoes +143 ONWALLPARTIALFILL 3 4 1 2 wood 6 6 5 5 #wooden button +##144 mob heads +145 SPECIAL anvil_base anvil_top anvil_top_damaged_1 anvil_top_damaged_2 #anvil +# 1.5 +146 CHEST /trap_small /trap_large #trapped chest +147 SOLIDPARTIAL 14 0 blockGold blockGold blockGold #weighted pressure plate (light) +148 SOLIDPARTIAL 14 0 blockIron blockIron blockIron #weighted pressure plate (heavy) +149 REPEATER comparator redtorch #repeater inactive +150 REPEATER comparator_lit redtorch_lit #repeater active +151 SOLIDPARTIAL 10 0 daylightDetector_side daylightDetector_side daylightDetector_top #daylight sensor +152 SOLID blockRedstone +153 SOLID netherquartz +154 SPECIAL hopper hopper_inside hopper_top #hopper +155 SOLIDDATATRUNKROTATED O 0 quartzblock_top quartzblock_side 0 quartzblock_chiseled_top quartzblock_chiseled 1 quartzblock_lines_top quartzblock_lines #block of quartz +156 STAIR quartzblock_side quartzblock_top #quartz stairs +157 RAILPOWERED activatorRail activatorRail_powered #activator rail +158 SOLIDORIENTED 2 3 4 5 dropper_front furnace_side furnace_top #dropper +# 1.6 +159 SOLIDDATA clayHardenedStained_0 clayHardenedStained_1 clayHardenedStained_2 clayHardenedStained_3 clayHardenedStained_4 clayHardenedStained_5 clayHardenedStained_6 clayHardenedStained_7 clayHardenedStained_8 clayHardenedStained_9 clayHardenedStained_10 clayHardenedStained_11 clayHardenedStained_12 clayHardenedStained_13 clayHardenedStained_14 clayHardenedStained_15 #stained hardened clay +##169-169 unused +170 SOLIDDATATRUNKROTATED S 1 hayBlock_top hayBlock 1 hayBlock_top hayBlock 1 hayBlock_top hayBlock 1 hayBlock_top hayBlock #hay block +171 SOLIDDATAPARTIAL 15 0 cloth_0 cloth_1 cloth_2 cloth_3 cloth_4 cloth_5 cloth_6 cloth_7 cloth_8 cloth_9 cloth_10 cloth_11 cloth_12 cloth_13 cloth_14 cloth_15 #carpet +172 SOLID clayHardened +173 SOLID blockCoal \ No newline at end of file diff --git a/blockimages.cpp b/blockimages.cpp old mode 100755 new mode 100644 index d9697c1..d68fa8d --- a/blockimages.cpp +++ b/blockimages.cpp @@ -1,2785 +1,3205 @@ -// Copyright 2010-2012 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#include -#include - -#include "blockimages.h" -#include "utils.h" - -using namespace std; - - -// in this file, confusingly, "tile" refers to the tiles of terrain.png, not to the map tiles -// -// also, this is a nasty mess in here; apologies to anyone reading this - - -void writeBlockImagesVersion(int B, const string& imgpath, int32_t version) -{ - string versionfile = imgpath + "/blocks-" + tostring(B) + ".version"; - ofstream outfile(versionfile.c_str()); - outfile << version; -} - -// get the version number associated with blocks-B.png; this is stored -// in blocks-B.version, which is just a single string with the version number -int getBlockImagesVersion(int B, const string& imgpath) -{ - string versionfile = imgpath + "/blocks-" + tostring(B) + ".version"; - ifstream infile(versionfile.c_str()); - // if there's no version file, assume the version is 157, which is how many - // blocks there were at the first "release" (before the version file was in use) - if (infile.fail()) - { - infile.close(); - writeBlockImagesVersion(B, imgpath, 157); - return 157; - } - // otherwise, read the version - int32_t v; - infile >> v; - // if the version is clearly insane, ignore it - if (v < 0 || v > 10000) - v = 0; - return v; -} - - - -bool BlockImages::create(int B, const string& imgpath) -{ - rectsize = 4*B; - setOffsets(); - - // first, see if blocks-B.png exists, and what its version is - int biversion = getBlockImagesVersion(B, imgpath); - string blocksfile = imgpath + "/blocks-" + tostring(B) + ".png"; - RGBAImage oldimg; - bool preserveold = false; - if (img.readPNG(blocksfile)) - { - // if it's the correct size and version, we're okay - int w = rectsize*16, h = (NUMBLOCKIMAGES/16 + 1) * rectsize; - if (img.w == w && img.h == h && biversion == NUMBLOCKIMAGES) - { - retouchAlphas(B); - checkOpacityAndTransparency(B); - return true; - } - // if it's a previous version (and the correct size for that version), we'll - // use terrain.png to build the new blocks, but preserve the existing ones - if (biversion < NUMBLOCKIMAGES && img.w == w && img.h == (biversion/16 + 1) * rectsize) - { - oldimg = img; - preserveold = true; - cerr << blocksfile << " is missing some blocks; will try to fill them in from terrain.png" << endl; - } - // otherwise, the file's been trashed somehow; rebuild it - else - { - cerr << blocksfile << " has incorrect size (expected " << w << "x" << h << endl; - cerr << "...will try to create from terrain.png, but without overwriting " << blocksfile << endl; - } - } - else - cerr << blocksfile << " not found (or failed to read as PNG); will try to build from terrain.png" << endl; - - // build blocks-B.png from terrain.png and fire.png - string terrainfile = imgpath + "/terrain.png"; - string firefile = imgpath + "/fire.png"; - string endportalfile = imgpath + "/endportal.png"; - string chestfile = imgpath + "/chest.png"; - string largechestfile = imgpath + "/largechest.png"; - string enderchestfile = imgpath + "/enderchest.png"; - if (!construct(B, terrainfile, firefile, endportalfile, chestfile, largechestfile, enderchestfile)) - { - cerr << "image path is missing at least one of these required files:" << endl; - cerr << "terrain.png, chest.png, largechest.png, enderchest.png -- from minecraft.jar or your tile pack" << endl; - cerr << "fire.png, endportal.png -- included with pigmap" << endl; - return false; - } - - // if we need to preserve the old version's blocks, copy them over - if (preserveold) - { - for (int i = 0; i < biversion; i++) - { - ImageRect rect = getRect(i); - blit(oldimg, rect, img, rect.x, rect.y); - } - } - - // write blocks-B.png and blocks-B.version - img.writePNG(blocksfile); - writeBlockImagesVersion(B, imgpath, NUMBLOCKIMAGES); - - retouchAlphas(B); - checkOpacityAndTransparency(B); - return true; -} - - - - -// given terrain.png, resize it so every texture becomes 2Bx2B instead of 16x16 (or whatever the actual -// texture size is) -// ...so the resulting image will be a 16x16 array of 2Bx2B images -RGBAImage getResizedTerrain(const RGBAImage& terrain, int terrainSize, int B) -{ - int newsize = 2*B; - RGBAImage img; - img.create(16*newsize, 16*newsize); - for (int y = 0; y < 16; y++) - for (int x = 0; x < 16; x++) - resize(terrain, ImageRect(x*terrainSize, y*terrainSize, terrainSize, terrainSize), - img, ImageRect(x*newsize, y*newsize, newsize, newsize)); - return img; -} - -// take the various textures from chest.png and use them to construct "flat" 14x14 tiles (or whatever -// the multiplied size is, if the textures are larger), then resize those flat images to 2Bx2B -// ...the resulting image will be a 3x1 array of 2Bx2B images: first the top, then the front, then -// the side -RGBAImage getResizedChest(const RGBAImage& chest, int scale, int B) -{ - int chestSize = 14 * scale; - RGBAImage chesttiles; - chesttiles.create(chestSize*3, chestSize); - - // top texture just gets copied straight over - blit(chest, ImageRect(14*scale, 0, 14*scale, 14*scale), chesttiles, 0, 0); - - // front tile gets the front lid texture plus the front bottom texture, then the latch on - // top of that - blit(chest, ImageRect(14*scale, 14*scale, 14*scale, 4*scale), chesttiles, chestSize, 0); - blit(chest, ImageRect(14*scale, 33*scale, 14*scale, 10*scale), chesttiles, chestSize, 4*scale); - blit(chest, ImageRect(scale, scale, 2*scale, 4*scale), chesttiles, chestSize + 6*scale, 2*scale); - - // side tile gets the side lid texture plus the side bottom texture - blit(chest, ImageRect(28*scale, 14*scale, 14*scale, 4*scale), chesttiles, chestSize*2, 0); - blit(chest, ImageRect(28*scale, 33*scale, 14*scale, 10*scale), chesttiles, chestSize*2, 4*scale); - - int newsize = 2*B; - RGBAImage img; - img.create(3*newsize, newsize); - for (int x = 0; x < 3; x++) - resize(chesttiles, ImageRect(x*chestSize, 0, chestSize, chestSize), - img, ImageRect(x*newsize, 0, newsize, newsize)); - return img; -} - -// same thing for largechest.png--construct flat tiles, then resize -// ...resulting image is a 7x1 array of 2Bx2B images: -// -left half of top -// -right half of top -// -left half of front -// -right half of front -// -left half of back -// -right half of back -// -side -RGBAImage getResizedLargeChest(const RGBAImage& chest, int scale, int B) -{ - int newsize = 2*B; - RGBAImage img; - img.create(7*newsize, newsize); - - // top texture gets copied straight over--note that the original texture is 30x14, but - // we're putting it into two squares - resize(chest, ImageRect(14*scale, 0, 30*scale, 14*scale), img, ImageRect(0, 0, newsize*2, newsize)); - - // front tile gets the front lid texture plus the front bottom texture, then the latch - // on top of that - RGBAImage fronttiles; - fronttiles.create(30*scale, 14*scale); - blit(chest, ImageRect(14*scale, 14*scale, 30*scale, 4*scale), fronttiles, 0, 0); - blit(chest, ImageRect(14*scale, 33*scale, 30*scale, 10*scale), fronttiles, 0, 4*scale); - blit(chest, ImageRect(scale, scale, 2*scale, 4*scale), fronttiles, 14*scale, 2*scale); - // do two resizes, to make sure the special end processing picks up the latch - resize(fronttiles, ImageRect(0, 0, 15*scale, 14*scale), img, ImageRect(2*newsize, 0, newsize, newsize)); - resize(fronttiles, ImageRect(15*scale, 0, 15*scale, 14*scale), img, ImageRect(3*newsize, 0, newsize, newsize)); - - // back tile gets the back lid texture plus the back bottom texture - RGBAImage backtiles; - backtiles.create(30*scale, 14*scale); - blit(chest, ImageRect(58*scale, 14*scale, 30*scale, 4*scale), backtiles, 0, 0); - blit(chest, ImageRect(58*scale, 33*scale, 30*scale, 10*scale), backtiles, 0, 4*scale); - resize(backtiles, ImageRect(0, 0, 30*scale, 14*scale), img, ImageRect(4*newsize, 0, 2*newsize, newsize)); - - // side tile gets the side lid texture plus the side bottom texture - RGBAImage sidetile; - sidetile.create(14*scale, 14*scale); - blit(chest, ImageRect(44*scale, 14*scale, 14*scale, 4*scale), sidetile, 0, 0); - blit(chest, ImageRect(44*scale, 33*scale, 14*scale, 10*scale), sidetile, 0, 4*scale); - resize(sidetile, ImageRect(0, 0, 14*scale, 14*scale), img, ImageRect(6*newsize, 0, newsize, newsize)); - - return img; -} - - - -// iterate over the pixels of a 2B-sized terrain tile; used for both source rectangles and -// destination parallelograms -struct FaceIterator -{ - bool end; // true if we're done - int x, y; // current pixel - int pos; - - int size; // number of columns to draw, as well as number of pixels in each - int deltaY; // amount to skew y-coord every 2 columns: -1 or 1 for E/W or N/S facing destinations, 0 for source - - FaceIterator(int xstart, int ystart, int dY, int sz) - { - size = sz; - deltaY = dY; - end = false; - x = xstart; - y = ystart; - pos = 0; - } - - void advance() - { - pos++; - if (pos >= size*size) - { - end = true; - return; - } - y++; - if (pos % size == 0) - { - x++; - y -= size; - if (pos % (2*size) == size) - y += deltaY; - } - } -}; - -// like FaceIterator with no deltaY (for source rectangles), but with the source rotated and/or flipped -struct RotatedFaceIterator -{ - bool end; - int x, y; - int pos; - - int size; - int rot; // 0 = down, then right; 1 = left, then down; 2 = up, then left; 3 = right, then up - bool flipX; - int dx1, dy1, dx2, dy2; - - RotatedFaceIterator(int xstart, int ystart, int r, int sz, bool fX) - { - size = sz; - rot = r; - flipX = fX; - end = false; - pos = 0; - if (rot == 0) - { - x = flipX ? (xstart + size - 1) : xstart; - y = ystart; - dx1 = 0; - dy1 = 1; - dx2 = flipX ? -1 : 1; - dy2 = 0; - } - else if (rot == 1) - { - x = flipX ? xstart : (xstart + size - 1); - y = ystart; - dx1 = flipX ? 1 : -1; - dy1 = 0; - dx2 = 0; - dy2 = 1; - } - else if (rot == 2) - { - x = flipX ? xstart : (xstart + size - 1); - y = ystart + size - 1; - dx1 = 0; - dy1 = -1; - dx2 = flipX ? 1 : -1; - dy2 = 0; - } - else - { - x = flipX ? (xstart + size - 1) : xstart; - y = ystart + size - 1; - dx1 = flipX ? -1 : 1; - dy1 = 0; - dx2 = 0; - dy2 = -1; - } - } - - void advance() - { - pos++; - if (pos >= size*size) - { - end = true; - return; - } - x += dx1; - y += dy1; - if (pos % size == 0) - { - x += dx2; - y += dy2; - x -= dx1 * size; - y -= dy1 * size; - } - } -}; - -// iterate over the pixels of the top face of a block -struct TopFaceIterator -{ - bool end; // true if we're done - int x, y; // current pixel - int pos; - - int size; // number of "columns", and number of pixels in each - - TopFaceIterator(int xstart, int ystart, int sz) - { - size = sz; - end = false; - x = xstart; - y = ystart; - pos = 0; - } - - void advance() - { - if ((pos/size) % 2 == 0) - { - int m = pos % size; - if (m == size - 1) - { - x += size - 1; - y -= size/2; - } - else if (m == size - 2) - y++; - else if (m % 2 == 0) - { - x--; - y++; - } - else - x--; - } - else - { - int m = pos % size; - if (m == 0) - y++; - else if (m == size - 1) - { - x += size - 1; - y -= size/2 - 1; - } - else if (m % 2 == 0) - { - x--; - y++; - } - else - x--; - } - pos++; - if (pos >= size*size) - end = true; - } -}; - - -struct SourceTile -{ - const RGBAImage *image; // or NULL for no tile - int xpos, ypos; // tile offset within the image - int rot; - bool flipX; - - SourceTile(const RGBAImage *img, int x, int y, int r, bool f) : image(img), xpos(x), ypos(y), rot(r), flipX(f) {} - SourceTile() : image(NULL), xpos(0), ypos(0), rot(0), flipX(false) {} - bool valid() const {return image != NULL;} -}; - -// iterate over a square source tile, with possible rotation and flip -struct SourceIterator -{ - SourceIterator(const SourceTile& tile, int tilesize) - : image(*(tile.image)), faceit(tile.xpos*tilesize, tile.ypos*tilesize, tile.rot, tilesize, tile.flipX) {} - - void advance() {faceit.advance();} - bool end() {return faceit.end;} - RGBAPixel pixel() {return image(faceit.x, faceit.y);} - - const RGBAImage& image; - RotatedFaceIterator faceit; -}; - -// construct a source iterator for a given terrain.png tile with rotation and/or flip -SourceTile terrainTile(const RGBAImage& tiles, int tile, int rot, bool flipX) -{ - if (tile < 0) - return SourceTile(); - return SourceTile(&tiles, tile%16, tile/16, rot, flipX); -} - -// construct a source iterator for a terrain.png tile with no rotation/flip -SourceTile terrainTile(const RGBAImage& tiles, int tile) -{ - return terrainTile(tiles, tile, 0, false); -} - - - -// draw a normal block image, using three terrain tiles (which may be flipped/rotated/missing), and adding a bit of shadow -// to the N and W faces -void drawRotatedBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& Nface, const SourceTile& Wface, const SourceTile& Uface, int B) -{ - int tilesize = 2*B; - // N face starts at [0,B] - if (Nface.valid()) - { - FaceIterator dstit(drect.x, drect.y + B, 1, tilesize); - for (SourceIterator srcit(Nface, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - dest(dstit.x, dstit.y) = srcit.pixel(); - darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); - } - } - // W face starts at [2B,2B] - if (Wface.valid()) - { - FaceIterator dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); - for (SourceIterator srcit(Wface, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - dest(dstit.x, dstit.y) = srcit.pixel(); - darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); - } - } - // U face starts at [2B-1,0] - if (Uface.valid()) - { - TopFaceIterator dstit(drect.x + 2*B-1, drect.y, tilesize); - for (SourceIterator srcit(Uface, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - dest(dstit.x, dstit.y) = srcit.pixel(); - } - } -} - -// overload of drawRotatedBlockImage taking three terrain.png tiles -void drawRotatedBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int Nface, int Wface, int Uface, int rotN, bool flipN, int rotW, bool flipW, int rotU, bool flipU, int B) -{ - drawRotatedBlockImage(dest, drect, terrainTile(tiles, Nface, rotN, flipN), terrainTile(tiles, Wface, rotW, flipW), terrainTile(tiles, Uface, rotU, flipU), B); -} - -void drawBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int Nface, int Wface, int Uface, int B) -{ - drawRotatedBlockImage(dest, drect, terrainTile(tiles, Nface), terrainTile(tiles, Wface), terrainTile(tiles, Uface), B); -} - -// draw a block image where the block isn't full height (half-steps, snow, etc.) -// topcutoff is the number of pixels (out of 2B) to chop off the top of the N and W faces -// bottomcutoff is the number of pixels (out of 2B) to chop off the bottom -// if shift is true, we start copying pixels from the very top of the source tile, even if there's a topcutoff -// U face can also be rotated, and N/W faces can be X-flipped (set 0x1 for N, 0x2 for W) -void drawPartialBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int Nface, int Wface, int Uface, int B, int topcutoff, int bottomcutoff, int rot, int flip, bool shift) -{ - int tilesize = 2*B; - if (topcutoff + bottomcutoff >= tilesize) - return; - int end = tilesize - bottomcutoff; - // N face starts at [0,B] - if (Nface != -1) - { - FaceIterator dstit(drect.x, drect.y + B, 1, tilesize); - for (RotatedFaceIterator srcit((Nface%16)*tilesize, (Nface/16)*tilesize, 0, tilesize, flip & 0x1); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize >= topcutoff && dstit.pos % tilesize < end) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y - (shift ? topcutoff : 0)); - darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); - } - } - } - // W face starts at [2B,2B] - if (Wface != -1) - { - FaceIterator dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); - for (RotatedFaceIterator srcit((Wface%16)*tilesize, (Wface/16)*tilesize, 0, tilesize, flip & 0x2); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize >= topcutoff && dstit.pos % tilesize < end) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y - (shift ? topcutoff : 0)); - darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); - } - } - } - // U face starts at [2B-1,topcutoff] - if (Uface != -1) - { - TopFaceIterator dstit(drect.x + 2*B-1, drect.y + topcutoff, tilesize); - for (RotatedFaceIterator srcit((Uface%16)*tilesize, (Uface/16)*tilesize, rot, tilesize, false); !srcit.end; srcit.advance(), dstit.advance()) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - } - } -} - -// draw two flat copies of a tile intersecting at the block center (saplings, etc.) -void drawItemBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, int B, bool N, bool S, bool E, bool W) -{ - if (!tile.valid()) - return; - int tilesize = 2*B; - int cutoff = tilesize/2; - // E/W face starting at [B,1.5B] -- southern half only - if (S) - { - FaceIterator dstit(drect.x + B, drect.y + B*3/2, -1, tilesize); - for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - if (dstit.pos / tilesize >= cutoff) - blend(dest(dstit.x, dstit.y), srcit.pixel()); - } - } - // N/S face starting at [B,0.5B] - if (E || W) - { - FaceIterator dstit(drect.x + B, drect.y + B/2, 1, tilesize); - for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - if ((W && dstit.pos / tilesize >= cutoff) || (E && dstit.pos / tilesize < cutoff)) - blend(dest(dstit.x, dstit.y), srcit.pixel()); - } - } - // E/W face starting at [B,1.5B] -- northern half only - if (N) - { - FaceIterator dstit(drect.x + B, drect.y + B*3/2, -1, tilesize); - for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - if (dstit.pos / tilesize < cutoff) - blend(dest(dstit.x, dstit.y), srcit.pixel()); - } - } -} - -void drawItemBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int B) -{ - drawItemBlockImage(dest, drect, terrainTile(tiles, tile), B, true, true, true, true); -} - - -// draw an item block image possibly missing some edges (iron bars, etc.) -void drawPartialItemBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int rot, bool flipX, int B, bool N, bool S, bool E, bool W) -{ - drawItemBlockImage(dest, drect, terrainTile(tiles, tile, rot, flipX), B, N, S, E, W); -} - -// draw four flat copies of a tile intersecting in a square (netherwart, etc.) -void drawMultiItemBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, int B) -{ - if (!tile.valid()) - return; - int tilesize = 2*B; - // E/W face starting at [0.5B,1.25B] - { - FaceIterator dstit(drect.x + B/2, drect.y + B*5/4, -1, tilesize); - for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - blend(dest(dstit.x, dstit.y), srcit.pixel()); - } - } - // E/W face starting at [1.5B,1.75B] - { - FaceIterator dstit(drect.x + 3*B/2, drect.y + B*7/4, -1, tilesize); - for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - blend(dest(dstit.x, dstit.y), srcit.pixel()); - } - } - // N/S face starting at [0.5B,0.75B] - { - FaceIterator dstit(drect.x + B/2, drect.y + B*3/4, 1, tilesize); - for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - blend(dest(dstit.x, dstit.y), srcit.pixel()); - } - } - // N/S face starting at [1.5B,0.25B] - { - FaceIterator dstit(drect.x + 3*B/2, drect.y + B/4, 1, tilesize); - for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - blend(dest(dstit.x, dstit.y), srcit.pixel()); - } - } -} - -void drawMultiItemBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int B) -{ - drawMultiItemBlockImage(dest, drect, terrainTile(tiles, tile), B); -} - -// draw a tile on a single upright face -// 0 = S, 1 = N, 2 = W, 3 = E -// ...handles transparency -void drawSingleFaceBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, int face, int B) -{ - if (!tile.valid()) - return; - int tilesize = 2*B; - int xoff, yoff, deltaY; - if (face == 0) - { - xoff = 2*B; - yoff = 0; - deltaY = 1; - } - else if (face == 1) - { - xoff = 0; - yoff = B; - deltaY = 1; - } - else if (face == 2) - { - xoff = 2*B; - yoff = 2*B; - deltaY = -1; - } - else - { - xoff = 0; - yoff = B; - deltaY = -1; - } - FaceIterator dstit(drect.x + xoff, drect.y + yoff, deltaY, tilesize); - for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - blend(dest(dstit.x, dstit.y), srcit.pixel()); - } -} - -void drawSingleFaceBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int face, int B) -{ - drawSingleFaceBlockImage(dest, drect, terrainTile(tiles, tile), face, B); -} - -// draw part of a tile on a single upright face -// 0 = S, 1 = N, 2 = W, 3 = E -// ...handles transparency -void drawPartialSingleFaceBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, int face, int B, double fstartv, double fendv, double fstarth, double fendh) -{ - int tilesize = 2*B; - int vstartcutoff = max(0, min(tilesize, (int)(fstartv * tilesize))); - int vendcutoff = max(0, min(tilesize, (int)(fendv * tilesize))); - int hstartcutoff = max(0, min(tilesize, (int)(fstarth * tilesize))); - int hendcutoff = max(0, min(tilesize, (int)(fendh * tilesize))); - int xoff, yoff, deltaY; - if (face == 0) - { - xoff = 2*B; - yoff = 0; - deltaY = 1; - } - else if (face == 1) - { - xoff = 0; - yoff = B; - deltaY = 1; - } - else if (face == 2) - { - xoff = 2*B; - yoff = 2*B; - deltaY = -1; - } - else - { - xoff = 0; - yoff = B; - deltaY = -1; - } - FaceIterator dstit(drect.x + xoff, drect.y + yoff, deltaY, tilesize); - for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize >= vstartcutoff && dstit.pos % tilesize < vendcutoff && - dstit.pos / tilesize >= hstartcutoff && dstit.pos / tilesize < hendcutoff) - blend(dest(dstit.x, dstit.y), srcit.pixel()); - } -} - -void drawPartialSingleFaceBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int face, int B, double fstartv, double fendv, double fstarth, double fendh) -{ - drawPartialSingleFaceBlockImage(dest, drect, terrainTile(tiles, tile), face, B, fstartv, fendv, fstarth, fendh); -} - -// draw a single tile on the floor, possibly with rotation -// 0 = top of tile is on S side; 1 = W, 2 = N, 3 = E -void drawFloorBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, int B) -{ - int tilesize = 2*B; - TopFaceIterator dstit(drect.x + 2*B-1, drect.y + 2*B, tilesize); - for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - dest(dstit.x, dstit.y) = srcit.pixel(); - } -} - -void drawFloorBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int rot, int B) -{ - drawFloorBlockImage(dest, drect, terrainTile(tiles, tile, rot, false), B); -} - -// draw part of a single tile on the floor -void drawPartialFloorBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, int B, double fstartv, double fendv, double fstarth, double fendh) -{ - int tilesize = 2*B; - int vstartcutoff = max(0, min(tilesize, (int)(fstartv * tilesize))); - int vendcutoff = max(0, min(tilesize, (int)(fendv * tilesize))); - int hstartcutoff = max(0, min(tilesize, (int)(fstarth * tilesize))); - int hendcutoff = max(0, min(tilesize, (int)(fendh * tilesize))); - TopFaceIterator dstit(drect.x + 2*B-1, drect.y + 2*B, tilesize); - for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize >= vstartcutoff && dstit.pos % tilesize < vendcutoff && - dstit.pos / tilesize >= hstartcutoff && dstit.pos / tilesize < hendcutoff) - dest(dstit.x, dstit.y) = srcit.pixel(); - } -} - -void drawPartialFloorBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int B, double fstartv, double fendv, double fstarth, double fendh) -{ - drawPartialFloorBlockImage(dest, drect, terrainTile(tiles, tile), B, fstartv, fendv, fstarth, fendh); -} - -// draw a single tile on the floor, possibly with rotation, angled upwards -// rot: 0 = top of tile is on S side; 1 = W, 2 = N, 3 = E -// up: 0 = S side of tile is highest; 1 = W, 2 = N, 3 = E -void drawAngledFloorBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, int up, int B) -{ - int tilesize = 2*B; - TopFaceIterator dstit(drect.x + 2*B-1, drect.y + 2*B, tilesize); - for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) - { - int yoff = 0; - int row = dstit.pos % tilesize, col = dstit.pos / tilesize; - if (up == 0) - yoff = tilesize - 1 - row; - else if (up == 1) - yoff = col; - else if (up == 2) - yoff = row; - else if (up == 3) - yoff = tilesize - 1 - col; - blend(dest(dstit.x, dstit.y - yoff), srcit.pixel()); - blend(dest(dstit.x, dstit.y - yoff + 1), srcit.pixel()); - } -} - -void drawAngledFloorBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int rot, int up, int B) -{ - drawAngledFloorBlockImage(dest, drect, terrainTile(tiles, tile, rot, false), up, B); -} - -// draw a single tile on the ceiling, possibly with rotation -// 0 = top of tile is on S side; 1 = W, 2 = N, 3 = E -void drawCeilBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int rot, int B) -{ - int tilesize = 2*B; - TopFaceIterator dstit(drect.x + 2*B-1, drect.y, tilesize); - for (RotatedFaceIterator srcit((tile%16)*tilesize, (tile/16)*tilesize, rot, tilesize, false); !srcit.end; srcit.advance(), dstit.advance()) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - } -} - -// draw a block image that's just a single color (plus shadows) -void drawSolidColorBlockImage(RGBAImage& dest, const ImageRect& drect, RGBAPixel p, int B) -{ - int tilesize = 2*B; - // N face starts at [0,B] - for (FaceIterator dstit(drect.x, drect.y + B, 1, tilesize); !dstit.end; dstit.advance()) - { - dest(dstit.x, dstit.y) = p; - darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); - } - // W face starts at [2B,2B] - for (FaceIterator dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !dstit.end; dstit.advance()) - { - dest(dstit.x, dstit.y) = p; - darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); - } - // U face starts at [2B-1,0] - for (TopFaceIterator dstit(drect.x + 2*B-1, drect.y, tilesize); !dstit.end; dstit.advance()) - { - dest(dstit.x, dstit.y) = p; - } -} - -// draw S-ascending stairs -void drawStairsS(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tileNW, int tileU, int B) -{ - int tilesize = 2*B; - // normal N face starts at [0,B]; draw the bottom half of it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize >= B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); - } - } - // normal W face starts at [2B,2B]; draw all but the upper-left quarter of it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize >= B || dstit.pos / tilesize >= B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); - } - } - // normal U face starts at [2B-1,0]; draw the top half of it - TopFaceIterator tdstit(drect.x + 2*B-1, drect.y, tilesize); - for (FaceIterator srcit((tileU%16)*tilesize, (tileU/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - // if B is odd, we need B pixels from each column, but if it's even, we need to alternate between - // B-1 and B+1 - int cutoff = B; - if (B % 2 == 0) - cutoff += ((tdstit.pos / tilesize) % 2 == 0) ? -1 : 1; - if (tdstit.pos % tilesize < cutoff) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } - } - // draw the top half of another N face at [B,B/2] - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x + B, drect.y + B/2, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - // ...but if B is odd, we need to add an extra [0,1] to the even-numbered columns - int adjust = 0; - if (B % 2 == 1 && (dstit.pos / tilesize) % 2 == 0) - adjust = 1; - if (dstit.pos % tilesize < B) - { - dest(dstit.x, dstit.y + adjust) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y + adjust), 0.9, 0.9, 0.9); - } - } - // draw the bottom half of another U face at [2B-1,B] - tdstit = TopFaceIterator(drect.x + 2*B-1, drect.y + B, tilesize); - for (FaceIterator srcit((tileU%16)*tilesize, (tileU/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - // again, if B is odd, take B pixels from each column; if even, take B-1 or B+1 - int cutoff = B; - if (B % 2 == 0) - cutoff += ((tdstit.pos / tilesize) % 2 == 0) ? -1 : 1; - if (tdstit.pos % tilesize >= cutoff) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } - } -} - -// draw S-ascending stairs inverted -void drawInvStairsS(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tileNW, int tileU, int B) -{ - int tilesize = 2*B; - // draw the bottom half of a N face at [B,B/2]; do this first because the others will partially cover it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x + B, drect.y + B/2, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - // ...but if B is odd, we need to add an extra [0,1] to the even-numbered columns - int adjust = 0; - if (B % 2 == 1 && (dstit.pos / tilesize) % 2 == 0) - adjust = 1; - if (dstit.pos % tilesize >= B) - { - dest(dstit.x, dstit.y + adjust) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y + adjust), 0.9, 0.9, 0.9); - } - } - // normal N face starts at [0,B]; draw the top half of it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize < B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); - } - } - // normal W face starts at [2B,2B]; draw all but the lower-left quarter of it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize < B || dstit.pos / tilesize >= B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); - } - } - // normal U face starts at [2B-1,0]; draw the whole thing - TopFaceIterator tdstit(drect.x + 2*B-1, drect.y, tilesize); - for (FaceIterator srcit((tileU%16)*tilesize, (tileU/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } -} - -// draw N-ascending stairs -void drawStairsN(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tileNW, int tileU, int B) -{ - int tilesize = 2*B; - // draw the top half of an an U face at [2B-1,B] - TopFaceIterator tdstit(drect.x + 2*B-1, drect.y + B, tilesize); - for (FaceIterator srcit((tileU%16)*tilesize, (tileU/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - // if B is odd, we need B pixels from each column, but if it's even, we need to alternate between - // B-1 and B+1 - int cutoff = B; - if (B % 2 == 0) - cutoff += ((tdstit.pos / tilesize) % 2 == 0) ? -1 : 1; - if (tdstit.pos % tilesize < cutoff) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } - } - // draw the bottom half of the normal U face at [2B-1,0] - tdstit = TopFaceIterator(drect.x + 2*B-1, drect.y, tilesize); - for (FaceIterator srcit((tileU%16)*tilesize, (tileU/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - // again, if B is odd, take B pixels from each column; if even, take B-1 or B+1 - int cutoff = B; - if (B % 2 == 0) - cutoff += ((tdstit.pos / tilesize) % 2 == 0) ? -1 : 1; - if (tdstit.pos % tilesize >= cutoff) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } - } - // normal N face starts at [0,B]; draw it all - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); - } - // normal W face starts at [2B,2B]; draw all but the upper-right quarter of it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize >= B || dstit.pos / tilesize < B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); - } - } -} - -// draw N-ascending stairs inverted -void drawInvStairsN(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tileNW, int tileU, int B) -{ - int tilesize = 2*B; - // normal U face starts at [2B-1,0]; draw the whole thing - TopFaceIterator tdstit(drect.x + 2*B-1, drect.y, tilesize); - for (FaceIterator srcit((tileU%16)*tilesize, (tileU/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } - // normal N face starts at [0,B]; draw it all - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); - } - // normal W face starts at [2B,2B]; draw all but the lower-right quarter of it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize < B || dstit.pos / tilesize < B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); - } - } -} - -// draw E-ascending stairs -void drawStairsE(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tileNW, int tileU, int B) -{ - int tilesize = 2*B; - // normal N face starts at [0,B]; draw all but the upper-right quarter of it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize >= B || dstit.pos / tilesize < B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); - } - } - // normal W face starts at [2B,2B]; draw the bottom half of it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize >= B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); - } - } - // normal U face starts at [2B-1,0]; draw the left half of it - TopFaceIterator tdstit(drect.x + 2*B-1, drect.y, tilesize); - int tcutoff = tilesize * B; - bool textra = false; - // if B is odd, we need to skip the last pixel of the last left-half column, and add the very first - // pixel of the first right-half column - if (B % 2 == 1) - { - tcutoff--; - textra = true; - } - for (FaceIterator srcit((tileU%16)*tilesize, (tileU/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - if (tdstit.pos < tcutoff || (textra && tdstit.pos == tcutoff + 1)) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } - } - // draw the top half of another W face at [B,1.5B] - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x + B, drect.y + 3*B/2, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - // ...but if B is odd, we need to add an extra [0,1] to the odd-numbered columns - int adjust = 0; - if (B % 2 == 1 && (dstit.pos / tilesize) % 2 == 1) - adjust = 1; - if (dstit.pos % tilesize < B) - { - dest(dstit.x, dstit.y + adjust) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y + adjust), 0.8, 0.8, 0.8); - } - } - // draw the right half of another U face at [2B-1,B] - tdstit = TopFaceIterator(drect.x + 2*B-1, drect.y + B, tilesize); - tcutoff = tilesize * B; - textra = false; - // if B is odd, do the reverse of what we did with the top half - if (B % 2 == 1) - { - tcutoff++; - textra = true; - } - for (FaceIterator srcit((tileU%16)*tilesize, (tileU/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - if (tdstit.pos >= tcutoff || (textra && tdstit.pos == tcutoff - 2)) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } - } -} - -// draw E-ascending stairs inverted -void drawInvStairsE(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tileNW, int tileU, int B) -{ - int tilesize = 2*B; - // draw the bottom half of a W face at [B,1.5B]; do this first because the others will partially cover it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x + B, drect.y + 3*B/2, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - // ...but if B is odd, we need to add an extra [0,1] to the odd-numbered columns - int adjust = 0; - if (B % 2 == 1 && (dstit.pos / tilesize) % 2 == 1) - adjust = 1; - if (dstit.pos % tilesize >= B) - { - dest(dstit.x, dstit.y + adjust) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y + adjust), 0.8, 0.8, 0.8); - } - } - // normal W face starts at [2B,2B]; draw the top half of it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize < B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); - } - } - // normal N face starts at [0,B]; draw all but the lower-right quarter of it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize < B || dstit.pos / tilesize < B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); - } - } - // normal U face starts at [2B-1,0]; draw the whole thing - TopFaceIterator tdstit(drect.x + 2*B-1, drect.y, tilesize); - for (FaceIterator srcit((tileU%16)*tilesize, (tileU/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } -} - -// draw W-ascending stairs -void drawStairsW(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tileNW, int tileU, int B) -{ - int tilesize = 2*B; - // draw the left half of an U face at [2B-1,B] - TopFaceIterator tdstit(drect.x + 2*B-1, drect.y + B, tilesize); - int tcutoff = tilesize * B; - bool textra = false; - // if B is odd, we need to skip the last pixel of the last left-half column, and add the very first - // pixel of the first right-half column - if (B % 2 == 1) - { - tcutoff--; - textra = true; - } - for (FaceIterator srcit((tileU%16)*tilesize, (tileU/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - if (tdstit.pos < tcutoff || (textra && tdstit.pos == tcutoff + 1)) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } - } - // draw the right half of the normal U face at [2B-1,0] - tdstit = TopFaceIterator(drect.x + 2*B-1, drect.y, tilesize); - tcutoff = tilesize * B; - textra = false; - // if B is odd, do the reverse of what we did with the top half - if (B % 2 == 1) - { - tcutoff++; - textra = true; - } - for (FaceIterator srcit((tileU%16)*tilesize, (tileU/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - if (tdstit.pos >= tcutoff || (textra && tdstit.pos == tcutoff - 2)) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } - } - // normal N face starts at [0,B]; draw all but the upper-left quarter of it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize >= B || dstit.pos / tilesize >= B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); - } - } - // normal W face starts at [2B,2B]; draw the whole thing - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); - } -} - -// draw W-ascending stairs inverted -void drawInvStairsW(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tileNW, int tileU, int B) -{ - int tilesize = 2*B; - // normal U face starts at [2B-1,0]; draw the whole thing - TopFaceIterator tdstit(drect.x + 2*B-1, drect.y, tilesize); - for (FaceIterator srcit((tileU%16)*tilesize, (tileU/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } - // normal W face starts at [2B,2B]; draw the whole thing - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); - } - // normal N face starts at [0,B]; draw all but the lower-left quarter of it - for (FaceIterator srcit((tileNW%16)*tilesize, (tileNW/16)*tilesize, 0, tilesize), - dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize < B || dstit.pos / tilesize >= B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); - } - } -} - -// draw crappy fence post -void drawFencePost(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int B) -{ - int tilesize = 2*B; - int tilex = (tile%16)*tilesize, tiley = (tile/16)*tilesize; - - // draw a 2x2 top at [2B-1,B-1] - for (int y = 0; y < 2; y++) - for (int x = 0; x < 2; x++) - dest(drect.x + 2*B - 1 + x, drect.y + B - 1 + y) = tiles(tilex + x, tiley + y); - - // draw a 1x2B side at [2B-1,B+1] - for (int y = 0; y < 2*B; y++) - dest(drect.x + 2*B - 1, drect.y + B + 1 + y) = tiles(tilex, tiley + y); - - // draw a 1x2B side at [2B,B+1] - for (int y = 0; y < 2*B; y++) - dest(drect.x + 2*B, drect.y + B + 1 + y) = tiles(tilex, tiley + y); -} - -// draw fence: post and four rails, each optional -void drawFence(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, bool N, bool S, bool E, bool W, bool post, int B) -{ - // first, E and S rails, since the post should be in front of them - int tilesize = 2*B; - if (E) - { - // N/S face starting at [B,0.5B]; left half, one strip - for (FaceIterator srcit((tile%16)*tilesize, (tile/16)*tilesize, 0, tilesize), - dstit(drect.x + B, drect.y + B/2, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos / tilesize < B && (((dstit.pos % tilesize) * 2 / B) % 4) == 1) - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - } - } - if (S) - { - // E/W face starting at [B,1.5B]; right half, one strip - for (FaceIterator srcit((tile%16)*tilesize, (tile/16)*tilesize, 0, tilesize), - dstit(drect.x + B, drect.y + B*3/2, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos / tilesize >= B && (((dstit.pos % tilesize) * 2 / B) % 4) == 1) - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - } - } - - // now the post - if (post) - drawFencePost(dest, drect, tiles, tile, B); - - // now the N and W rails - if (W) - { - // N/S face starting at [B,0.5B]; right half, one strip - for (FaceIterator srcit((tile%16)*tilesize, (tile/16)*tilesize, 0, tilesize), - dstit(drect.x + B, drect.y + B/2, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos / tilesize >= B && (((dstit.pos % tilesize) * 2 / B) % 4) == 1) - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - } - } - if (N) - { - // E/W face starting at [B,1.5B]; left half, one strip - for (FaceIterator srcit((tile%16)*tilesize, (tile/16)*tilesize, 0, tilesize), - dstit(drect.x + B, drect.y + B*3/2, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos / tilesize < B && (((dstit.pos % tilesize) * 2 / B) % 4) == 1) - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - } - } -} - -// draw crappy sign facing out towards the viewer -void drawSign(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int B) -{ - // start with fence post - drawFencePost(dest, drect, tiles, tile, B); - - int tilesize = 2*B; - // draw the top half of a tile at [B,B] - for (FaceIterator srcit((tile%16)*tilesize, (tile/16)*tilesize, 0, tilesize), - dstit(drect.x + B, drect.y + B, 0, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize < B) - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - } -} - -// draw crappy wall lever -void drawWallLever(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int face, int B) -{ - drawPartialSingleFaceBlockImage(dest, drect, tiles, 16, face, B, 0.5, 1, 0.35, 0.65); - drawSingleFaceBlockImage(dest, drect, tiles, 96, face, B); -} - -void drawFloorLeverNS(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int B) -{ - drawPartialFloorBlockImage(dest, drect, tiles, 16, B, 0.25, 0.75, 0.35, 0.65); - drawItemBlockImage(dest, drect, tiles, 96, B); -} - -void drawFloorLeverEW(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int B) -{ - drawPartialFloorBlockImage(dest, drect, tiles, 16, B, 0.35, 0.65, 0.25, 0.75); - drawItemBlockImage(dest, drect, tiles, 96, B); -} - -void drawRepeater(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int rot, int B) -{ - drawFloorBlockImage(dest, drect, tiles, tile, rot, B); - drawItemBlockImage(dest, drect, tiles, 99, B); -} - -void drawFire(RGBAImage& dest, const ImageRect& drect, const RGBAImage& firetile, int B) -{ - drawSingleFaceBlockImage(dest, drect, firetile, 0, 0, B); - drawSingleFaceBlockImage(dest, drect, firetile, 0, 3, B); - drawSingleFaceBlockImage(dest, drect, firetile, 0, 1, B); - drawSingleFaceBlockImage(dest, drect, firetile, 0, 2, B); -} - -// draw crappy brewing stand: full base tile plus item-shaped stand -void drawBrewingStand(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int base, int stand, int B) -{ - drawFloorBlockImage(dest, drect, tiles, base, 0, B); - drawItemBlockImage(dest, drect, tiles, stand, B); -} - -void drawCauldron(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int side, int liquid, int cutoff, int B) -{ - // start with E/S sides, since liquid goes in front of them - drawSingleFaceBlockImage(dest, drect, tiles, side, 0, B); - drawSingleFaceBlockImage(dest, drect, tiles, side, 3, B); - - // draw the liquid - if (liquid != -1) - drawPartialBlockImage(dest, drect, tiles, -1, -1, liquid, B, cutoff, 0, 0, 0, true); - - // now the N/W sides - drawSingleFaceBlockImage(dest, drect, tiles, side, 1, B); - drawSingleFaceBlockImage(dest, drect, tiles, side, 2, B); -} - -void drawVines(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int B, bool N, bool S, bool E, bool W, bool top) -{ - if (S) - drawSingleFaceBlockImage(dest, drect, tiles, tile, 0, B); - if (E) - drawSingleFaceBlockImage(dest, drect, tiles, tile, 3, B); - if (N) - drawSingleFaceBlockImage(dest, drect, tiles, tile, 1, B); - if (W) - drawSingleFaceBlockImage(dest, drect, tiles, tile, 2, B); - if (top) - drawCeilBlockImage(dest, drect, tiles, tile, 0, B); -} - -// draw crappy dragon egg--just a half-size block -void drawDragonEgg(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tiles, int tile, int B) -{ - int tilesize = 2*B; - // N face at [0,0.5B]; draw the bottom-right quarter of it - for (FaceIterator srcit((tile%16)*tilesize, (tile/16)*tilesize, 0, tilesize), - dstit(drect.x, drect.y + B/2, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize >= B && dstit.pos / tilesize >= B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); - } - } - // W face at [2B,1.5B]; draw the bottom-left quarter of it - for (FaceIterator srcit((tile%16)*tilesize, (tile/16)*tilesize, 0, tilesize), - dstit(drect.x + 2*B, drect.y + 3*B/2, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) - { - if (dstit.pos % tilesize >= B && dstit.pos / tilesize < B) - { - dest(dstit.x, dstit.y) = tiles(srcit.x, srcit.y); - darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); - } - } - // draw the bottom-right quarter of a U face at [2B-1,0.5B] - TopFaceIterator tdstit(drect.x + 2*B-1, drect.y + B/2, tilesize); - for (FaceIterator srcit((tile%16)*tilesize, (tile/16)*tilesize, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) - { - // again, if B is odd, take B pixels from each column; if even, take B-1 or B+1 - int cutoff = B; - if (B % 2 == 0) - cutoff += ((tdstit.pos / tilesize) % 2 == 0) ? -1 : 1; - if (tdstit.pos % tilesize >= cutoff && tdstit.pos / tilesize >= cutoff) - { - dest(tdstit.x, tdstit.y) = tiles(srcit.x, srcit.y); - } - } -} - - - - -int offsetIdx(uint16_t blockID, uint8_t blockData) -{ - return blockID * 16 + blockData; -} - -void setOffsetsForID(uint16_t blockID, int offset, BlockImages& bi) -{ - int start = blockID * 16; - int end = start + 16; - fill(bi.blockOffsets + start, bi.blockOffsets + end, offset); -} - -void BlockImages::setOffsets() -{ - // default is the dummy image - fill(blockOffsets, blockOffsets + 4096*16, 0); - - //!!!!!! might want to use darker redstone wire for lower strength, just for some visual variety? - - setOffsetsForID(1, 1, *this); - setOffsetsForID(2, 2, *this); - setOffsetsForID(3, 3, *this); - setOffsetsForID(4, 4, *this); - setOffsetsForID(5, 5, *this); - blockOffsets[offsetIdx(5, 1)] = 435; - blockOffsets[offsetIdx(5, 2)] = 436; - blockOffsets[offsetIdx(5, 3)] = 437; - setOffsetsForID(6, 6, *this); - blockOffsets[offsetIdx(6, 1)] = 250; - blockOffsets[offsetIdx(6, 5)] = 250; - blockOffsets[offsetIdx(6, 9)] = 250; - blockOffsets[offsetIdx(6, 13)] = 250; - blockOffsets[offsetIdx(6, 2)] = 251; - blockOffsets[offsetIdx(6, 6)] = 251; - blockOffsets[offsetIdx(6, 10)] = 251; - blockOffsets[offsetIdx(6, 14)] = 251; - blockOffsets[offsetIdx(6, 3)] = 429; - blockOffsets[offsetIdx(6, 7)] = 429; - blockOffsets[offsetIdx(6, 11)] = 429; - blockOffsets[offsetIdx(6, 15)] = 429; - setOffsetsForID(7, 7, *this); - setOffsetsForID(8, 8, *this); - blockOffsets[offsetIdx(8, 1)] = 9; - blockOffsets[offsetIdx(8, 2)] = 10; - blockOffsets[offsetIdx(8, 3)] = 11; - blockOffsets[offsetIdx(8, 4)] = 12; - blockOffsets[offsetIdx(8, 5)] = 13; - blockOffsets[offsetIdx(8, 6)] = 14; - blockOffsets[offsetIdx(8, 7)] = 15; - setOffsetsForID(9, 8, *this); - blockOffsets[offsetIdx(9, 1)] = 9; - blockOffsets[offsetIdx(9, 2)] = 10; - blockOffsets[offsetIdx(9, 3)] = 11; - blockOffsets[offsetIdx(9, 4)] = 12; - blockOffsets[offsetIdx(9, 5)] = 13; - blockOffsets[offsetIdx(9, 6)] = 14; - blockOffsets[offsetIdx(9, 7)] = 15; - setOffsetsForID(10, 16, *this); - blockOffsets[offsetIdx(10, 6)] = 19; - blockOffsets[offsetIdx(10, 4)] = 18; - blockOffsets[offsetIdx(10, 2)] = 17; - setOffsetsForID(11, 16, *this); - blockOffsets[offsetIdx(11, 6)] = 19; - blockOffsets[offsetIdx(11, 4)] = 18; - blockOffsets[offsetIdx(11, 2)] = 17; - setOffsetsForID(12, 20, *this); - setOffsetsForID(13, 483, *this); - setOffsetsForID(14, 22, *this); - setOffsetsForID(15, 23, *this); - setOffsetsForID(16, 24, *this); - setOffsetsForID(17, 25, *this); - blockOffsets[offsetIdx(17, 1)] = 219; - blockOffsets[offsetIdx(17, 2)] = 220; - blockOffsets[offsetIdx(17, 3)] = 427; - blockOffsets[offsetIdx(17, 4)] = 532; - blockOffsets[offsetIdx(17, 5)] = 534; - blockOffsets[offsetIdx(17, 6)] = 536; - blockOffsets[offsetIdx(17, 7)] = 538; - blockOffsets[offsetIdx(17, 8)] = 531; - blockOffsets[offsetIdx(17, 9)] = 533; - blockOffsets[offsetIdx(17, 10)] = 535; - blockOffsets[offsetIdx(17, 11)] = 537; - setOffsetsForID(18, 26, *this); - blockOffsets[offsetIdx(18, 1)] = 248; - blockOffsets[offsetIdx(18, 5)] = 248; - blockOffsets[offsetIdx(18, 9)] = 248; - blockOffsets[offsetIdx(18, 13)] = 248; - blockOffsets[offsetIdx(18, 2)] = 249; - blockOffsets[offsetIdx(18, 6)] = 249; - blockOffsets[offsetIdx(18, 10)] = 249; - blockOffsets[offsetIdx(18, 14)] = 249; - blockOffsets[offsetIdx(18, 3)] = 428; - blockOffsets[offsetIdx(18, 7)] = 428; - blockOffsets[offsetIdx(18, 11)] = 428; - blockOffsets[offsetIdx(18, 15)] = 428; - setOffsetsForID(19, 27, *this); - setOffsetsForID(20, 28, *this); - setOffsetsForID(21, 221, *this); - setOffsetsForID(22, 222, *this); - setOffsetsForID(23, 223, *this); - blockOffsets[offsetIdx(23, 2)] = 225; - blockOffsets[offsetIdx(23, 4)] = 224; - blockOffsets[offsetIdx(23, 5)] = 225; - setOffsetsForID(24, 226, *this); - blockOffsets[offsetIdx(24, 1)] = 431; - blockOffsets[offsetIdx(24, 2)] = 432; - setOffsetsForID(25, 227, *this); - setOffsetsForID(26, 285, *this); - blockOffsets[offsetIdx(26, 1)] = 286; - blockOffsets[offsetIdx(26, 5)] = 286; - blockOffsets[offsetIdx(26, 2)] = 287; - blockOffsets[offsetIdx(26, 6)] = 287; - blockOffsets[offsetIdx(26, 3)] = 288; - blockOffsets[offsetIdx(26, 7)] = 288; - blockOffsets[offsetIdx(26, 8)] = 281; - blockOffsets[offsetIdx(26, 12)] = 281; - blockOffsets[offsetIdx(26, 9)] = 282; - blockOffsets[offsetIdx(26, 13)] = 282; - blockOffsets[offsetIdx(26, 10)] = 283; - blockOffsets[offsetIdx(26, 14)] = 283; - blockOffsets[offsetIdx(26, 11)] = 284; - blockOffsets[offsetIdx(26, 15)] = 284; - setOffsetsForID(27, 258, *this); - blockOffsets[offsetIdx(27, 1)] = 259; - blockOffsets[offsetIdx(27, 2)] = 260; - blockOffsets[offsetIdx(27, 3)] = 261; - blockOffsets[offsetIdx(27, 4)] = 262; - blockOffsets[offsetIdx(27, 5)] = 263; - blockOffsets[offsetIdx(27, 8)] = 252; - blockOffsets[offsetIdx(27, 9)] = 253; - blockOffsets[offsetIdx(27, 10)] = 254; - blockOffsets[offsetIdx(27, 11)] = 255; - blockOffsets[offsetIdx(27, 12)] = 256; - blockOffsets[offsetIdx(27, 13)] = 257; - setOffsetsForID(28, 264, *this); - blockOffsets[offsetIdx(28, 1)] = 265; - blockOffsets[offsetIdx(28, 2)] = 266; - blockOffsets[offsetIdx(28, 3)] = 267; - blockOffsets[offsetIdx(28, 4)] = 268; - blockOffsets[offsetIdx(28, 5)] = 269; - setOffsetsForID(29, 413, *this); - blockOffsets[offsetIdx(29, 1)] = 414; - blockOffsets[offsetIdx(29, 9)] = 414; - blockOffsets[offsetIdx(29, 4)] = 415; - blockOffsets[offsetIdx(29, 12)] = 415; - blockOffsets[offsetIdx(29, 5)] = 416; - blockOffsets[offsetIdx(29, 13)] = 416; - blockOffsets[offsetIdx(29, 3)] = 417; - blockOffsets[offsetIdx(29, 11)] = 417; - blockOffsets[offsetIdx(29, 2)] = 418; - blockOffsets[offsetIdx(29, 10)] = 418; - setOffsetsForID(30, 272, *this); - setOffsetsForID(31, 273, *this); - blockOffsets[offsetIdx(31, 0)] = 275; - blockOffsets[offsetIdx(31, 2)] = 274; - setOffsetsForID(32, 275, *this); - setOffsetsForID(33, 407, *this); - blockOffsets[offsetIdx(33, 1)] = 408; - blockOffsets[offsetIdx(33, 9)] = 408; - blockOffsets[offsetIdx(33, 4)] = 409; - blockOffsets[offsetIdx(33, 12)] = 409; - blockOffsets[offsetIdx(33, 5)] = 410; - blockOffsets[offsetIdx(33, 13)] = 410; - blockOffsets[offsetIdx(33, 3)] = 411; - blockOffsets[offsetIdx(33, 11)] = 411; - blockOffsets[offsetIdx(33, 2)] = 412; - blockOffsets[offsetIdx(33, 10)] = 412; - blockOffsets[offsetIdx(35, 0)] = 29; - blockOffsets[offsetIdx(35, 1)] = 204; - blockOffsets[offsetIdx(35, 2)] = 205; - blockOffsets[offsetIdx(35, 3)] = 206; - blockOffsets[offsetIdx(35, 4)] = 207; - blockOffsets[offsetIdx(35, 5)] = 208; - blockOffsets[offsetIdx(35, 6)] = 209; - blockOffsets[offsetIdx(35, 7)] = 210; - blockOffsets[offsetIdx(35, 8)] = 211; - blockOffsets[offsetIdx(35, 9)] = 212; - blockOffsets[offsetIdx(35, 10)] = 213; - blockOffsets[offsetIdx(35, 11)] = 214; - blockOffsets[offsetIdx(35, 12)] = 215; - blockOffsets[offsetIdx(35, 13)] = 216; - blockOffsets[offsetIdx(35, 14)] = 217; - blockOffsets[offsetIdx(35, 15)] = 218; - setOffsetsForID(37, 30, *this); - setOffsetsForID(38, 31, *this); - setOffsetsForID(39, 32, *this); - setOffsetsForID(40, 33, *this); - setOffsetsForID(41, 34, *this); - setOffsetsForID(42, 35, *this); - setOffsetsForID(43, 36, *this); - blockOffsets[offsetIdx(43, 1)] = 226; - blockOffsets[offsetIdx(43, 2)] = 5; - blockOffsets[offsetIdx(43, 3)] = 4; - blockOffsets[offsetIdx(43, 4)] = 38; - blockOffsets[offsetIdx(43, 5)] = 294; - setOffsetsForID(44, 37, *this); - blockOffsets[offsetIdx(44, 1)] = 229; - blockOffsets[offsetIdx(44, 2)] = 230; - blockOffsets[offsetIdx(44, 3)] = 231; - blockOffsets[offsetIdx(44, 4)] = 302; - blockOffsets[offsetIdx(44, 5)] = 303; - blockOffsets[offsetIdx(44, 8)] = 458; - blockOffsets[offsetIdx(44, 9)] = 459; - blockOffsets[offsetIdx(44, 10)] = 460; - blockOffsets[offsetIdx(44, 11)] = 461; - blockOffsets[offsetIdx(44, 12)] = 462; - blockOffsets[offsetIdx(44, 13)] = 463; - setOffsetsForID(45, 38, *this); - setOffsetsForID(46, 39, *this); - setOffsetsForID(47, 40, *this); - setOffsetsForID(48, 41, *this); - setOffsetsForID(49, 42, *this); - setOffsetsForID(50, 43, *this); - blockOffsets[offsetIdx(50, 1)] = 44; - blockOffsets[offsetIdx(50, 2)] = 45; - blockOffsets[offsetIdx(50, 3)] = 46; - blockOffsets[offsetIdx(50, 4)] = 47; - setOffsetsForID(51, 189, *this); - setOffsetsForID(52, 49, *this); - setOffsetsForID(53, 50, *this); - blockOffsets[offsetIdx(53, 1)] = 51; - blockOffsets[offsetIdx(53, 2)] = 52; - blockOffsets[offsetIdx(53, 3)] = 53; - blockOffsets[offsetIdx(53, 4)] = 438; - blockOffsets[offsetIdx(53, 5)] = 439; - blockOffsets[offsetIdx(53, 6)] = 440; - blockOffsets[offsetIdx(53, 7)] = 441; - setOffsetsForID(54, 484, *this); - blockOffsets[offsetIdx(54, 4)] = 485; - blockOffsets[offsetIdx(54, 2)] = 486; - blockOffsets[offsetIdx(54, 5)] = 486; - setOffsetsForID(55, 55, *this); - setOffsetsForID(56, 56, *this); - setOffsetsForID(57, 57, *this); - setOffsetsForID(58, 58, *this); - setOffsetsForID(59, 59, *this); - blockOffsets[offsetIdx(59, 6)] = 60; - blockOffsets[offsetIdx(59, 5)] = 61; - blockOffsets[offsetIdx(59, 4)] = 62; - blockOffsets[offsetIdx(59, 3)] = 63; - blockOffsets[offsetIdx(59, 2)] = 64; - blockOffsets[offsetIdx(59, 1)] = 65; - blockOffsets[offsetIdx(59, 0)] = 66; - setOffsetsForID(60, 67, *this); - setOffsetsForID(61, 183, *this); - blockOffsets[offsetIdx(61, 2)] = 185; - blockOffsets[offsetIdx(61, 4)] = 184; - blockOffsets[offsetIdx(61, 5)] = 185; - setOffsetsForID(62, 186, *this); - blockOffsets[offsetIdx(62, 2)] = 188; - blockOffsets[offsetIdx(62, 4)] = 187; - blockOffsets[offsetIdx(62, 5)] = 188; - setOffsetsForID(63, 73, *this); - blockOffsets[offsetIdx(63, 0)] = 72; - blockOffsets[offsetIdx(63, 1)] = 72; - blockOffsets[offsetIdx(63, 4)] = 70; - blockOffsets[offsetIdx(63, 5)] = 70; - blockOffsets[offsetIdx(63, 6)] = 71; - blockOffsets[offsetIdx(63, 7)] = 71; - blockOffsets[offsetIdx(63, 8)] = 72; - blockOffsets[offsetIdx(63, 9)] = 72; - blockOffsets[offsetIdx(63, 12)] = 70; - blockOffsets[offsetIdx(63, 13)] = 70; - blockOffsets[offsetIdx(63, 14)] = 71; - blockOffsets[offsetIdx(63, 15)] = 71; - setOffsetsForID(64, 74, *this); - setOffsetsForID(65, 82, *this); - blockOffsets[offsetIdx(65, 3)] = 83; - blockOffsets[offsetIdx(65, 4)] = 84; - blockOffsets[offsetIdx(65, 5)] = 85; - setOffsetsForID(66, 86, *this); - blockOffsets[offsetIdx(66, 1)] = 87; - blockOffsets[offsetIdx(66, 2)] = 200; - blockOffsets[offsetIdx(66, 3)] = 201; - blockOffsets[offsetIdx(66, 4)] = 202; - blockOffsets[offsetIdx(66, 5)] = 203; - blockOffsets[offsetIdx(66, 6)] = 92; - blockOffsets[offsetIdx(66, 7)] = 93; - blockOffsets[offsetIdx(66, 8)] = 94; - blockOffsets[offsetIdx(66, 9)] = 95; - setOffsetsForID(67, 96, *this); - blockOffsets[offsetIdx(67, 1)] = 97; - blockOffsets[offsetIdx(67, 2)] = 98; - blockOffsets[offsetIdx(67, 3)] = 99; - blockOffsets[offsetIdx(67, 4)] = 442; - blockOffsets[offsetIdx(67, 5)] = 443; - blockOffsets[offsetIdx(67, 6)] = 444; - blockOffsets[offsetIdx(67, 7)] = 445; - setOffsetsForID(68, 100, *this); - blockOffsets[offsetIdx(68, 3)] = 101; - blockOffsets[offsetIdx(68, 4)] = 102; - blockOffsets[offsetIdx(68, 5)] = 103; - setOffsetsForID(69, 194, *this); - blockOffsets[offsetIdx(69, 2)] = 195; - blockOffsets[offsetIdx(69, 3)] = 196; - blockOffsets[offsetIdx(69, 4)] = 197; - blockOffsets[offsetIdx(69, 5)] = 198; - blockOffsets[offsetIdx(69, 6)] = 199; - blockOffsets[offsetIdx(69, 10)] = 195; - blockOffsets[offsetIdx(69, 11)] = 196; - blockOffsets[offsetIdx(69, 12)] = 197; - blockOffsets[offsetIdx(69, 13)] = 198; - blockOffsets[offsetIdx(69, 14)] = 199; - setOffsetsForID(70, 110, *this); - setOffsetsForID(71, 111, *this); - setOffsetsForID(72, 119, *this); - setOffsetsForID(73, 120, *this); - setOffsetsForID(74, 120, *this); - setOffsetsForID(75, 121, *this); - blockOffsets[offsetIdx(75, 1)] = 145; - blockOffsets[offsetIdx(75, 2)] = 146; - blockOffsets[offsetIdx(75, 3)] = 147; - blockOffsets[offsetIdx(75, 4)] = 148; - setOffsetsForID(76, 122, *this); - blockOffsets[offsetIdx(76, 1)] = 141; - blockOffsets[offsetIdx(76, 2)] = 142; - blockOffsets[offsetIdx(76, 3)] = 143; - blockOffsets[offsetIdx(76, 4)] = 144; - setOffsetsForID(77, 190, *this); - blockOffsets[offsetIdx(77, 2)] = 191; - blockOffsets[offsetIdx(77, 3)] = 192; - blockOffsets[offsetIdx(77, 4)] = 193; - blockOffsets[offsetIdx(77, 10)] = 191; - blockOffsets[offsetIdx(77, 11)] = 192; - blockOffsets[offsetIdx(77, 12)] = 193; - setOffsetsForID(78, 127, *this); - setOffsetsForID(79, 128, *this); - setOffsetsForID(80, 129, *this); - setOffsetsForID(81, 130, *this); - setOffsetsForID(82, 131, *this); - setOffsetsForID(83, 132, *this); - setOffsetsForID(84, 133, *this); - setOffsetsForID(85, 134, *this); - setOffsetsForID(86, 153, *this); - blockOffsets[offsetIdx(86, 0)] = 135; - blockOffsets[offsetIdx(86, 1)] = 154; - blockOffsets[offsetIdx(86, 3)] = 153; - setOffsetsForID(87, 136, *this); - setOffsetsForID(88, 137, *this); - setOffsetsForID(89, 138, *this); - setOffsetsForID(90, 139, *this); - setOffsetsForID(91, 155, *this); - blockOffsets[offsetIdx(91, 0)] = 140; - blockOffsets[offsetIdx(91, 1)] = 156; - blockOffsets[offsetIdx(91, 3)] = 155; - setOffsetsForID(92, 289, *this); - setOffsetsForID(93, 247, *this); - blockOffsets[offsetIdx(93, 1)] = 244; - blockOffsets[offsetIdx(93, 5)] = 244; - blockOffsets[offsetIdx(93, 9)] = 244; - blockOffsets[offsetIdx(93, 13)] = 244; - blockOffsets[offsetIdx(93, 2)] = 246; - blockOffsets[offsetIdx(93, 6)] = 246; - blockOffsets[offsetIdx(93, 10)] = 246; - blockOffsets[offsetIdx(93, 14)] = 246; - blockOffsets[offsetIdx(93, 3)] = 245; - blockOffsets[offsetIdx(93, 7)] = 245; - blockOffsets[offsetIdx(93, 11)] = 245; - blockOffsets[offsetIdx(93, 15)] = 245; - setOffsetsForID(94, 243, *this); - blockOffsets[offsetIdx(94, 1)] = 240; - blockOffsets[offsetIdx(94, 5)] = 240; - blockOffsets[offsetIdx(94, 9)] = 240; - blockOffsets[offsetIdx(94, 13)] = 240; - blockOffsets[offsetIdx(94, 2)] = 242; - blockOffsets[offsetIdx(94, 6)] = 242; - blockOffsets[offsetIdx(94, 10)] = 242; - blockOffsets[offsetIdx(94, 14)] = 242; - blockOffsets[offsetIdx(94, 3)] = 241; - blockOffsets[offsetIdx(94, 7)] = 241; - blockOffsets[offsetIdx(94, 11)] = 241; - blockOffsets[offsetIdx(94, 15)] = 241; - setOffsetsForID(95, 270, *this); - setOffsetsForID(96, 276, *this); - blockOffsets[offsetIdx(96, 4)] = 277; - blockOffsets[offsetIdx(96, 5)] = 278; - blockOffsets[offsetIdx(96, 6)] = 279; - blockOffsets[offsetIdx(96, 7)] = 280; - setOffsetsForID(97, 1, *this); - blockOffsets[offsetIdx(96, 1)] = 4; - blockOffsets[offsetIdx(96, 2)] = 294; - setOffsetsForID(98, 294, *this); - blockOffsets[offsetIdx(98, 1)] = 295; - blockOffsets[offsetIdx(98, 2)] = 296; - blockOffsets[offsetIdx(98, 3)] = 430; - setOffsetsForID(99, 336, *this); - blockOffsets[offsetIdx(99, 1)] = 342; - blockOffsets[offsetIdx(99, 2)] = 341; - blockOffsets[offsetIdx(99, 3)] = 341; - blockOffsets[offsetIdx(99, 4)] = 342; - blockOffsets[offsetIdx(99, 5)] = 341; - blockOffsets[offsetIdx(99, 6)] = 341; - blockOffsets[offsetIdx(99, 7)] = 344; - blockOffsets[offsetIdx(99, 8)] = 343; - blockOffsets[offsetIdx(99, 9)] = 343; - blockOffsets[offsetIdx(99, 10)] = 345; - setOffsetsForID(100, 336, *this); - blockOffsets[offsetIdx(100, 1)] = 338; - blockOffsets[offsetIdx(100, 2)] = 337; - blockOffsets[offsetIdx(100, 3)] = 337; - blockOffsets[offsetIdx(100, 4)] = 338; - blockOffsets[offsetIdx(100, 5)] = 337; - blockOffsets[offsetIdx(100, 6)] = 337; - blockOffsets[offsetIdx(100, 7)] = 340; - blockOffsets[offsetIdx(100, 8)] = 339; - blockOffsets[offsetIdx(100, 9)] = 339; - blockOffsets[offsetIdx(100, 10)] = 345; - setOffsetsForID(101, 355, *this); - setOffsetsForID(102, 366, *this); - setOffsetsForID(103, 290, *this); - setOffsetsForID(104, 395, *this); - blockOffsets[offsetIdx(104, 1)] = 396; - blockOffsets[offsetIdx(104, 2)] = 397; - blockOffsets[offsetIdx(104, 3)] = 398; - blockOffsets[offsetIdx(104, 4)] = 399; - blockOffsets[offsetIdx(104, 5)] = 400; - blockOffsets[offsetIdx(104, 6)] = 401; - blockOffsets[offsetIdx(104, 7)] = 402; - setOffsetsForID(105, 395, *this); - blockOffsets[offsetIdx(105, 1)] = 396; - blockOffsets[offsetIdx(105, 2)] = 397; - blockOffsets[offsetIdx(105, 3)] = 398; - blockOffsets[offsetIdx(105, 4)] = 399; - blockOffsets[offsetIdx(105, 5)] = 400; - blockOffsets[offsetIdx(105, 6)] = 401; - blockOffsets[offsetIdx(105, 7)] = 402; - setOffsetsForID(106, 379, *this); - blockOffsets[offsetIdx(106, 2)] = 380; - blockOffsets[offsetIdx(106, 8)] = 381; - blockOffsets[offsetIdx(106, 10)] = 382; - blockOffsets[offsetIdx(106, 4)] = 383; - blockOffsets[offsetIdx(106, 6)] = 384; - blockOffsets[offsetIdx(106, 12)] = 385; - blockOffsets[offsetIdx(106, 14)] = 386; - blockOffsets[offsetIdx(106, 1)] = 387; - blockOffsets[offsetIdx(106, 3)] = 388; - blockOffsets[offsetIdx(106, 9)] = 389; - blockOffsets[offsetIdx(106, 11)] = 390; - blockOffsets[offsetIdx(106, 5)] = 391; - blockOffsets[offsetIdx(106, 7)] = 392; - blockOffsets[offsetIdx(106, 13)] = 393; - blockOffsets[offsetIdx(106, 15)] = 394; - setOffsetsForID(107, 347, *this); - blockOffsets[offsetIdx(107, 1)] = 346; - blockOffsets[offsetIdx(107, 3)] = 346; - blockOffsets[offsetIdx(107, 5)] = 346; - blockOffsets[offsetIdx(107, 7)] = 346; - setOffsetsForID(108, 304, *this); - blockOffsets[offsetIdx(108, 1)] = 305; - blockOffsets[offsetIdx(108, 2)] = 306; - blockOffsets[offsetIdx(108, 3)] = 307; - blockOffsets[offsetIdx(108, 4)] = 446; - blockOffsets[offsetIdx(108, 5)] = 447; - blockOffsets[offsetIdx(108, 6)] = 448; - blockOffsets[offsetIdx(108, 7)] = 449; - setOffsetsForID(109, 308, *this); - blockOffsets[offsetIdx(109, 1)] = 309; - blockOffsets[offsetIdx(109, 2)] = 310; - blockOffsets[offsetIdx(109, 3)] = 311; - blockOffsets[offsetIdx(109, 4)] = 450; - blockOffsets[offsetIdx(109, 5)] = 451; - blockOffsets[offsetIdx(109, 6)] = 452; - blockOffsets[offsetIdx(109, 7)] = 453; - setOffsetsForID(110, 291, *this); - setOffsetsForID(111, 316, *this); - setOffsetsForID(112, 292, *this); - setOffsetsForID(113, 332, *this); - setOffsetsForID(114, 312, *this); - blockOffsets[offsetIdx(114, 1)] = 313; - blockOffsets[offsetIdx(114, 2)] = 314; - blockOffsets[offsetIdx(114, 3)] = 315; - blockOffsets[offsetIdx(114, 4)] = 454; - blockOffsets[offsetIdx(114, 5)] = 455; - blockOffsets[offsetIdx(114, 6)] = 456; - blockOffsets[offsetIdx(114, 7)] = 457; - setOffsetsForID(115, 333, *this); - blockOffsets[offsetIdx(115, 1)] = 334; - blockOffsets[offsetIdx(115, 2)] = 334; - blockOffsets[offsetIdx(115, 3)] = 335; - setOffsetsForID(116, 348, *this); - setOffsetsForID(117, 350, *this); - setOffsetsForID(118, 351, *this); - blockOffsets[offsetIdx(118, 1)] = 352; - blockOffsets[offsetIdx(118, 2)] = 353; - blockOffsets[offsetIdx(118, 3)] = 354; - setOffsetsForID(119, 377, *this); - setOffsetsForID(120, 349, *this); - setOffsetsForID(121, 293, *this); - setOffsetsForID(122, 378, *this); - setOffsetsForID(123, 434, *this); - setOffsetsForID(124, 433, *this); - setOffsetsForID(125, 5, *this); - blockOffsets[offsetIdx(125, 1)] = 435; - blockOffsets[offsetIdx(125, 2)] = 436; - blockOffsets[offsetIdx(125, 3)] = 437; - setOffsetsForID(126, 230, *this); - blockOffsets[offsetIdx(126, 1)] = 464; - blockOffsets[offsetIdx(126, 2)] = 466; - blockOffsets[offsetIdx(126, 3)] = 468; - blockOffsets[offsetIdx(126, 8)] = 460; - blockOffsets[offsetIdx(126, 9)] = 465; - blockOffsets[offsetIdx(126, 10)] = 467; - blockOffsets[offsetIdx(126, 11)] = 469; - setOffsetsForID(127, 522, *this); - blockOffsets[offsetIdx(127, 1)] = 519; - blockOffsets[offsetIdx(127, 2)] = 521; - blockOffsets[offsetIdx(127, 3)] = 520; - blockOffsets[offsetIdx(127, 4)] = 526; - blockOffsets[offsetIdx(127, 5)] = 523; - blockOffsets[offsetIdx(127, 6)] = 525; - blockOffsets[offsetIdx(127, 7)] = 524; - blockOffsets[offsetIdx(127, 8)] = 530; - blockOffsets[offsetIdx(127, 9)] = 527; - blockOffsets[offsetIdx(127, 10)] = 529; - blockOffsets[offsetIdx(127, 11)] = 528; - setOffsetsForID(128, 470, *this); - blockOffsets[offsetIdx(128, 1)] = 471; - blockOffsets[offsetIdx(128, 2)] = 472; - blockOffsets[offsetIdx(128, 3)] = 473; - blockOffsets[offsetIdx(128, 4)] = 474; - blockOffsets[offsetIdx(128, 5)] = 475; - blockOffsets[offsetIdx(128, 6)] = 476; - blockOffsets[offsetIdx(128, 7)] = 477; - setOffsetsForID(129, 478, *this); - setOffsetsForID(130, 479, *this); - blockOffsets[offsetIdx(130, 4)] = 480; - blockOffsets[offsetIdx(130, 2)] = 481; - blockOffsets[offsetIdx(130, 5)] = 481; - blockOffsets[offsetIdx(131, 0)] = 542; - blockOffsets[offsetIdx(131, 4)] = 542; - blockOffsets[offsetIdx(131, 8)] = 542; - blockOffsets[offsetIdx(131, 12)] = 542; - blockOffsets[offsetIdx(131, 1)] = 539; - blockOffsets[offsetIdx(131, 5)] = 539; - blockOffsets[offsetIdx(131, 9)] = 539; - blockOffsets[offsetIdx(131, 13)] = 539; - blockOffsets[offsetIdx(131, 2)] = 541; - blockOffsets[offsetIdx(131, 6)] = 541; - blockOffsets[offsetIdx(131, 10)] = 541; - blockOffsets[offsetIdx(131, 14)] = 541; - blockOffsets[offsetIdx(131, 3)] = 540; - blockOffsets[offsetIdx(131, 7)] = 540; - blockOffsets[offsetIdx(131, 11)] = 540; - blockOffsets[offsetIdx(131, 15)] = 540; - setOffsetsForID(132, 543, *this); - setOffsetsForID(133, 482, *this); - setOffsetsForID(134, 495, *this); - blockOffsets[offsetIdx(134, 1)] = 496; - blockOffsets[offsetIdx(134, 2)] = 497; - blockOffsets[offsetIdx(134, 3)] = 498; - blockOffsets[offsetIdx(134, 4)] = 499; - blockOffsets[offsetIdx(134, 5)] = 500; - blockOffsets[offsetIdx(134, 6)] = 501; - blockOffsets[offsetIdx(134, 7)] = 502; - setOffsetsForID(135, 503, *this); - blockOffsets[offsetIdx(135, 1)] = 504; - blockOffsets[offsetIdx(135, 2)] = 505; - blockOffsets[offsetIdx(135, 3)] = 506; - blockOffsets[offsetIdx(135, 4)] = 507; - blockOffsets[offsetIdx(135, 5)] = 508; - blockOffsets[offsetIdx(135, 6)] = 509; - blockOffsets[offsetIdx(135, 7)] = 510; - setOffsetsForID(136, 511, *this); - blockOffsets[offsetIdx(136, 1)] = 512; - blockOffsets[offsetIdx(136, 2)] = 513; - blockOffsets[offsetIdx(136, 3)] = 514; - blockOffsets[offsetIdx(136, 4)] = 515; - blockOffsets[offsetIdx(136, 5)] = 516; - blockOffsets[offsetIdx(136, 6)] = 517; - blockOffsets[offsetIdx(136, 7)] = 518; -} - -void BlockImages::checkOpacityAndTransparency(int B) -{ - opacity.clear(); - opacity.resize(NUMBLOCKIMAGES, true); - transparency.clear(); - transparency.resize(NUMBLOCKIMAGES, true); - - for (int i = 0; i < NUMBLOCKIMAGES; i++) - { - ImageRect rect = getRect(i); - // use the face iterators to examine the N, W, and U faces; any non-100% alpha makes - // the block non-opaque, and any non-0% alpha makes the block non-transparent - int tilesize = 2*B; - // N face starts at [0,B] - for (FaceIterator it(rect.x, rect.y + B, 1, tilesize); !it.end; it.advance()) - { - int a = ALPHA(img(it.x, it.y)); - if (a < 255) - opacity[i] = false; - if (a > 0) - transparency[i] = false; - if (!opacity[i] && !transparency[i]) - break; - } - if (!opacity[i] && !transparency[i]) - continue; - // W face starts at [2B,2B] - for (FaceIterator it(rect.x + 2*B, rect.y + 2*B, -1, tilesize); !it.end; it.advance()) - { - int a = ALPHA(img(it.x, it.y)); - if (a < 255) - opacity[i] = false; - if (a > 0) - transparency[i] = false; - if (!opacity[i] && !transparency[i]) - break; - } - if (!opacity[i] && !transparency[i]) - continue; - // U face starts at [2B-1,0] - for (TopFaceIterator it(rect.x + 2*B-1, rect.y, tilesize); !it.end; it.advance()) - { - int a = ALPHA(img(it.x, it.y)); - if (a < 255) - opacity[i] = false; - if (a > 0) - transparency[i] = false; - if (!opacity[i] && !transparency[i]) - break; - } - } -} - -void BlockImages::retouchAlphas(int B) -{ - for (int i = 0; i < NUMBLOCKIMAGES; i++) - { - ImageRect rect = getRect(i); - // use the face iterators to examine the N, W, and U faces; any alpha under 10 is changed - // to 0, and any alpha above 245 is changed to 255 - int tilesize = 2*B; - // N face starts at [0,B] - for (FaceIterator it(rect.x, rect.y + B, 1, tilesize); !it.end; it.advance()) - { - int a = ALPHA(img(it.x, it.y)); - if (a < 10) - setAlpha(img(it.x, it.y), 0); - else if (a > 245) - setAlpha(img(it.x, it.y), 255); - } - // W face starts at [2B,2B] - for (FaceIterator it(rect.x + 2*B, rect.y + 2*B, -1, tilesize); !it.end; it.advance()) - { - int a = ALPHA(img(it.x, it.y)); - if (a < 10) - setAlpha(img(it.x, it.y), 0); - else if (a > 245) - setAlpha(img(it.x, it.y), 255); - } - // U face starts at [2B-1,0] - for (TopFaceIterator it(rect.x + 2*B-1, rect.y, tilesize); !it.end; it.advance()) - { - int a = ALPHA(img(it.x, it.y)); - if (a < 10) - setAlpha(img(it.x, it.y), 0); - else if (a > 245) - setAlpha(img(it.x, it.y), 255); - } - } -} - -int deinterpolate(int targetj, int srcrange, int destrange) -{ - for (int i = 0; i < destrange; i++) - { - int j = interpolate(i, destrange, srcrange); - if (j >= targetj) - return i; - } - return destrange - 1; -} - -bool BlockImages::construct(int B, const string& terrainfile, const string& firefile, const string& endportalfile, const string& chestfile, const string& largechestfile, const string& enderchestfile) -{ - if (B < 2) - return false; - - // read the terrain file, check that it's okay, and get a resized copy for use - RGBAImage terrain; - if (!terrain.readPNG(terrainfile)) - return false; - if (terrain.w % 16 != 0 || terrain.h != terrain.w) - return false; - int terrainSize = terrain.w / 16; - RGBAImage tiles = getResizedTerrain(terrain, terrainSize, B); - - // read fire.png, make sure it's okay, and get a resized copy - RGBAImage fire; - if (!fire.readPNG(firefile)) - return false; - if (fire.w != fire.h) - return false; - RGBAImage firetile; - firetile.create(2*B, 2*B); - resize(fire, ImageRect(0, 0, fire.w, fire.h), firetile, ImageRect(0, 0, 2*B, 2*B)); - - // read endportal.png, make sure it's okay, and get a resized copy - RGBAImage endportal; - if (!endportal.readPNG(endportalfile)) - return false; - if (endportal.w != endportal.h) - return false; - RGBAImage endportaltile; - endportaltile.create(2*B, 2*B); - resize(endportal, ImageRect(0, 0, endportal.w, endportal.h), endportaltile, ImageRect(0, 0, 2*B, 2*B)); - - // read chest.png, make sure it's okay, and build resized tiles - RGBAImage chest; - if (!chest.readPNG(chestfile)) - return false; - if (chest.w % 64 != 0 || chest.h != chest.w) - return false; - int chestScale = chest.w / 64; - RGBAImage chesttiles = getResizedChest(chest, chestScale, B); - - // read enderchest.png, make sure it's okay, and build resized tiles - RGBAImage enderchest; - if (!enderchest.readPNG(enderchestfile)) - return false; - if (enderchest.w % 64 != 0 || enderchest.h != enderchest.w) - return false; - int enderchestScale = enderchest.w / 64; - RGBAImage enderchesttiles = getResizedChest(enderchest, enderchestScale, B); - - // read largechest.png, make sure it's okay, and build resized tiles - RGBAImage largechest; - if (!largechest.readPNG(largechestfile)) - return false; - if (largechest.w % 128 != 0 || largechest.h != largechest.w / 2) - return false; - int largechestScale = largechest.w / 128; - RGBAImage largechesttiles = getResizedLargeChest(largechest, largechestScale, B); - - // colorize various tiles - darken(tiles, ImageRect(0, 0, 2*B, 2*B), 0.6, 0.95, 0.3); // tile 0 = grass top - darken(tiles, ImageRect(14*B, 4*B, 2*B, 2*B), 0.6, 0.95, 0.3); // tile 39 = tall grass - darken(tiles, ImageRect(16*B, 6*B, 2*B, 2*B), 0.6, 0.95, 0.3); // tile 56 = fern - darken(tiles, ImageRect(8*B, 20*B, 2*B, 2*B), 0.9, 0.1, 0.1); // tile 164 = redstone dust - darken(tiles, ImageRect(24*B, 8*B, 2*B, 2*B), 0.3, 0.95, 0.3); // tile 76 = lily pad - darken(tiles, ImageRect(30*B, 16*B, 2*B, 2*B), 0.35, 1.0, 0.15); // tile 143 = vines - - // create colorized copies of leaf tiles (can't colorize in place because normal and - // birch leaves use the same texture) - RGBAImage leaftiles; - leaftiles.create(8*B, 2*B); - // normal - blit(tiles, ImageRect(8*B, 6*B, 2*B, 2*B), leaftiles, 0, 0); - darken(leaftiles, ImageRect(0, 0, 2*B, 2*B), 0.3, 1.0, 0.1); - // pine - blit(tiles, ImageRect(8*B, 16*B, 2*B, 2*B), leaftiles, 2*B, 0); - darken(leaftiles, ImageRect(2*B, 0, 2*B, 2*B), 0.3, 1.0, 0.45); - // birch - blit(tiles, ImageRect(8*B, 6*B, 2*B, 2*B), leaftiles, 4*B, 0); - darken(leaftiles, ImageRect(4*B, 0, 2*B, 2*B), 0.55, 0.9, 0.1); - // jungle - blit(tiles, ImageRect(8*B, 24*B, 2*B, 2*B), leaftiles, 6*B, 0); - darken(leaftiles, ImageRect(6*B, 0, 2*B, 2*B), 0.35, 1.0, 0.05); - - // create colorized/shortened copies of stem tiles - RGBAImage stemtiles; - stemtiles.create(20*B, 2*B); - // levels 0-7 - for (int i = 1; i <= 8; i++) - blit(tiles, ImageRect(30*B, 12*B, 2*B, i*B/4), stemtiles, (i-1)*2*B, 2*B - i*B/4); - // stem connecting to melon/pumpkin, and flipped version - blit(tiles, ImageRect(30*B, 14*B, 2*B, 2*B), stemtiles, 16*B, 0); - blit(tiles, ImageRect(30*B, 14*B, 2*B, 2*B), stemtiles, 18*B, 0); - flipX(stemtiles, ImageRect(18*B, 0, 2*B, 2*B)); - // green for levels 0-6, brown for level 7 and the connectors - darken(stemtiles, ImageRect(0, 0, 14*B, 2*B), 0.45, 0.95, 0.4); - darken(stemtiles, ImageRect(14*B, 0, 6*B, 2*B), 0.75, 0.6, 0.3); - - // calculate the pixel offset used for cactus/cake; represents one pixel of the default - // 16x16 texture size - int smallOffset = (terrainSize + 15) / 16; // ceil(terrainSize/16) - - // resize the cactus tiles again, this time taking a smaller portion of the terrain - // image (to drop the transparent border) - resize(terrain, ImageRect(5*terrainSize + smallOffset, 4*terrainSize + smallOffset, terrainSize - 2*smallOffset, terrainSize - 2*smallOffset), - tiles, ImageRect(5*2*B, 4*2*B, 2*B, 2*B)); - resize(terrain, ImageRect(6*terrainSize + smallOffset, 4*terrainSize, terrainSize - 2*smallOffset, terrainSize), - tiles, ImageRect(6*2*B, 4*2*B, 2*B, 2*B)); - - // ...and the same thing for the cake tiles - resize(terrain, ImageRect(9*terrainSize + smallOffset, 7*terrainSize + smallOffset, terrainSize - 2*smallOffset, terrainSize - 2*smallOffset), - tiles, ImageRect(9*2*B, 7*2*B, 2*B, 2*B)); - resize(terrain, ImageRect(10*terrainSize + smallOffset, 7*terrainSize, terrainSize - 2*smallOffset, terrainSize), - tiles, ImageRect(10*2*B, 7*2*B, 2*B, 2*B)); - - // determine some cutoff values for partial block images: given a particular pixel offset in terrain.png--for - // example, the end portal frame texture is missing its top 3 (out of 16) pixels--we need to know which pixel - // in the resized tile is the first one past that offset - // ...if the terrain tile size isn't a multiple of 16 for some reason, this may break down and be ugly - int CUTOFF_2_16 = deinterpolate(2 * terrainSize/16, terrainSize, 2*B); - int CUTOFF_3_16 = deinterpolate(3 * terrainSize/16, terrainSize, 2*B); - int CUTOFF_4_16 = deinterpolate(4 * terrainSize/16, terrainSize, 2*B); - int CUTOFF_6_16 = deinterpolate(6 * terrainSize/16, terrainSize, 2*B); - int CUTOFF_8_16 = deinterpolate(8 * terrainSize/16, terrainSize, 2*B); - int CUTOFF_10_16 = deinterpolate(10 * terrainSize/16, terrainSize, 2*B); - int CUTOFF_12_16 = deinterpolate(12 * terrainSize/16, terrainSize, 2*B); - int CUTOFF_14_16 = deinterpolate(14 * terrainSize/16, terrainSize, 2*B); - - // initialize image - img.create(rectsize * 16, (NUMBLOCKIMAGES/16 + 1) * rectsize); - - // build all block images - - drawBlockImage(img, getRect(1), tiles, 1, 1, 1, B); // stone - drawBlockImage(img, getRect(2), tiles, 3, 3, 0, B); // grass - drawBlockImage(img, getRect(3), tiles, 2, 2, 2, B); // dirt - drawBlockImage(img, getRect(4), tiles, 16, 16, 16, B); // cobblestone - drawBlockImage(img, getRect(5), tiles, 4, 4, 4, B); // planks - drawBlockImage(img, getRect(435), tiles, 198, 198, 198, B); // pine planks - drawBlockImage(img, getRect(436), tiles, 214, 214, 214, B); // birch planks - drawBlockImage(img, getRect(437), tiles, 199, 199, 199, B); // jungle planks - drawBlockImage(img, getRect(7), tiles, 17, 17, 17, B); // bedrock - drawBlockImage(img, getRect(8), tiles, 205, 205, 205, B); // full water - drawBlockImage(img, getRect(157), tiles, -1, -1, 205, B); // water surface - drawBlockImage(img, getRect(178), tiles, 205, -1, 205, B); // water missing W - drawBlockImage(img, getRect(179), tiles, -1, 205, 205, B); // water missing N - drawBlockImage(img, getRect(16), tiles, 237, 237, 237, B); // full lava - drawBlockImage(img, getRect(20), tiles, 18, 18, 18, B); // sand - drawBlockImage(img, getRect(483), tiles, 19, 19, 19, B); // gravel - drawBlockImage(img, getRect(22), tiles, 32, 32, 32, B); // gold ore - drawBlockImage(img, getRect(23), tiles, 33, 33, 33, B); // iron ore - drawBlockImage(img, getRect(24), tiles, 34, 34, 34, B); // coal ore - drawBlockImage(img, getRect(25), tiles, 20, 20, 21, B); // log - drawBlockImage(img, getRect(219), tiles, 116, 116, 21, B); // pine log - drawBlockImage(img, getRect(220), tiles, 117, 117, 21, B); // birch log - drawBlockImage(img, getRect(427), tiles, 153, 153, 21, B); // jungle log - drawBlockImage(img, getRect(26), leaftiles, 0, 0, 0, B); // leaves - drawBlockImage(img, getRect(248), leaftiles, 1, 1, 1, B); // pine leaves - drawBlockImage(img, getRect(249), leaftiles, 2, 2, 2, B); // birch leaves - drawBlockImage(img, getRect(428), leaftiles, 3, 3, 3, B); // jungle leaves - drawBlockImage(img, getRect(27), tiles, 48, 48, 48, B); // sponge - drawBlockImage(img, getRect(28), tiles, 49, 49, 49, B); // glass - drawBlockImage(img, getRect(29), tiles, 64, 64, 64, B); // white wool - drawBlockImage(img, getRect(204), tiles, 210, 210, 210, B); // orange wool - drawBlockImage(img, getRect(205), tiles, 194, 194, 194, B); // magenta wool - drawBlockImage(img, getRect(206), tiles, 178, 178, 178, B); // light blue wool - drawBlockImage(img, getRect(207), tiles, 162, 162, 162, B); // yellow wool - drawBlockImage(img, getRect(208), tiles, 146, 146, 146, B); // lime wool - drawBlockImage(img, getRect(209), tiles, 130, 130, 130, B); // pink wool - drawBlockImage(img, getRect(210), tiles, 114, 114, 114, B); // gray wool - drawBlockImage(img, getRect(211), tiles, 225, 225, 225, B); // light gray wool - drawBlockImage(img, getRect(212), tiles, 209, 209, 209, B); // cyan wool - drawBlockImage(img, getRect(213), tiles, 193, 193, 193, B); // purple wool - drawBlockImage(img, getRect(214), tiles, 177, 177, 177, B); // blue wool - drawBlockImage(img, getRect(215), tiles, 161, 161, 161, B); // brown wool - drawBlockImage(img, getRect(216), tiles, 145, 145, 145, B); // green wool - drawBlockImage(img, getRect(217), tiles, 129, 129, 129, B); // red wool - drawBlockImage(img, getRect(218), tiles, 113, 113, 113, B); // black wool - drawBlockImage(img, getRect(34), tiles, 23, 23, 23, B); // gold block - drawBlockImage(img, getRect(35), tiles, 22, 22, 22, B); // iron block - drawBlockImage(img, getRect(36), tiles, 5, 5, 6, B); // double stone slab - drawBlockImage(img, getRect(38), tiles, 7, 7, 7, B); // brick - drawBlockImage(img, getRect(39), tiles, 8, 8, 9, B); // TNT - drawBlockImage(img, getRect(40), tiles, 35, 35, 4, B); // bookshelf - drawBlockImage(img, getRect(41), tiles, 36, 36, 36, B); // mossy cobblestone - drawBlockImage(img, getRect(42), tiles, 37, 37, 37, B); // obsidian - drawBlockImage(img, getRect(49), tiles, 65, 65, 65, B); // spawner - drawBlockImage(img, getRect(484), chesttiles, 2, 1, 0, B); // chest facing W - drawBlockImage(img, getRect(485), chesttiles, 1, 2, 0, B); // chest facing N - drawBlockImage(img, getRect(486), chesttiles, 2, 2, 0, B); // chest facing E/S - drawBlockImage(img, getRect(479), enderchesttiles, 2, 1, 0, B); // ender chest facing W - drawBlockImage(img, getRect(480), enderchesttiles, 1, 2, 0, B); // ender chest facing N - drawBlockImage(img, getRect(481), enderchesttiles, 2, 2, 0, B); // ender chest facing E/S - drawBlockImage(img, getRect(489), largechesttiles, 2, 6, 0, B); // double chest E facing N - drawBlockImage(img, getRect(490), largechesttiles, 3, 6, 1, B); // double chest W facing N - drawBlockImage(img, getRect(493), largechesttiles, 4, 6, 0, B); // double chest E facing S - drawBlockImage(img, getRect(494), largechesttiles, 5, 6, 1, B); // double chest W facing S - drawBlockImage(img, getRect(270), chesttiles, 2, 1, 0, B); // locked chest facing W - drawBlockImage(img, getRect(271), chesttiles, 1, 2, 0, B); // locked chest facing N - drawBlockImage(img, getRect(56), tiles, 50, 50, 50, B); // diamond ore - drawBlockImage(img, getRect(57), tiles, 24, 24, 24, B); // diamond block - drawBlockImage(img, getRect(58), tiles, 59, 60, 43, B); // workbench - drawBlockImage(img, getRect(67), tiles, 2, 2, 87, B); // farmland - drawBlockImage(img, getRect(183), tiles, 45, 44, 62, B); // furnace W - drawBlockImage(img, getRect(184), tiles, 44, 45, 62, B); // furnace N - drawBlockImage(img, getRect(185), tiles, 45, 45, 62, B); // furnace E/S - drawBlockImage(img, getRect(186), tiles, 45, 61, 62, B); // lit furnace W - drawBlockImage(img, getRect(187), tiles, 61, 45, 62, B); // lit furnace N - drawBlockImage(img, getRect(188), tiles, 45, 45, 62, B); // lit furnace E/S - drawBlockImage(img, getRect(120), tiles, 51, 51, 51, B); // redstone ore - drawBlockImage(img, getRect(128), tiles, 67, 67, 67, B); // ice - drawBlockImage(img, getRect(180), tiles, -1, -1, 67, B); // ice surface - drawBlockImage(img, getRect(181), tiles, 67, -1, 67, B); // ice missing W - drawBlockImage(img, getRect(182), tiles, -1, 67, 67, B); // ice missing N - drawBlockImage(img, getRect(129), tiles, 66, 66, 66, B); // snow block - drawBlockImage(img, getRect(130), tiles, 70, 70, 69, B); // cactus - drawBlockImage(img, getRect(131), tiles, 72, 72, 72, B); // clay - drawBlockImage(img, getRect(133), tiles, 74, 74, 75, B); // jukebox - drawBlockImage(img, getRect(135), tiles, 118, 119, 102, B); // pumpkin facing W - drawBlockImage(img, getRect(153), tiles, 118, 118, 102, B); // pumpkin facing E/S - drawBlockImage(img, getRect(154), tiles, 119, 118, 102, B); // pumpkin facing N - drawBlockImage(img, getRect(136), tiles, 103, 103, 103, B); // netherrack - drawBlockImage(img, getRect(137), tiles, 104, 104, 104, B); // soul sand - drawBlockImage(img, getRect(138), tiles, 105, 105, 105, B); // glowstone - drawBlockImage(img, getRect(140), tiles, 118, 120, 102, B); // jack-o-lantern W - drawBlockImage(img, getRect(155), tiles, 118, 118, 102, B); // jack-o-lantern E/S - drawBlockImage(img, getRect(156), tiles, 120, 118, 102, B); // jack-o-lantern N - drawBlockImage(img, getRect(221), tiles, 160, 160, 160, B); // lapis ore - drawBlockImage(img, getRect(222), tiles, 144, 144, 144, B); // lapis block - drawBlockImage(img, getRect(223), tiles, 45, 46, 62, B); // dispenser W - drawBlockImage(img, getRect(224), tiles, 46, 45, 62, B); // dispenser N - drawBlockImage(img, getRect(225), tiles, 45, 45, 62, B); // dispenser E/S - drawBlockImage(img, getRect(226), tiles, 192, 192, 176, B); // sandstone - drawBlockImage(img, getRect(431), tiles, 229, 229, 176, B); // hieroglyphic sandstone - drawBlockImage(img, getRect(432), tiles, 230, 230, 176, B); // smooth sandstone - drawBlockImage(img, getRect(227), tiles, 74, 74, 74, B); // note block - drawBlockImage(img, getRect(290), tiles, 136, 136, 137, B); // melon - drawBlockImage(img, getRect(291), tiles, 77, 77, 78, B); // mycelium - drawBlockImage(img, getRect(292), tiles, 224, 224, 224, B); // nether brick - drawBlockImage(img, getRect(293), tiles, 175, 175, 175, B); // end stone - drawBlockImage(img, getRect(294), tiles, 54, 54, 54, B); // stone brick - drawBlockImage(img, getRect(295), tiles, 100, 100, 100, B); // mossy stone brick - drawBlockImage(img, getRect(296), tiles, 101, 101, 101, B); // cracked stone brick - drawBlockImage(img, getRect(430), tiles, 213, 213, 213, B); // circle stone brick - drawBlockImage(img, getRect(336), tiles, 142, 142, 142, B); // mushroom flesh - drawBlockImage(img, getRect(337), tiles, 142, 142, 125, B); // red cap top only - drawBlockImage(img, getRect(338), tiles, 125, 142, 125, B); // red cap N - drawBlockImage(img, getRect(339), tiles, 142, 125, 125, B); // red cap W - drawBlockImage(img, getRect(340), tiles, 125, 125, 125, B); // red cap NW - drawBlockImage(img, getRect(341), tiles, 142, 142, 126, B); // brown cap top only - drawBlockImage(img, getRect(342), tiles, 126, 142, 126, B); // brown cap N - drawBlockImage(img, getRect(343), tiles, 142, 126, 126, B); // brown cap W - drawBlockImage(img, getRect(344), tiles, 126, 126, 126, B); // brown cap NW - drawBlockImage(img, getRect(345), tiles, 141, 141, 142, B); // mushroom stem - drawBlockImage(img, getRect(433), tiles, 212, 212, 212, B); // redstone lamp on - drawBlockImage(img, getRect(434), tiles, 211, 211, 211, B); // redstone lamp off - drawBlockImage(img, getRect(478), tiles, 171, 171, 171, B); // emerald ore - drawBlockImage(img, getRect(482), tiles, 25, 25, 25, B); // emerald block - drawRotatedBlockImage(img, getRect(407), tiles, 108, 108, 109, 2, false, 2, false, 0, false, B); // closed piston D - drawRotatedBlockImage(img, getRect(408), tiles, 108, 108, 107, 0, false, 0, false, 0, false, B); // closed piston U - drawRotatedBlockImage(img, getRect(409), tiles, 107, 108, 108, 0, false, 1, false, 2, false, B); // closed piston N - drawRotatedBlockImage(img, getRect(410), tiles, 109, 108, 108, 0, false, 3, false, 0, false, B); // closed piston S - drawRotatedBlockImage(img, getRect(411), tiles, 108, 107, 108, 3, false, 0, false, 3, false, B); // closed piston W - drawRotatedBlockImage(img, getRect(412), tiles, 108, 109, 108, 1, false, 0, false, 1, false, B); // closed piston E - drawRotatedBlockImage(img, getRect(413), tiles, 108, 108, 109, 2, false, 2, false, 0, false, B); // closed sticky piston D - drawRotatedBlockImage(img, getRect(414), tiles, 108, 108, 106, 0, false, 0, false, 0, false, B); // closed sticky piston U - drawRotatedBlockImage(img, getRect(415), tiles, 106, 108, 108, 0, false, 1, false, 2, false, B); // closed sticky piston N - drawRotatedBlockImage(img, getRect(416), tiles, 109, 108, 108, 0, false, 3, false, 0, false, B); // closed sticky piston S - drawRotatedBlockImage(img, getRect(417), tiles, 108, 106, 108, 3, false, 0, false, 3, false, B); // closed sticky piston W - drawRotatedBlockImage(img, getRect(418), tiles, 108, 109, 108, 1, false, 0, false, 1, false, B); // closed sticky piston E - drawRotatedBlockImage(img, getRect(487), largechesttiles, 6, 2, 0, 0, false, 0, false, 1, false, B); // double chest N facing W - drawRotatedBlockImage(img, getRect(488), largechesttiles, 6, 3, 1, 0, false, 0, false, 1, false, B); // double chest S facing W - drawRotatedBlockImage(img, getRect(491), largechesttiles, 6, 4, 0, 0, false, 0, false, 1, false, B); // double chest N facing E - drawRotatedBlockImage(img, getRect(492), largechesttiles, 6, 5, 1, 0, false, 0, false, 1, false, B); // double chest S facing E - drawRotatedBlockImage(img, getRect(531), tiles, 20, 21, 20, 1, false, 0, false, 1, false, B); // log EW - drawRotatedBlockImage(img, getRect(532), tiles, 21, 20, 20, 0, false, 3, false, 0, false, B); // log NS - drawRotatedBlockImage(img, getRect(533), tiles, 116, 21, 116, 1, false, 0, false, 1, false, B); // pine log EW - drawRotatedBlockImage(img, getRect(534), tiles, 21, 116, 116, 0, false, 3, false, 0, false, B); // pine log NS - drawRotatedBlockImage(img, getRect(535), tiles, 117, 21, 117, 1, false, 0, false, 1, false, B); // birch log EW - drawRotatedBlockImage(img, getRect(536), tiles, 21, 117, 117, 0, false, 3, false, 0, false, B); // birch log NS - drawRotatedBlockImage(img, getRect(537), tiles, 153, 21, 153, 1, false, 0, false, 1, false, B); // jungle log EW - drawRotatedBlockImage(img, getRect(538), tiles, 21, 153, 153, 0, false, 3, false, 0, false, B); // jungle log NS - - drawPartialBlockImage(img, getRect(9), tiles, 205, 205, 205, B, CUTOFF_2_16, 0, 0, 0, true); // water level 7 - drawPartialBlockImage(img, getRect(10), tiles, 205, 205, 205, B, CUTOFF_4_16, 0, 0, 0, true); // water level 6 - drawPartialBlockImage(img, getRect(11), tiles, 205, 205, 205, B, CUTOFF_6_16, 0, 0, 0, true); // water level 5 - drawPartialBlockImage(img, getRect(12), tiles, 205, 205, 205, B, CUTOFF_8_16, 0, 0, 0, true); // water level 4 - drawPartialBlockImage(img, getRect(13), tiles, 205, 205, 205, B, CUTOFF_10_16, 0, 0, 0, true); // water level 3 - drawPartialBlockImage(img, getRect(14), tiles, 205, 205, 205, B, CUTOFF_12_16, 0, 0, 0, true); // water level 2 - drawPartialBlockImage(img, getRect(15), tiles, 205, 205, 205, B, CUTOFF_14_16, 0, 0, 0, true); // water level 1 - drawPartialBlockImage(img, getRect(17), tiles, 237, 237, 237, B, CUTOFF_4_16, 0, 0, 0, true); // lava level 3 - drawPartialBlockImage(img, getRect(18), tiles, 237, 237, 237, B, CUTOFF_8_16, 0, 0, 0, true); // lava level 2 - drawPartialBlockImage(img, getRect(19), tiles, 237, 237, 237, B, CUTOFF_12_16, 0, 0, 0, true); // lava level 1 - drawPartialBlockImage(img, getRect(37), tiles, 5, 5, 6, B, CUTOFF_8_16, 0, 0, 0, true); // stone slab - drawPartialBlockImage(img, getRect(229), tiles, 192, 192, 176, B, CUTOFF_8_16, 0, 0, 0, true); // sandstone slab - drawPartialBlockImage(img, getRect(230), tiles, 4, 4, 4, B, CUTOFF_8_16, 0, 0, 0, true); // wooden slab - drawPartialBlockImage(img, getRect(231), tiles, 16, 16, 16, B, CUTOFF_8_16, 0, 0, 0, true); // cobble slab - drawPartialBlockImage(img, getRect(302), tiles, 7, 7, 7, B, CUTOFF_8_16, 0, 0, 0, true); // brick slab - drawPartialBlockImage(img, getRect(303), tiles, 54, 54, 54, B, CUTOFF_8_16, 0, 0, 0, true); // stone brick slab - drawPartialBlockImage(img, getRect(464), tiles, 198, 198, 198, B, CUTOFF_8_16, 0, 0, 0, true); // pine slab - drawPartialBlockImage(img, getRect(466), tiles, 214, 214, 214, B, CUTOFF_8_16, 0, 0, 0, true); // birch slab - drawPartialBlockImage(img, getRect(468), tiles, 199, 199, 199, B, CUTOFF_8_16, 0, 0, 0, true); // jungle slab - drawPartialBlockImage(img, getRect(458), tiles, 5, 5, 6, B, 0, CUTOFF_8_16, 0, 0, false); // stone slab inv - drawPartialBlockImage(img, getRect(459), tiles, 192, 192, 176, B, 0, CUTOFF_8_16, 0, 0, false); // sandstone slab inv - drawPartialBlockImage(img, getRect(460), tiles, 4, 4, 4, B, 0, CUTOFF_8_16, 0, 0, false); // wooden slab inv - drawPartialBlockImage(img, getRect(461), tiles, 16, 16, 16, B, 0, CUTOFF_8_16, 0, 0, false); // cobble slab inv - drawPartialBlockImage(img, getRect(462), tiles, 7, 7, 7, B, 0, CUTOFF_8_16, 0, 0, false); // brick slab inv - drawPartialBlockImage(img, getRect(463), tiles, 54, 54, 54, B, 0, CUTOFF_8_16, 0, 0, false); // stone brick slab inv - drawPartialBlockImage(img, getRect(465), tiles, 198, 198, 198, B, 0, CUTOFF_8_16, 0, 0, false); // pine slab inv - drawPartialBlockImage(img, getRect(467), tiles, 214, 214, 214, B, 0, CUTOFF_8_16, 0, 0, false); // birch slab inv - drawPartialBlockImage(img, getRect(469), tiles, 199, 199, 199, B, 0, CUTOFF_8_16, 0, 0, false); // jungle slab inv - drawPartialBlockImage(img, getRect(110), tiles, 1, 1, 1, B, CUTOFF_14_16, 0, 0, 0, true); // stone pressure plate - drawPartialBlockImage(img, getRect(119), tiles, 4, 4, 4, B, CUTOFF_14_16, 0, 0, 0, true); // wood pressure plate - drawPartialBlockImage(img, getRect(127), tiles, 66, 66, 66, B, CUTOFF_12_16, 0, 0, 0, true); // snow - drawPartialBlockImage(img, getRect(289), tiles, 122, 122, 121, B, CUTOFF_8_16, 0, 0, 0, false); // cake - drawPartialBlockImage(img, getRect(281), tiles, 151, 152, 135, B, CUTOFF_8_16, 0, 0, 0, false); // bed head W - drawPartialBlockImage(img, getRect(282), tiles, 152, 151, 135, B, CUTOFF_8_16, 0, 3, 2, false); // bed head N - drawPartialBlockImage(img, getRect(283), tiles, 151, -1, 135, B, CUTOFF_8_16, 0, 2, 1, false); // bed head E - drawPartialBlockImage(img, getRect(284), tiles, -1, 151, 135, B, CUTOFF_8_16, 0, 1, 0, false); // bed head S - drawPartialBlockImage(img, getRect(285), tiles, 150, -1, 134, B, CUTOFF_8_16, 0, 0, 0, false); // bed foot W - drawPartialBlockImage(img, getRect(286), tiles, -1, 150, 134, B, CUTOFF_8_16, 0, 3, 2, false); // bed foot N - drawPartialBlockImage(img, getRect(287), tiles, 150, 149, 134, B, CUTOFF_8_16, 0, 2, 1, false); // bed foot E - drawPartialBlockImage(img, getRect(288), tiles, 149, 150, 134, B, CUTOFF_8_16, 0, 1, 0, false); // bed foot S - drawPartialBlockImage(img, getRect(348), tiles, 182, 182, 166, B, CUTOFF_4_16, 0, 0, 0, false); // enchantment table - drawPartialBlockImage(img, getRect(349), tiles, 159, 159, 158, B, CUTOFF_3_16, 0, 0, 0, false); // end portal frame - drawPartialBlockImage(img, getRect(377), endportaltile, 0, 0, 0, B, CUTOFF_4_16, 0, 0, 0, true); // end portal - - drawItemBlockImage(img, getRect(6), tiles, 15, B); // sapling - drawItemBlockImage(img, getRect(30), tiles, 13, B); // yellow flower - drawItemBlockImage(img, getRect(31), tiles, 12, B); // red rose - drawItemBlockImage(img, getRect(32), tiles, 29, B); // brown mushroom - drawItemBlockImage(img, getRect(33), tiles, 28, B); // red mushroom - drawItemBlockImage(img, getRect(43), tiles, 80, B); // torch floor - drawItemBlockImage(img, getRect(59), tiles, 95, B); // wheat level 7 - drawItemBlockImage(img, getRect(60), tiles, 94, B); // wheat level 6 - drawItemBlockImage(img, getRect(61), tiles, 93, B); // wheat level 5 - drawItemBlockImage(img, getRect(62), tiles, 92, B); // wheat level 4 - drawItemBlockImage(img, getRect(63), tiles, 91, B); // wheat level 3 - drawItemBlockImage(img, getRect(64), tiles, 90, B); // wheat level 2 - drawItemBlockImage(img, getRect(65), tiles, 89, B); // wheat level 1 - drawItemBlockImage(img, getRect(66), tiles, 88, B); // wheat level 0 - drawItemBlockImage(img, getRect(121), tiles, 115, B); // red torch floor off - drawItemBlockImage(img, getRect(122), tiles, 99, B); // red torch floor on - drawItemBlockImage(img, getRect(132), tiles, 73, B); // reeds - drawItemBlockImage(img, getRect(250), tiles, 63, B); // pine sapling - drawItemBlockImage(img, getRect(251), tiles, 79, B); // birch sapling - drawItemBlockImage(img, getRect(429), tiles, 30, B); // birch sapling - drawItemBlockImage(img, getRect(272), tiles, 11, B); // web - drawItemBlockImage(img, getRect(273), tiles, 39, B); // tall grass - drawItemBlockImage(img, getRect(274), tiles, 56, B); // fern - drawItemBlockImage(img, getRect(275), tiles, 55, B); // dead shrub - drawMultiItemBlockImage(img, getRect(333), tiles, 226, B); // netherwart small - drawMultiItemBlockImage(img, getRect(334), tiles, 227, B); // netherwart medium - drawMultiItemBlockImage(img, getRect(335), tiles, 228, B); // netherwart large - drawItemBlockImage(img, getRect(355), tiles, 85, B); // iron bars NSEW - drawPartialItemBlockImage(img, getRect(356), tiles, 85, 0, false, B, true, true, false, false); // iron bars NS - drawPartialItemBlockImage(img, getRect(357), tiles, 85, 0, false, B, true, false, true, false); // iron bars NE - drawPartialItemBlockImage(img, getRect(358), tiles, 85, 0, false, B, true, false, false, true); // iron bars NW - drawPartialItemBlockImage(img, getRect(359), tiles, 85, 0, false, B, false, true, true, false); // iron bars SE - drawPartialItemBlockImage(img, getRect(360), tiles, 85, 0, false, B, false, true, false, true); // iron bars SW - drawPartialItemBlockImage(img, getRect(361), tiles, 85, 0, false, B, false, false, true, true); // iron bars EW - drawPartialItemBlockImage(img, getRect(362), tiles, 85, 0, false, B, false, true, true, true); // iron bars SEW - drawPartialItemBlockImage(img, getRect(363), tiles, 85, 0, false, B, true, false, true, true); // iron bars NEW - drawPartialItemBlockImage(img, getRect(364), tiles, 85, 0, false, B, true, true, false, true); // iron bars NSW - drawPartialItemBlockImage(img, getRect(365), tiles, 85, 0, false, B, true, true, true, false); // iron bars NSE - drawPartialItemBlockImage(img, getRect(419), tiles, 85, 0, false, B, true, false, false, false); // iron bars N - drawPartialItemBlockImage(img, getRect(420), tiles, 85, 0, false, B, false, true, false, false); // iron bars S - drawPartialItemBlockImage(img, getRect(421), tiles, 85, 0, false, B, false, false, true, false); // iron bars E - drawPartialItemBlockImage(img, getRect(422), tiles, 85, 0, false, B, false, false, false, true); // iron bars W - drawItemBlockImage(img, getRect(366), tiles, 49, B); // glass pane NSEW - drawPartialItemBlockImage(img, getRect(367), tiles, 49, 0, false, B, true, true, false, false); // glass pane NS - drawPartialItemBlockImage(img, getRect(368), tiles, 49, 0, false, B, true, false, true, false); // glass pane NE - drawPartialItemBlockImage(img, getRect(369), tiles, 49, 0, false, B, true, false, false, true); // glass pane NW - drawPartialItemBlockImage(img, getRect(370), tiles, 49, 0, false, B, false, true, true, false); // glass pane SE - drawPartialItemBlockImage(img, getRect(371), tiles, 49, 0, false, B, false, true, false, true); // glass pane SW - drawPartialItemBlockImage(img, getRect(372), tiles, 49, 0, false, B, false, false, true, true); // glass pane EW - drawPartialItemBlockImage(img, getRect(373), tiles, 49, 0, false, B, false, true, true, true); // glass pane SEW - drawPartialItemBlockImage(img, getRect(374), tiles, 49, 0, false, B, true, false, true, true); // glass pane NEW - drawPartialItemBlockImage(img, getRect(375), tiles, 49, 0, false, B, true, true, false, true); // glass pane NSW - drawPartialItemBlockImage(img, getRect(376), tiles, 49, 0, false, B, true, true, true, false); // glass pane NSE - drawPartialItemBlockImage(img, getRect(423), tiles, 49, 0, false, B, true, false, false, false); // glass pane N - drawPartialItemBlockImage(img, getRect(424), tiles, 49, 0, false, B, false, true, false, false); // glass pane S - drawPartialItemBlockImage(img, getRect(425), tiles, 49, 0, false, B, false, false, true, false); // glass pane E - drawPartialItemBlockImage(img, getRect(426), tiles, 49, 0, false, B, false, false, false, true); // glass pane W - drawItemBlockImage(img, getRect(395), stemtiles, 0, B); // stem level 0 - drawItemBlockImage(img, getRect(396), stemtiles, 1, B); // stem level 1 - drawItemBlockImage(img, getRect(397), stemtiles, 2, B); // stem level 2 - drawItemBlockImage(img, getRect(398), stemtiles, 3, B); // stem level 3 - drawItemBlockImage(img, getRect(399), stemtiles, 4, B); // stem level 4 - drawItemBlockImage(img, getRect(400), stemtiles, 5, B); // stem level 5 - drawItemBlockImage(img, getRect(401), stemtiles, 6, B); // stem level 6 - drawItemBlockImage(img, getRect(402), stemtiles, 7, B); // stem level 7 - drawPartialItemBlockImage(img, getRect(403), stemtiles, 8, 0, false, B, true, true, false, false); // stem pointing N - drawPartialItemBlockImage(img, getRect(404), stemtiles, 9, 0, false, B, true, true, false, false); // stem pointing S - drawPartialItemBlockImage(img, getRect(405), stemtiles, 8, 0, false, B, false, false, true, true); // stem pointing E - drawPartialItemBlockImage(img, getRect(406), stemtiles, 9, 0, false, B, false, false, true, true); // stem pointing W - drawPartialItemBlockImage(img, getRect(519), tiles, 170, 0, true, B, true, false, false, false); // cocoa level 0 stem N - drawPartialItemBlockImage(img, getRect(520), tiles, 170, 0, false, B, false, true, false, false); // cocoa level 0 stem S - drawPartialItemBlockImage(img, getRect(521), tiles, 170, 0, true, B, false, false, true, false); // cocoa level 0 stem E - drawPartialItemBlockImage(img, getRect(522), tiles, 170, 0, false, B, false, false, false, true); // cocoa level 0 stem W - drawPartialItemBlockImage(img, getRect(523), tiles, 169, 0, true, B, true, false, false, false); // cocoa level 1 stem N - drawPartialItemBlockImage(img, getRect(524), tiles, 169, 0, false, B, false, true, false, false); // cocoa level 1 stem S - drawPartialItemBlockImage(img, getRect(525), tiles, 169, 0, true, B, false, false, true, false); // cocoa level 1 stem E - drawPartialItemBlockImage(img, getRect(526), tiles, 169, 0, false, B, false, false, false, true); // cocoa level 1 stem W - drawPartialItemBlockImage(img, getRect(527), tiles, 168, 0, true, B, true, false, false, false); // cocoa level 2 stem N - drawPartialItemBlockImage(img, getRect(528), tiles, 168, 0, false, B, false, true, false, false); // cocoa level 2 stem S - drawPartialItemBlockImage(img, getRect(529), tiles, 168, 0, true, B, false, false, true, false); // cocoa level 2 stem E - drawPartialItemBlockImage(img, getRect(530), tiles, 168, 0, false, B, false, false, false, true); // cocoa level 2 stem W - drawPartialItemBlockImage(img, getRect(543), tiles, 173, 2, false, B, true, true, true, true); // tripwire NSEW - drawPartialItemBlockImage(img, getRect(544), tiles, 173, 2, false, B, true, true, false, false); // tripwire NS - drawPartialItemBlockImage(img, getRect(545), tiles, 173, 2, false, B, true, false, true, false); // tripwire NE - drawPartialItemBlockImage(img, getRect(546), tiles, 173, 2, false, B, true, false, false, true); // tripwire NW - drawPartialItemBlockImage(img, getRect(547), tiles, 173, 2, false, B, false, true, true, false); // tripwire SE - drawPartialItemBlockImage(img, getRect(548), tiles, 173, 2, false, B, false, true, false, true); // tripwire SW - drawPartialItemBlockImage(img, getRect(549), tiles, 173, 2, false, B, false, false, true, true); // tripwire EW - drawPartialItemBlockImage(img, getRect(550), tiles, 173, 2, false, B, false, true, true, true); // tripwire SEW - drawPartialItemBlockImage(img, getRect(551), tiles, 173, 2, false, B, true, false, true, true); // tripwire NEW - drawPartialItemBlockImage(img, getRect(552), tiles, 173, 2, false, B, true, true, false, true); // tripwire NSW - drawPartialItemBlockImage(img, getRect(553), tiles, 173, 2, false, B, true, true, true, false); // tripwire NSE - - drawSingleFaceBlockImage(img, getRect(44), tiles, 80, 1, B); // torch pointing S - drawSingleFaceBlockImage(img, getRect(45), tiles, 80, 0, B); // torch pointing N - drawSingleFaceBlockImage(img, getRect(46), tiles, 80, 3, B); // torch pointing W - drawSingleFaceBlockImage(img, getRect(47), tiles, 80, 2, B); // torch pointing E - drawSingleFaceBlockImage(img, getRect(74), tiles, 97, 3, B); // wood door S side - drawSingleFaceBlockImage(img, getRect(75), tiles, 97, 2, B); // wood door N side - drawSingleFaceBlockImage(img, getRect(76), tiles, 97, 0, B); // wood door W side - drawSingleFaceBlockImage(img, getRect(77), tiles, 97, 1, B); // wood door E side - drawSingleFaceBlockImage(img, getRect(78), tiles, 81, 3, B); // wood door top S - drawSingleFaceBlockImage(img, getRect(79), tiles, 81, 2, B); // wood door top N - drawSingleFaceBlockImage(img, getRect(80), tiles, 81, 0, B); // wood door top W - drawSingleFaceBlockImage(img, getRect(81), tiles, 81, 1, B); // wood door top E - drawSingleFaceBlockImage(img, getRect(82), tiles, 83, 2, B); // ladder E side - drawSingleFaceBlockImage(img, getRect(83), tiles, 83, 3, B); // ladder W side - drawSingleFaceBlockImage(img, getRect(84), tiles, 83, 0, B); // ladder N side - drawSingleFaceBlockImage(img, getRect(85), tiles, 83, 1, B); // ladder S side - drawSingleFaceBlockImage(img, getRect(111), tiles, 98, 3, B); // iron door S side - drawSingleFaceBlockImage(img, getRect(112), tiles, 98, 2, B); // iron door N side - drawSingleFaceBlockImage(img, getRect(113), tiles, 98, 0, B); // iron door W side - drawSingleFaceBlockImage(img, getRect(114), tiles, 98, 1, B); // iron door E side - drawSingleFaceBlockImage(img, getRect(115), tiles, 82, 3, B); // iron door top S - drawSingleFaceBlockImage(img, getRect(116), tiles, 82, 2, B); // iron door top N - drawSingleFaceBlockImage(img, getRect(117), tiles, 82, 0, B); // iron door top W - drawSingleFaceBlockImage(img, getRect(118), tiles, 82, 1, B); // iron door top E - drawSingleFaceBlockImage(img, getRect(141), tiles, 99, 1, B); // red torch S on - drawSingleFaceBlockImage(img, getRect(142), tiles, 99, 0, B); // red torch N on - drawSingleFaceBlockImage(img, getRect(143), tiles, 99, 3, B); // red torch W on - drawSingleFaceBlockImage(img, getRect(144), tiles, 99, 2, B); // red torch E on - drawSingleFaceBlockImage(img, getRect(145), tiles, 115, 1, B); // red torch S off - drawSingleFaceBlockImage(img, getRect(146), tiles, 115, 0, B); // red torch N off - drawSingleFaceBlockImage(img, getRect(147), tiles, 115, 3, B); // red torch W off - drawSingleFaceBlockImage(img, getRect(148), tiles, 115, 2, B); // red torch E off - drawSingleFaceBlockImage(img, getRect(277), tiles, 84, 2, B); // trapdoor open W - drawSingleFaceBlockImage(img, getRect(278), tiles, 84, 3, B); // trapdoor open E - drawSingleFaceBlockImage(img, getRect(279), tiles, 84, 0, B); // trapdoor open S - drawSingleFaceBlockImage(img, getRect(280), tiles, 84, 1, B); // trapdoor open N - drawSingleFaceBlockImage(img, getRect(539), tiles, 172, 0, B); // tripwire hook S - drawSingleFaceBlockImage(img, getRect(540), tiles, 172, 1, B); // tripwire hook N - drawSingleFaceBlockImage(img, getRect(541), tiles, 172, 2, B); // tripwire hook W - drawSingleFaceBlockImage(img, getRect(542), tiles, 172, 3, B); // tripwire hook E - - drawPartialSingleFaceBlockImage(img, getRect(100), tiles, 4, 2, B, 0.25, 0.75, 0, 1); // wall sign facing E - drawPartialSingleFaceBlockImage(img, getRect(101), tiles, 4, 3, B, 0.25, 0.75, 0, 1); // wall sign facing W - drawPartialSingleFaceBlockImage(img, getRect(102), tiles, 4, 0, B, 0.25, 0.75, 0, 1); // wall sign facing N - drawPartialSingleFaceBlockImage(img, getRect(103), tiles, 4, 1, B, 0.25, 0.75, 0, 1); // wall sign facing S - drawPartialSingleFaceBlockImage(img, getRect(190), tiles, 1, 1, B, 0.35, 0.65, 0.35, 0.65); // stone button facing S - drawPartialSingleFaceBlockImage(img, getRect(191), tiles, 1, 0, B, 0.35, 0.65, 0.35, 0.65); // stone button facing N - drawPartialSingleFaceBlockImage(img, getRect(192), tiles, 1, 3, B, 0.35, 0.65, 0.35, 0.65); // stone button facing W - drawPartialSingleFaceBlockImage(img, getRect(193), tiles, 1, 2, B, 0.35, 0.65, 0.35, 0.65); // stone button facing E - - drawSolidColorBlockImage(img, getRect(139), 0xd07b2748, B); // portal - - drawStairsS(img, getRect(50), tiles, 4, 4, B); // wood stairs asc S - drawStairsN(img, getRect(51), tiles, 4, 4, B); // wood stairs asc N - drawStairsW(img, getRect(52), tiles, 4, 4, B); // wood stairs asc W - drawStairsE(img, getRect(53), tiles, 4, 4, B); // wood stairs asc E - drawStairsS(img, getRect(96), tiles, 16, 16, B); // cobble stairs asc S - drawStairsN(img, getRect(97), tiles, 16, 16, B); // cobble stairs asc N - drawStairsW(img, getRect(98), tiles, 16, 16, B); // cobble stairs asc W - drawStairsE(img, getRect(99), tiles, 16, 16, B); // cobble stairs asc E - drawStairsS(img, getRect(304), tiles, 7, 7, B); // brick stairs asc S - drawStairsN(img, getRect(305), tiles, 7, 7, B); // brick stairs asc N - drawStairsW(img, getRect(306), tiles, 7, 7, B); // brick stairs asc W - drawStairsE(img, getRect(307), tiles, 7, 7, B); // brick stairs asc E - drawStairsS(img, getRect(308), tiles, 54, 54, B); // stone brick stairs asc S - drawStairsN(img, getRect(309), tiles, 54, 54, B); // stone brick stairs asc N - drawStairsW(img, getRect(310), tiles, 54, 54, B); // stone brick stairs asc W - drawStairsE(img, getRect(311), tiles, 54, 54, B); // stone brick stairs asc E - drawStairsS(img, getRect(312), tiles, 224, 224, B); // nether brick stairs asc S - drawStairsN(img, getRect(313), tiles, 224, 224, B); // nether brick stairs asc N - drawStairsW(img, getRect(314), tiles, 224, 224, B); // nether brick stairs asc W - drawStairsE(img, getRect(315), tiles, 224, 224, B); // nether brick stairs asc E - drawStairsS(img, getRect(470), tiles, 192, 176, B); // sandstone stairs asc S - drawStairsN(img, getRect(471), tiles, 192, 176, B); // sandstone stairs asc N - drawStairsW(img, getRect(472), tiles, 192, 176, B); // sandstone stairs asc W - drawStairsE(img, getRect(473), tiles, 192, 176, B); // sandstone stairs asc E - drawStairsS(img, getRect(495), tiles, 198, 198, B); // pine stairs asc S - drawStairsN(img, getRect(496), tiles, 198, 198, B); // pine stairs asc N - drawStairsW(img, getRect(497), tiles, 198, 198, B); // pine stairs asc W - drawStairsE(img, getRect(498), tiles, 198, 198, B); // pine stairs asc E - drawStairsS(img, getRect(503), tiles, 214, 214, B); // birch stairs asc S - drawStairsN(img, getRect(504), tiles, 214, 214, B); // birch stairs asc N - drawStairsW(img, getRect(505), tiles, 214, 214, B); // birch stairs asc W - drawStairsE(img, getRect(506), tiles, 214, 214, B); // birch stairs asc E - drawStairsS(img, getRect(511), tiles, 199, 199, B); // jungle stairs asc S - drawStairsN(img, getRect(512), tiles, 199, 199, B); // jungle stairs asc N - drawStairsW(img, getRect(513), tiles, 199, 199, B); // jungle stairs asc W - drawStairsE(img, getRect(514), tiles, 199, 199, B); // jungle stairs asc E - drawInvStairsS(img, getRect(438), tiles, 4, 4, B); // wood stairs asc S inverted - drawInvStairsN(img, getRect(439), tiles, 4, 4, B); // wood stairs asc N inverted - drawInvStairsW(img, getRect(440), tiles, 4, 4, B); // wood stairs asc W inverted - drawInvStairsE(img, getRect(441), tiles, 4, 4, B); // wood stairs asc E inverted - drawInvStairsS(img, getRect(442), tiles, 16, 16, B); // cobble stairs asc S inverted - drawInvStairsN(img, getRect(443), tiles, 16, 16, B); // cobble stairs asc N inverted - drawInvStairsW(img, getRect(444), tiles, 16, 16, B); // cobble stairs asc W inverted - drawInvStairsE(img, getRect(445), tiles, 16, 16, B); // cobble stairs asc E inverted - drawInvStairsS(img, getRect(446), tiles, 7, 7, B); // brick stairs asc S inverted - drawInvStairsN(img, getRect(447), tiles, 7, 7, B); // brick stairs asc N inverted - drawInvStairsW(img, getRect(448), tiles, 7, 7, B); // brick stairs asc W inverted - drawInvStairsE(img, getRect(449), tiles, 7, 7, B); // brick stairs asc E inverted - drawInvStairsS(img, getRect(450), tiles, 54, 54, B); // stone brick stairs asc S inverted - drawInvStairsN(img, getRect(451), tiles, 54, 54, B); // stone brick stairs asc N inverted - drawInvStairsW(img, getRect(452), tiles, 54, 54, B); // stone brick stairs asc W inverted - drawInvStairsE(img, getRect(453), tiles, 54, 54, B); // stone brick stairs asc E inverted - drawInvStairsS(img, getRect(454), tiles, 224, 224, B); // nether brick stairs asc S inverted - drawInvStairsN(img, getRect(455), tiles, 224, 224, B); // nether brick stairs asc N inverted - drawInvStairsW(img, getRect(456), tiles, 224, 224, B); // nether brick stairs asc W inverted - drawInvStairsE(img, getRect(457), tiles, 224, 224, B); // nether brick stairs asc E inverted - drawInvStairsS(img, getRect(474), tiles, 192, 176, B); // sandstone stairs asc S inverted - drawInvStairsN(img, getRect(475), tiles, 192, 176, B); // sandstone stairs asc N inverted - drawInvStairsW(img, getRect(476), tiles, 192, 176, B); // sandstone stairs asc W inverted - drawInvStairsE(img, getRect(477), tiles, 192, 176, B); // sandstone stairs asc E inverted - drawInvStairsS(img, getRect(499), tiles, 198, 198, B); // pine stairs asc S inverted - drawInvStairsN(img, getRect(500), tiles, 198, 198, B); // pine stairs asc N inverted - drawInvStairsW(img, getRect(501), tiles, 198, 198, B); // pine stairs asc W inverted - drawInvStairsE(img, getRect(502), tiles, 198, 198, B); // pine stairs asc E inverted - drawInvStairsS(img, getRect(507), tiles, 214, 214, B); // birch stairs asc S inverted - drawInvStairsN(img, getRect(508), tiles, 214, 214, B); // birch stairs asc N inverted - drawInvStairsW(img, getRect(509), tiles, 214, 214, B); // birch stairs asc W inverted - drawInvStairsE(img, getRect(510), tiles, 214, 214, B); // birch stairs asc E inverted - drawInvStairsS(img, getRect(515), tiles, 199, 199, B); // jungle stairs asc S inverted - drawInvStairsN(img, getRect(516), tiles, 199, 199, B); // jungle stairs asc N inverted - drawInvStairsW(img, getRect(517), tiles, 199, 199, B); // jungle stairs asc W inverted - drawInvStairsE(img, getRect(518), tiles, 199, 199, B); // jungle stairs asc E inverted - - drawFloorBlockImage(img, getRect(55), tiles, 164, 0, B); // redstone wire NSEW - drawFloorBlockImage(img, getRect(86), tiles, 128, 1, B); // track EW - drawFloorBlockImage(img, getRect(87), tiles, 128, 0, B); // track NS - drawFloorBlockImage(img, getRect(92), tiles, 112, 1, B); // track NE corner - drawFloorBlockImage(img, getRect(93), tiles, 112, 0, B); // track SE corner - drawFloorBlockImage(img, getRect(94), tiles, 112, 3, B); // track SW corner - drawFloorBlockImage(img, getRect(95), tiles, 112, 2, B); // track NW corner - drawFloorBlockImage(img, getRect(252), tiles, 179, 1, B); // booster on EW - drawFloorBlockImage(img, getRect(253), tiles, 179, 0, B); // booster on NS - drawFloorBlockImage(img, getRect(258), tiles, 163, 1, B); // booster off EW - drawFloorBlockImage(img, getRect(259), tiles, 163, 0, B); // booster off NS - drawFloorBlockImage(img, getRect(264), tiles, 195, 1, B); // detector EW - drawFloorBlockImage(img, getRect(265), tiles, 195, 0, B); // detector NS - drawFloorBlockImage(img, getRect(276), tiles, 84, 0, B); // trapdoor closed - drawFloorBlockImage(img, getRect(316), tiles, 76, 0, B); // lily pad - - drawAngledFloorBlockImage(img, getRect(200), tiles, 128, 0, 0, B); // track asc S - drawAngledFloorBlockImage(img, getRect(201), tiles, 128, 0, 2, B); // track asc N - drawAngledFloorBlockImage(img, getRect(202), tiles, 128, 1, 3, B); // track asc E - drawAngledFloorBlockImage(img, getRect(203), tiles, 128, 1, 1, B); // track asc W - drawAngledFloorBlockImage(img, getRect(254), tiles, 179, 0, 0, B); // booster on asc S - drawAngledFloorBlockImage(img, getRect(255), tiles, 179, 0, 2, B); // booster on asc N - drawAngledFloorBlockImage(img, getRect(256), tiles, 179, 1, 3, B); // booster on asc E - drawAngledFloorBlockImage(img, getRect(257), tiles, 179, 1, 1, B); // booster on asc W - drawAngledFloorBlockImage(img, getRect(260), tiles, 163, 0, 0, B); // booster off asc S - drawAngledFloorBlockImage(img, getRect(261), tiles, 163, 0, 2, B); // booster off asc N - drawAngledFloorBlockImage(img, getRect(262), tiles, 163, 1, 3, B); // booster off asc E - drawAngledFloorBlockImage(img, getRect(263), tiles, 163, 1, 1, B); // booster off asc W - drawAngledFloorBlockImage(img, getRect(266), tiles, 195, 0, 0, B); // detector asc S - drawAngledFloorBlockImage(img, getRect(267), tiles, 195, 0, 2, B); // detector asc N - drawAngledFloorBlockImage(img, getRect(268), tiles, 195, 1, 3, B); // detector asc E - drawAngledFloorBlockImage(img, getRect(269), tiles, 195, 1, 1, B); // detector asc W - - drawFencePost(img, getRect(134), tiles, 4, B); // fence post - drawFence(img, getRect(158), tiles, 4, true, false, false, false, true, B); // fence N - drawFence(img, getRect(159), tiles, 4, false, true, false, false, true, B); // fence S - drawFence(img, getRect(160), tiles, 4, true, true, false, false, true, B); // fence NS - drawFence(img, getRect(161), tiles, 4, false, false, true, false, true, B); // fence E - drawFence(img, getRect(162), tiles, 4, true, false, true, false, true, B); // fence NE - drawFence(img, getRect(163), tiles, 4, false, true, true, false, true, B); // fence SE - drawFence(img, getRect(164), tiles, 4, true, true, true, false, true, B); // fence NSE - drawFence(img, getRect(165), tiles, 4, false, false, false, true, true, B); // fence W - drawFence(img, getRect(166), tiles, 4, true, false, false, true, true, B); // fence NW - drawFence(img, getRect(167), tiles, 4, false, true, false, true, true, B); // fence SW - drawFence(img, getRect(168), tiles, 4, true, true, false, true, true, B); // fence NSW - drawFence(img, getRect(169), tiles, 4, false, false, true, true, true, B); // fence EW - drawFence(img, getRect(170), tiles, 4, true, false, true, true, true, B); // fence NEW - drawFence(img, getRect(171), tiles, 4, false, true, true, true, true, B); // fence SEW - drawFence(img, getRect(172), tiles, 4, true, true, true, true, true, B); // fence NSEW - drawFencePost(img, getRect(332), tiles, 224, B); // nether fence post - drawFence(img, getRect(317), tiles, 224, true, false, false, false, true, B); // nether fence N - drawFence(img, getRect(318), tiles, 224, false, true, false, false, true, B); // nether fence S - drawFence(img, getRect(319), tiles, 224, true, true, false, false, true, B); // nether fence NS - drawFence(img, getRect(320), tiles, 224, false, false, true, false, true, B); // nether fence E - drawFence(img, getRect(321), tiles, 224, true, false, true, false, true, B); // nether fence NE - drawFence(img, getRect(322), tiles, 224, false, true, true, false, true, B); // nether fence SE - drawFence(img, getRect(323), tiles, 224, true, true, true, false, true, B); // nether fence NSE - drawFence(img, getRect(324), tiles, 224, false, false, false, true, true, B); // nether fence W - drawFence(img, getRect(325), tiles, 224, true, false, false, true, true, B); // nether fence NW - drawFence(img, getRect(326), tiles, 224, false, true, false, true, true, B); // nether fence SW - drawFence(img, getRect(327), tiles, 224, true, true, false, true, true, B); // nether fence NSW - drawFence(img, getRect(328), tiles, 224, false, false, true, true, true, B); // nether fence EW - drawFence(img, getRect(329), tiles, 224, true, false, true, true, true, B); // nether fence NEW - drawFence(img, getRect(330), tiles, 224, false, true, true, true, true, B); // nether fence SEW - drawFence(img, getRect(331), tiles, 224, true, true, true, true, true, B); // nether fence NSEW - drawFence(img, getRect(346), tiles, 4, false, false, true, true, false, B); // fence gate EW - drawFence(img, getRect(347), tiles, 4, true, true, false, false, false, B); // fence gate NS - - drawSign(img, getRect(70), tiles, 4, B); // sign facing N/S - drawSign(img, getRect(71), tiles, 4, B); // sign facing NE/SW - drawSign(img, getRect(72), tiles, 4, B); // sign facing E/W - drawSign(img, getRect(73), tiles, 4, B); // sign facing SE/NW - - drawWallLever(img, getRect(194), tiles, 1, B); // wall lever facing S - drawWallLever(img, getRect(195), tiles, 0, B); // wall lever facing N - drawWallLever(img, getRect(196), tiles, 3, B); // wall lever facing W - drawWallLever(img, getRect(197), tiles, 2, B); // wall lever facing E - drawFloorLeverEW(img, getRect(198), tiles, B); // ground lever EW - drawFloorLeverNS(img, getRect(199), tiles, B); // ground lever NS - - drawRepeater(img, getRect(240), tiles, 147, 0, B); // repeater on N - drawRepeater(img, getRect(241), tiles, 147, 2, B); // repeater on S - drawRepeater(img, getRect(242), tiles, 147, 3, B); // repeater on E - drawRepeater(img, getRect(243), tiles, 147, 1, B); // repeater on W - drawRepeater(img, getRect(244), tiles, 131, 0, B); // repeater on N - drawRepeater(img, getRect(245), tiles, 131, 2, B); // repeater on S - drawRepeater(img, getRect(246), tiles, 131, 3, B); // repeater on E - drawRepeater(img, getRect(247), tiles, 131, 1, B); // repeater on W - - drawFire(img, getRect(189), firetile, B); // fire - - drawBrewingStand(img, getRect(350), tiles, 156, 157, B); // brewing stand - - drawCauldron(img, getRect(351), tiles, 154, -1, 0, B); // cauldron empty - drawCauldron(img, getRect(352), tiles, 154, 205, CUTOFF_10_16, B); // cauldron 1/3 full - drawCauldron(img, getRect(353), tiles, 154, 205, CUTOFF_6_16, B); // cauldron 2/3 full - drawCauldron(img, getRect(354), tiles, 154, 205, CUTOFF_2_16, B); // cauldron full - - drawDragonEgg(img, getRect(378), tiles, 167, B); // dragon egg - - drawVines(img, getRect(379), tiles, 143, B, false, false, false, false, true); // vines top only - drawVines(img, getRect(380), tiles, 143, B, true, false, false, false, false); // vines N - drawVines(img, getRect(381), tiles, 143, B, false, true, false, false, false); // vines S - drawVines(img, getRect(382), tiles, 143, B, true, true, false, false, false); // vines NS - drawVines(img, getRect(383), tiles, 143, B, false, false, true, false, false); // vines E - drawVines(img, getRect(384), tiles, 143, B, true, false, true, false, false); // vines NE - drawVines(img, getRect(385), tiles, 143, B, false, true, true, false, false); // vines SE - drawVines(img, getRect(386), tiles, 143, B, true, true, true, false, false); // vines NSE - drawVines(img, getRect(387), tiles, 143, B, false, false, false, true, false); // vines W - drawVines(img, getRect(388), tiles, 143, B, true, false, false, true, false); // vines NW - drawVines(img, getRect(389), tiles, 143, B, false, true, false, true, false); // vines SW - drawVines(img, getRect(390), tiles, 143, B, true, true, false, true, false); // vines NSW - drawVines(img, getRect(391), tiles, 143, B, false, false, true, true, false); // vines EW - drawVines(img, getRect(392), tiles, 143, B, true, false, true, true, false); // vines NEW - drawVines(img, getRect(393), tiles, 143, B, false, true, true, true, false); // vines SEW - drawVines(img, getRect(394), tiles, 143, B, true, true, true, true, false); // vines NSEW - - return true; -} - +// Copyright 2010-2012 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#include +#include +#include +#include +#include + +#include "blockimages.h" +#include "utils.h" + +using namespace std; + + +// in this file, confusingly, "tile" refers to the texture/image tile, not to the map tiles +// also, this is a nasty mess in here; apologies to anyone reading this + + +void writeBlockImagesVersion(int B, const string& imgpath, int32_t version) +{ + string versionfile = imgpath + "/blocks-" + tostring(B) + ".version"; + ofstream outfile(versionfile.c_str()); + outfile << version; +} + +// get the version number associated with blocks-B.png; this is stored +// in blocks-B.version, which is just a single string with the version number +int getBlockImagesVersion(int B, const string& imgpath) +{ + string versionfile = imgpath + "/blocks-" + tostring(B) + ".version"; + ifstream infile(versionfile.c_str()); + // if there's no version file, assume the version is 0, so new version file + // will be generated according to the descriptor file version + if (infile.fail()) + { + infile.close(); + writeBlockImagesVersion(B, imgpath, 0); + return 0; + } + // otherwise, read the version + int32_t v; + infile >> v; + // if the version is clearly insane, ignore it + if (v < 0 || v > 10000) + v = 0; + return v; +} + +bool BlockImages::create(int B, const string& imgpath) +{ + rectsize = 4*B; + + // 1.5 + // Block mapping is specified in a separate list, pointing to textures listed in another file + string blocktexturesfile = imgpath + "/blocktextures.list"; + string blockdescriptorfile = imgpath + "/blockdescriptor.list"; + + ifstream texturelist(blocktexturesfile.c_str()); + ifstream descriptorlist(blockdescriptorfile.c_str()); + if(descriptorlist.fail()) + { + descriptorlist.close(); + cerr << blockdescriptorfile << " is missing" << endl; + return false; + } + setBlockDescriptors(descriptorlist); + blockversion = setOffsets(); + + // first, see if blocks-B.png exists, and what its version is + int biversion = getBlockImagesVersion(B, imgpath); + string blocksfile = imgpath + "/blocks-" + tostring(B) + ".png"; + if (img.readPNG(blocksfile)) + { + // if it's the correct size and version, we're okay + int w = rectsize*16, h = (blockversion/16 + 1) * rectsize; + if (img.w == w && img.h == h && biversion == blockversion) + { + retouchAlphas(B); + checkOpacityAndTransparency(B); + return true; + } + // if it's a previous version, we will need to build a new one, in case descriptor order + // has been changed + if (biversion < blockversion && img.w == w && img.h == (biversion/16 + 1) * rectsize) + { + cerr << blocksfile << " is of and older version (" << biversion << ")" << endl; + cerr << "...new block file will be built (" << blockversion << ")" << endl; + } + // otherwise, the file's been trashed somehow; rebuild it + else + { + cerr << blocksfile << " has incorrect size (expected " << w << "x" << h << ")" << endl; + cerr << "...will try to create new block file" << blocksfile << endl; + } + } + else + cerr << blocksfile << " not found (or failed to read as PNG); will try to build from provided textures" << endl; + + // build blocks-B.png from list of textures in texture list file + if(texturelist.fail()) + { + texturelist.close(); + cerr << blocktexturesfile << " is missing" << endl; + return false; + } + else if (!construct(B, texturelist, descriptorlist, imgpath)) + { + cerr << "image path is missing at least one of the required files" << endl; + cerr << "from minecraft.jar or your tile pack." << endl; + cerr << "endportal.png -- included with pigmap" << endl; + return false; + } + + // write blocks-B.png and blocks-B.version + img.writePNG(blocksfile); + writeBlockImagesVersion(B, imgpath, blockversion); + + retouchAlphas(B); + checkOpacityAndTransparency(B); + return true; +} + + + +// take the various textures from chest.png and use them to construct "flat" 14x14 tiles (or whatever +// the multiplied size is, if the textures are larger), then resize those flat images to 2Bx2B +// ...the resulting image will be a 3x1 array of 2Bx2B images: first the top, then the front, then +// the side +int generateChestTiles(unordered_map& blockTextures, const RGBAImage& texture, const string& name, int B) +{ + int scale = texture.w / 64; + + int chestSize = 14 * scale; + RGBAImage chesttiles; + chesttiles.create(chestSize*3, chestSize); + + // top texture just gets copied straight over + blit(texture, ImageRect(14*scale, 0, 14*scale, 14*scale), chesttiles, 0, 0); + + // front tile gets the front lid texture plus the front bottom texture, then the latch on + // top of that + blit(texture, ImageRect(14*scale, 14*scale, 14*scale, 4*scale), chesttiles, chestSize, 0); + blit(texture, ImageRect(14*scale, 33*scale, 14*scale, 10*scale), chesttiles, chestSize, 4*scale); + blit(texture, ImageRect(scale, scale, 2*scale, 4*scale), chesttiles, chestSize + 6*scale, 2*scale); + + // side tile gets the side lid texture plus the side bottom texture + blit(texture, ImageRect(28*scale, 14*scale, 14*scale, 4*scale), chesttiles, chestSize*2, 0); + blit(texture, ImageRect(28*scale, 33*scale, 14*scale, 10*scale), chesttiles, chestSize*2, 4*scale); + + int tilesize = 2*B; + for (int x = 0; x < 3; x++) + { + RGBAImage img; + img.create(tilesize, tilesize); + resize(chesttiles, ImageRect(x*chestSize, 0, chestSize, chestSize), + img, ImageRect(0, 0, tilesize, tilesize)); + blockTextures["/" + name + "_" + tostring(x)] = img; + } + return 3; +} + +// same thing for largechest.png--construct flat tiles, then resize +// ...resulting image is a 7x1 array of 2Bx2B images: +// -left half of top +// -right half of top +// -left half of front +// -right half of front +// -left half of back +// -right half of back +// -side +int generateLargeChestTiles(unordered_map& blockTextures, const RGBAImage& texture, const string& name, int B) +{ + int scale = texture.w / 128; + + int tilesize = 2*B; + RGBAImage chesttiles; + chesttiles.create(7*tilesize, tilesize); + + // top texture gets copied straight over--note that the original texture is 30x14, but + // we're putting it into two squares + resize(texture, ImageRect(14*scale, 0, 30*scale, 14*scale), chesttiles, ImageRect(0, 0, tilesize*2, tilesize)); + // front tile gets the front lid texture plus the front bottom texture, then the latch + // on top of that + RGBAImage fronttiles; + fronttiles.create(30*scale, 14*scale); + blit(texture, ImageRect(14*scale, 14*scale, 30*scale, 4*scale), fronttiles, 0, 0); + blit(texture, ImageRect(14*scale, 33*scale, 30*scale, 10*scale), fronttiles, 0, 4*scale); + blit(texture, ImageRect(scale, scale, 2*scale, 4*scale), fronttiles, 14*scale, 2*scale); + // do two resizes, to make sure the special end processing picks up the latch + resize(fronttiles, ImageRect(0, 0, 15*scale, 14*scale), chesttiles, ImageRect(2*tilesize, 0, tilesize, tilesize)); + resize(fronttiles, ImageRect(15*scale, 0, 15*scale, 14*scale), chesttiles, ImageRect(3*tilesize, 0, tilesize, tilesize)); + + // back tile gets the back lid texture plus the back bottom texture + RGBAImage backtiles; + backtiles.create(30*scale, 14*scale); + blit(texture, ImageRect(58*scale, 14*scale, 30*scale, 4*scale), backtiles, 0, 0); + blit(texture, ImageRect(58*scale, 33*scale, 30*scale, 10*scale), backtiles, 0, 4*scale); + resize(backtiles, ImageRect(0, 0, 30*scale, 14*scale), chesttiles, ImageRect(4*tilesize, 0, 2*tilesize, tilesize)); + + // side tile gets the side lid texture plus the side bottom texture + RGBAImage sidetile; + sidetile.create(14*scale, 14*scale); + blit(texture, ImageRect(44*scale, 14*scale, 14*scale, 4*scale), sidetile, 0, 0); + blit(texture, ImageRect(44*scale, 33*scale, 14*scale, 10*scale), sidetile, 0, 4*scale); + resize(sidetile, ImageRect(0, 0, 14*scale, 14*scale), chesttiles, ImageRect(6*tilesize, 0, tilesize, tilesize)); + + for (int x = 0; x < 7; x++) + { + RGBAImage img; + img.create(tilesize, tilesize); + blit(chesttiles, ImageRect(x*tilesize, 0, tilesize, tilesize), img, 0, 0); + blockTextures["/" + name + "_" + tostring(x)] = img; + } + + return 3; +} + + + +// iterate over the pixels of a 2B-sized texture tile; used for both source rectangles and +// destination parallelograms +struct FaceIterator +{ + bool end; // true if we're done + int x, y; // current pixel + int pos; + + int size; // number of columns to draw, as well as number of pixels in each + int deltaY; // amount to skew y-coord every 2 columns: -1 or 1 for N/S or W/E facing destinations, 0 for source + + FaceIterator(int xstart, int ystart, int dY, int sz) + { + size = sz; + deltaY = dY; + end = false; + x = xstart; + y = ystart; + pos = 0; + } + + void advance() + { + pos++; + if (pos >= size*size) + { + end = true; + return; + } + y++; + if (pos % size == 0) + { + x++; + y -= size; + if (pos % (2*size) == size) + y += deltaY; + } + } +}; + +// like FaceIterator with no deltaY (for source rectangles), but with the source rotated and/or flipped +struct RotatedFaceIterator +{ + bool end; + int x, y; + int pos; + + int size; + int rot; // 0 = down, then right; 1 = left, then down; 2 = up, then left; 3 = right, then up + bool flipX; + int dx1, dy1, dx2, dy2; + + RotatedFaceIterator(int xstart, int ystart, int r, int sz, bool fX) + { + size = sz; + rot = r; + flipX = fX; + end = false; + pos = 0; + if (rot == 0) + { + x = flipX ? (xstart + size - 1) : xstart; + y = ystart; + dx1 = 0; + dy1 = 1; + dx2 = flipX ? -1 : 1; + dy2 = 0; + } + else if (rot == 1) + { + x = flipX ? xstart : (xstart + size - 1); + y = ystart; + dx1 = flipX ? 1 : -1; + dy1 = 0; + dx2 = 0; + dy2 = 1; + } + else if (rot == 2) + { + x = flipX ? xstart : (xstart + size - 1); + y = ystart + size - 1; + dx1 = 0; + dy1 = -1; + dx2 = flipX ? 1 : -1; + dy2 = 0; + } + else + { + x = flipX ? (xstart + size - 1) : xstart; + y = ystart + size - 1; + dx1 = flipX ? -1 : 1; + dy1 = 0; + dx2 = 0; + dy2 = -1; + } + } + + void advance() + { + pos++; + if (pos >= size*size) + { + end = true; + return; + } + x += dx1; + y += dy1; + if (pos % size == 0) + { + x += dx2; + y += dy2; + x -= dx1 * size; + y -= dy1 * size; + } + } +}; + +// iterate over the pixels of the top face of a block +struct TopFaceIterator +{ + bool end; // true if we're done + int x, y; // current pixel + int pos; + + int size; // number of "columns", and number of pixels in each + + TopFaceIterator(int xstart, int ystart, int sz) + { + size = sz; + end = false; + x = xstart; + y = ystart; + pos = 0; + } + + void advance() + { + if ((pos/size) % 2 == 0) + { + int m = pos % size; + if (m == size - 1) + { + x += size - 1; + y -= size/2; + } + else if (m == size - 2) + y++; + else if (m % 2 == 0) + { + x--; + y++; + } + else + x--; + } + else + { + int m = pos % size; + if (m == 0) + y++; + else if (m == size - 1) + { + x += size - 1; + y -= size/2 - 1; + } + else if (m % 2 == 0) + { + x--; + y++; + } + else + x--; + } + pos++; + if (pos >= size*size) + end = true; + } +}; + + +struct SourceTile +{ + const RGBAImage *image; // or NULL for no tile + int xpos, ypos; // tile offset within the image + int rot; + bool flipX; + + SourceTile(const RGBAImage *img, int r, bool f) : image(img), xpos(0), ypos(0), rot(r), flipX(f) {} + SourceTile() : image(NULL), xpos(0), ypos(0), rot(0), flipX(false) {} + bool valid() const {return image != NULL;} +}; + +// iterate over a square source tile, with possible rotation and flip +struct SourceIterator +{ + SourceIterator(const SourceTile& tile, int tilesize) + : image(*(tile.image)), faceit(tile.xpos*tilesize, tile.ypos*tilesize, tile.rot, tilesize, tile.flipX) {} + + void advance() {faceit.advance();} + bool end() {return faceit.end;} + RGBAPixel pixel() {return image(faceit.x, faceit.y);} + + const RGBAImage& image; + RotatedFaceIterator faceit; +}; + +// construct a source iterator for a given texture tile with rotation and/or flip +SourceTile blockTile(const RGBAImage& tile, int rot, bool flipX) +{ + return SourceTile(&tile, rot, flipX); +} +SourceTile blockTile(const RGBAImage& tile) +{ + return blockTile(tile, 0, false); +} + + +int deinterpolate(int targetj, int srcrange, int destrange) +{ + for (int i = 0; i < destrange; i++) + { + int j = interpolate(i, destrange, srcrange); + if (j >= targetj) + return i; + } + return destrange - 1; +} + +// draw a normal block image, using three texture tiles (which may be flipped/rotated/missing), and adding a bit of shadow +// to the N and W faces +void drawRotatedBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& Wface, const SourceTile& Sface, const SourceTile& Uface, int B) // re-oriented +{ + int tilesize = 2*B; + // N face starts at [0,B] + if (Wface.valid()) + { + FaceIterator dstit(drect.x, drect.y + B, 1, tilesize); + for (SourceIterator srcit(Wface, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + dest(dstit.x, dstit.y) = srcit.pixel(); + darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); + } + } + // W face starts at [2B,2B] + if (Sface.valid()) + { + FaceIterator dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); + for (SourceIterator srcit(Sface, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + dest(dstit.x, dstit.y) = srcit.pixel(); + darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); + } + } + // U face starts at [2B-1,0] + if (Uface.valid()) + { + TopFaceIterator dstit(drect.x + 2*B-1, drect.y, tilesize); + for (SourceIterator srcit(Uface, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + dest(dstit.x, dstit.y) = srcit.pixel(); + } + } +} + +// overload of drawRotatedBlockImage taking three .png tiles +void drawBlockImage(RGBAImage& dest, const ImageRect& drect, RGBAImage& Wface, RGBAImage& Sface, RGBAImage& Uface, int B) // re-oriented +{ + drawRotatedBlockImage(dest, drect, blockTile(Wface), blockTile(Sface), blockTile(Uface), B); +} + +// draw a block image where the block isn't full height (half-steps, snow, etc.) +// topcutoff is the number of pixels (out of 2B) to chop off the top of the N and W faces +// bottomcutoff is the number of pixels (out of 2B) to chop off the bottom +// if shift is true, we start copying pixels from the very top of the source tile, even if there's a topcutoff +// U face can also be rotated, and N/W faces can be X-flipped (set 0x1 for N, 0x2 for W) +void drawPartialBlockImage(RGBAImage& dest, const ImageRect& drect, RGBAImage& Wface, RGBAImage& Sface, RGBAImage& Uface, int B, bool W, bool S, bool U, int topcutoff, int bottomcutoff, int rot, int flip, bool shift) // re-oriented +{ + int tilesize = 2*B; + if (topcutoff + bottomcutoff >= tilesize) + return; + int end = tilesize - bottomcutoff; + // W face starts at [0,B] + if (W) + { + FaceIterator dstit(drect.x, drect.y + B, 1, tilesize); + for (RotatedFaceIterator srcit(0, 0, 0, tilesize, flip & 0x1); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize >= topcutoff && dstit.pos % tilesize < end) + { + dest(dstit.x, dstit.y) = Wface(srcit.x, srcit.y - (shift ? topcutoff : 0)); + darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); + } + } + } + // S face starts at [2B,2B] + if (S) + { + FaceIterator dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); + for (RotatedFaceIterator srcit(0, 0, 0, tilesize, flip & 0x2); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize >= topcutoff && dstit.pos % tilesize < end) + { + dest(dstit.x, dstit.y) = Sface(srcit.x, srcit.y - (shift ? topcutoff : 0)); + darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); + } + } + } + // U face starts at [2B-1,topcutoff] + if (U) + { + TopFaceIterator dstit(drect.x + 2*B-1, drect.y + topcutoff, tilesize); + for (RotatedFaceIterator srcit(0, 0, rot, tilesize, false); !srcit.end; srcit.advance(), dstit.advance()) + { + dest(dstit.x, dstit.y) = Uface(srcit.x, srcit.y); + } + } +} +// override drawPartialBlockImage without arguments for optional face drawing (draw all faces by default) +void drawPartialBlockImage(RGBAImage& dest, const ImageRect& drect, RGBAImage& Wface, RGBAImage& Sface, RGBAImage& Uface, int B, int topcutoff, int bottomcutoff, int rot, int flip, bool shift) +{ + drawPartialBlockImage(dest, drect, Wface, Sface, Uface, B, true, true, true, topcutoff, bottomcutoff, rot, flip, shift); +} + +// draw two flat copies of a tile intersecting at the block center (saplings, etc.) +void drawItemBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, bool N, bool S, bool W, bool E, int B) // re-oriented +{ + if (!tile.valid()) + return; + int tilesize = 2*B; + int cutoff = tilesize/2; + // S face starting at [B,1.5B] -- eastern half only + if (E) + { + FaceIterator dstit(drect.x + B, drect.y + B*3/2, -1, tilesize); + for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + if (dstit.pos / tilesize >= cutoff) + blend(dest(dstit.x, dstit.y), srcit.pixel()); + } + } + // W face starting at [B,0.5B] + if (N || S) + { + FaceIterator dstit(drect.x + B, drect.y + B/2, 1, tilesize); + for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + if ((S && dstit.pos / tilesize >= cutoff) || (N && dstit.pos / tilesize < cutoff)) + blend(dest(dstit.x, dstit.y), srcit.pixel()); + } + } + // S face starting at [B,1.5B] -- western half only + if (W) + { + FaceIterator dstit(drect.x + B, drect.y + B*3/2, -1, tilesize); + for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + if (dstit.pos / tilesize < cutoff) + blend(dest(dstit.x, dstit.y), srcit.pixel()); + } + } +} + +void drawItemBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int B) +{ + drawItemBlockImage(dest, drect, blockTile(tile), true, true, true, true, B); +} + + +// draw an item block image possibly missing some edges (iron bars, etc.) +void drawPartialItemBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int rot, bool flipX, bool N, bool S, bool W, bool E, int B) +{ + drawItemBlockImage(dest, drect, blockTile(tile, rot, flipX), N, S, W, E, B); +} + +// draw four flat copies of a tile intersecting in a square (netherwart, etc.) +void drawMultiItemBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, int B) +{ + if (!tile.valid()) + return; + int tilesize = 2*B; + // E/W face starting at [0.5B,1.25B] + { + FaceIterator dstit(drect.x + B/2, drect.y + B*5/4, -1, tilesize); + for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + blend(dest(dstit.x, dstit.y), srcit.pixel()); + } + } + // E/W face starting at [1.5B,1.75B] + { + FaceIterator dstit(drect.x + 3*B/2, drect.y + B*7/4, -1, tilesize); + for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + blend(dest(dstit.x, dstit.y), srcit.pixel()); + } + } + // N/S face starting at [0.5B,0.75B] + { + FaceIterator dstit(drect.x + B/2, drect.y + B*3/4, 1, tilesize); + for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + blend(dest(dstit.x, dstit.y), srcit.pixel()); + } + } + // N/S face starting at [1.5B,0.25B] + { + FaceIterator dstit(drect.x + 3*B/2, drect.y + B/4, 1, tilesize); + for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + blend(dest(dstit.x, dstit.y), srcit.pixel()); + } + } +} + +void drawMultiItemBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int B) +{ + drawMultiItemBlockImage(dest, drect, blockTile(tile), B); +} + +// draw a tile on a single upright face +// 0 = E, 1 = W, 2 = S, 3 = N +// ...handles transparency +void drawSingleFaceBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, int face, int B) // re-oriented maybe? +{ + if (!tile.valid()) + return; + int tilesize = 2*B; + int xoff, yoff, deltaY; + if (face == 0) + { + xoff = 2*B; + yoff = 0; + deltaY = 1; + } + else if (face == 1) + { + xoff = 0; + yoff = B; + deltaY = 1; + } + else if (face == 2) + { + xoff = 2*B; + yoff = 2*B; + deltaY = -1; + } + else + { + xoff = 0; + yoff = B; + deltaY = -1; + } + FaceIterator dstit(drect.x + xoff, drect.y + yoff, deltaY, tilesize); + for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + blend(dest(dstit.x, dstit.y), srcit.pixel()); + } +} + +void drawSingleFaceBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int face, int B) +{ + drawSingleFaceBlockImage(dest, drect, blockTile(tile), face, B); +} + +// draw part of a tile on a single upright face +// 0 = S, 1 = N, 2 = W, 3 = E +// ...handles transparency +void drawPartialSingleFaceBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, int face, int B, double croptop, double cropbottom, double cropleft, double cropright) +{ + int tilesize = 2*B; + int xoff, yoff, deltaY; + if (face == 0) + { + xoff = 2*B; + yoff = 0; + deltaY = 1; + } + else if (face == 1) + { + xoff = 0; + yoff = B; + deltaY = 1; + } + else if (face == 2) + { + xoff = 2*B; + yoff = 2*B; + deltaY = -1; + } + else + { + xoff = 0; + yoff = B; + deltaY = -1; + } + FaceIterator dstit(drect.x + xoff, drect.y + yoff, deltaY, tilesize); + for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize >= croptop && dstit.pos % tilesize < tilesize - cropbottom && + dstit.pos / tilesize >= cropleft && dstit.pos / tilesize < tilesize - cropright) + blend(dest(dstit.x, dstit.y), srcit.pixel()); + } +} + +void drawPartialSingleFaceBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int face, int B, int croptop, int cropbottom, int cropleft, int cropright) +{ + drawPartialSingleFaceBlockImage(dest, drect, blockTile(tile), face, B, croptop, cropbottom, cropleft, cropright); +} + +// draw a single tile on the floor, possibly with rotation +// 0 = top of tile is on E side; 1 = N, 2 = E, 3 = S +void drawFloorBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, int B) +{ + int tilesize = 2*B; + TopFaceIterator dstit(drect.x + 2*B-1, drect.y + 2*B, tilesize); + for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + dest(dstit.x, dstit.y) = srcit.pixel(); + } +} + +void drawFloorBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int rot, int B) +{ + drawFloorBlockImage(dest, drect, blockTile(tile, rot, false), B); +} + +// draw a single tile on the floor, possibly with rotation, angled upwards +// rot: 0 = top of tile is on S side; 1 = W, 2 = N, 3 = E +// up: 0 = S side of tile is highest; 1 = W, 2 = N, 3 = E +void drawAngledFloorBlockImage(RGBAImage& dest, const ImageRect& drect, const SourceTile& tile, int up, int B) +{ + int tilesize = 2*B; + TopFaceIterator dstit(drect.x + 2*B-1, drect.y + 2*B, tilesize); + for (SourceIterator srcit(tile, tilesize); !srcit.end(); srcit.advance(), dstit.advance()) + { + int yoff = 0; + int row = dstit.pos % tilesize, col = dstit.pos / tilesize; + if (up == 0) + yoff = tilesize - 1 - row; + else if (up == 1) + yoff = col; + else if (up == 2) + yoff = row; + else if (up == 3) + yoff = tilesize - 1 - col; + blend(dest(dstit.x, dstit.y - yoff), srcit.pixel()); + blend(dest(dstit.x, dstit.y - yoff + 1), srcit.pixel()); + } +} + +void drawAngledFloorBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int rot, int up, int B) +{ + drawAngledFloorBlockImage(dest, drect, blockTile(tile, rot, false), up, B); +} + +// draw a single tile on the ceiling, possibly with rotation +// 0 = top of tile is on S side; 1 = W, 2 = N, 3 = E +void drawCeilBlockImage(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int rot, int B) +{ + int tilesize = 2*B; + TopFaceIterator dstit(drect.x + 2*B-1, drect.y, tilesize); + for (RotatedFaceIterator srcit(0, 0, rot, tilesize, false); !srcit.end; srcit.advance(), dstit.advance()) + { + dest(dstit.x, dstit.y) = tile(srcit.x, srcit.y); + } +} + +// draw a block image that's just a single color (plus shadows) +void drawSolidColorBlockImage(RGBAImage& dest, const ImageRect& drect, RGBAPixel p, int B) +{ + int tilesize = 2*B; + // N face starts at [0,B] + for (FaceIterator dstit(drect.x, drect.y + B, 1, tilesize); !dstit.end; dstit.advance()) + { + dest(dstit.x, dstit.y) = p; + darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); + } + // W face starts at [2B,2B] + for (FaceIterator dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !dstit.end; dstit.advance()) + { + dest(dstit.x, dstit.y) = p; + darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); + } + // U face starts at [2B-1,0] + for (TopFaceIterator dstit(drect.x + 2*B-1, drect.y, tilesize); !dstit.end; dstit.advance()) + { + dest(dstit.x, dstit.y) = p; + } +} + +// draw E-ascending stairs +void drawStairsE(RGBAImage& dest, const ImageRect& drect, RGBAImage& tileWS, RGBAImage& tileU, int B) +{ + int tilesize = 2*B; + // normal W face starts at [0,B]; draw the bottom half of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize >= B) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); + } + } + // normal S face starts at [2B,2B]; draw all but the upper-left quarter of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize >= B || dstit.pos / tilesize >= B) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); + } + } + // normal U face starts at [2B-1,0]; draw the top half of it + TopFaceIterator tdstit(drect.x + 2*B-1, drect.y, tilesize); + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + // if B is odd, we need B pixels from each column, but if it's even, we need to alternate between + // B-1 and B+1 + int cutoff = B; + if (B % 2 == 0) + cutoff += ((tdstit.pos / tilesize) % 2 == 0) ? -1 : 1; + if (tdstit.pos % tilesize < cutoff) + { + dest(tdstit.x, tdstit.y) = tileU(srcit.x, srcit.y); + } + } + // draw the top half of another W face at [B,B/2] + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + B, drect.y + B/2, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + // ...but if B is odd, we need to add an extra [0,1] to the even-numbered columns + int adjust = 0; + if (B % 2 == 1 && (dstit.pos / tilesize) % 2 == 0) + adjust = 1; + if (dstit.pos % tilesize < B) + { + dest(dstit.x, dstit.y + adjust) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y + adjust), 0.9, 0.9, 0.9); + } + } + // draw the bottom half of another U face at [2B-1,B] + tdstit = TopFaceIterator(drect.x + 2*B-1, drect.y + B, tilesize); + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + // again, if B is odd, take B pixels from each column; if even, take B-1 or B+1 + int cutoff = B; + if (B % 2 == 0) + cutoff += ((tdstit.pos / tilesize) % 2 == 0) ? -1 : 1; + if (tdstit.pos % tilesize >= cutoff) + { + dest(tdstit.x, tdstit.y) = tileU(srcit.x, srcit.y); + } + } +} + +// draw E-ascending stairs inverted +void drawInvStairsE(RGBAImage& dest, const ImageRect& drect, RGBAImage& tileWS, RGBAImage& tileU, int B) +{ + int tilesize = 2*B; + // draw the bottom half of a W face at [B,B/2]; do this first because the others will partially cover it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + B, drect.y + B/2, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + // ...but if B is odd, we need to add an extra [0,1] to the even-numbered columns + int adjust = 0; + if (B % 2 == 1 && (dstit.pos / tilesize) % 2 == 0) + adjust = 1; + if (dstit.pos % tilesize >= B) + { + dest(dstit.x, dstit.y + adjust) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y + adjust), 0.9, 0.9, 0.9); + } + } + // normal W face starts at [0,B]; draw the top half of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize < B) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); + } + } + // normal S face starts at [2B,2B]; draw all but the lower-left quarter of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize < B || dstit.pos / tilesize >= B) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); + } + } + // normal U face starts at [2B-1,0]; draw the whole thing + TopFaceIterator tdstit(drect.x + 2*B-1, drect.y, tilesize); + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + dest(tdstit.x, tdstit.y) = tileU(srcit.x, srcit.y); + } +} + +// draw W-ascending stairs +void drawStairsW(RGBAImage& dest, const ImageRect& drect, RGBAImage& tileWS, RGBAImage& tileU, int B) +{ + int tilesize = 2*B; + // draw the top half of an an U face at [2B-1,B] + TopFaceIterator tdstit(drect.x + 2*B-1, drect.y + B, tilesize); + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + // if B is odd, we need B pixels from each column, but if it's even, we need to alternate between + // B-1 and B+1 + int cutoff = B; + if (B % 2 == 0) + cutoff += ((tdstit.pos / tilesize) % 2 == 0) ? -1 : 1; + if (tdstit.pos % tilesize < cutoff) + { + dest(tdstit.x, tdstit.y) = tileU(srcit.x, srcit.y); + } + } + // draw the bottom half of the normal U face at [2B-1,0] + tdstit = TopFaceIterator(drect.x + 2*B-1, drect.y, tilesize); + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + // again, if B is odd, take B pixels from each column; if even, take B-1 or B+1 + int cutoff = B; + if (B % 2 == 0) + cutoff += ((tdstit.pos / tilesize) % 2 == 0) ? -1 : 1; + if (tdstit.pos % tilesize >= cutoff) + { + dest(tdstit.x, tdstit.y) = tileU(srcit.x, srcit.y); + } + } + // normal W face starts at [0,B]; draw it all + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); + } + // normal S face starts at [2B,2B]; draw all but the upper-right quarter of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize >= B || dstit.pos / tilesize < B) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); + } + } +} + +// draw W-ascending stairs inverted +void drawInvStairsW(RGBAImage& dest, const ImageRect& drect, RGBAImage& tileWS, RGBAImage& tileU, int B) +{ + int tilesize = 2*B; + // normal U face starts at [2B-1,0]; draw the whole thing + TopFaceIterator tdstit(drect.x + 2*B-1, drect.y, tilesize); + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + dest(tdstit.x, tdstit.y) = tileU(srcit.x, srcit.y); + } + // normal W face starts at [0,B]; draw it all + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); + } + // normal S face starts at [2B,2B]; draw all but the lower-right quarter of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize < B || dstit.pos / tilesize < B) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); + } + } +} + +// draw N-ascending stairs +void drawStairsN(RGBAImage& dest, const ImageRect& drect, RGBAImage& tileWS, RGBAImage& tileU, int B) +{ + int tilesize = 2*B; + // normal W face starts at [0,B]; draw all but the upper-right quarter of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize >= B || dstit.pos / tilesize < B) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); + } + } + // normal S face starts at [2B,2B]; draw the bottom half of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize >= B) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); + } + } + // normal U face starts at [2B-1,0]; draw the left half of it + TopFaceIterator tdstit(drect.x + 2*B-1, drect.y, tilesize); + int tcutoff = tilesize * B; + bool textra = false; + // if B is odd, we need to skip the last pixel of the last left-half column, and add the very first + // pixel of the first right-half column + if (B % 2 == 1) + { + tcutoff--; + textra = true; + } + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + if (tdstit.pos < tcutoff || (textra && tdstit.pos == tcutoff + 1)) + { + dest(tdstit.x, tdstit.y) = tileU(srcit.x, srcit.y); + } + } + // draw the top half of another S face at [B,1.5B] + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + B, drect.y + 3*B/2, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + // ...but if B is odd, we need to add an extra [0,1] to the odd-numbered columns + int adjust = 0; + if (B % 2 == 1 && (dstit.pos / tilesize) % 2 == 1) + adjust = 1; + if (dstit.pos % tilesize < B) + { + dest(dstit.x, dstit.y + adjust) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y + adjust), 0.8, 0.8, 0.8); + } + } + // draw the right half of another U face at [2B-1,B] + tdstit = TopFaceIterator(drect.x + 2*B-1, drect.y + B, tilesize); + tcutoff = tilesize * B; + textra = false; + // if B is odd, do the reverse of what we did with the top half + if (B % 2 == 1) + { + tcutoff++; + textra = true; + } + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + if (tdstit.pos >= tcutoff || (textra && tdstit.pos == tcutoff - 2)) + { + dest(tdstit.x, tdstit.y) = tileU(srcit.x, srcit.y); + } + } +} + +// draw N-ascending stairs inverted +void drawInvStairsN(RGBAImage& dest, const ImageRect& drect, RGBAImage& tileWS, RGBAImage& tileU, int B) +{ + int tilesize = 2*B; + // draw the bottom half of a S face at [B,1.5B]; do this first because the others will partially cover it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + B, drect.y + 3*B/2, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + // ...but if B is odd, we need to add an extra [0,1] to the odd-numbered columns + int adjust = 0; + if (B % 2 == 1 && (dstit.pos / tilesize) % 2 == 1) + adjust = 1; + if (dstit.pos % tilesize >= B) + { + dest(dstit.x, dstit.y + adjust) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y + adjust), 0.8, 0.8, 0.8); + } + } + // normal S face starts at [2B,2B]; draw the top half of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize < B) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); + } + } + // normal W face starts at [0,B]; draw all but the lower-right quarter of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize < B || dstit.pos / tilesize < B) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); + } + } + // normal U face starts at [2B-1,0]; draw the whole thing + TopFaceIterator tdstit(drect.x + 2*B-1, drect.y, tilesize); + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + dest(tdstit.x, tdstit.y) = tileU(srcit.x, srcit.y); + } +} + +// draw S-ascending stairs +void drawStairsS(RGBAImage& dest, const ImageRect& drect, RGBAImage& tileWS, RGBAImage& tileU, int B) +{ + int tilesize = 2*B; + // draw the left half of an U face at [2B-1,B] + TopFaceIterator tdstit(drect.x + 2*B-1, drect.y + B, tilesize); + int tcutoff = tilesize * B; + bool textra = false; + // if B is odd, we need to skip the last pixel of the last left-half column, and add the very first + // pixel of the first right-half column + if (B % 2 == 1) + { + tcutoff--; + textra = true; + } + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + if (tdstit.pos < tcutoff || (textra && tdstit.pos == tcutoff + 1)) + { + dest(tdstit.x, tdstit.y) = tileU(srcit.x, srcit.y); + } + } + // draw the right half of the normal U face at [2B-1,0] + tdstit = TopFaceIterator(drect.x + 2*B-1, drect.y, tilesize); + tcutoff = tilesize * B; + textra = false; + // if B is odd, do the reverse of what we did with the top half + if (B % 2 == 1) + { + tcutoff++; + textra = true; + } + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + if (tdstit.pos >= tcutoff || (textra && tdstit.pos == tcutoff - 2)) + { + dest(tdstit.x, tdstit.y) = tileU(srcit.x, srcit.y); + } + } + // normal W face starts at [0,B]; draw all but the upper-left quarter of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize >= B || dstit.pos / tilesize >= B) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); + } + } + // normal S face starts at [2B,2B]; draw the whole thing + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); + } +} + +// draw S-ascending stairs inverted +void drawInvStairsS(RGBAImage& dest, const ImageRect& drect, RGBAImage& tileWS, RGBAImage& tileU, int B) +{ + int tilesize = 2*B; + // normal U face starts at [2B-1,0]; draw the whole thing + TopFaceIterator tdstit(drect.x + 2*B-1, drect.y, tilesize); + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + dest(tdstit.x, tdstit.y) = tileU(srcit.x, srcit.y); + } + // normal S face starts at [2B,2B]; draw the whole thing + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + 2*B, drect.y + 2*B, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); + } + // normal W face starts at [0,B]; draw all but the lower-left quarter of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x, drect.y + B, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize < B || dstit.pos / tilesize >= B) + { + dest(dstit.x, dstit.y) = tileWS(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); + } + } +} + +// generic functions for drawing separate faces with offsets and cutoffs +// - tint: controls how much face is darkened +// - offset: controls how far face is offset from original cube position inwards +// - croptop: controls how much of the face is cropped from the top +// - cropbottom: controls how much of the face is cropped from the bottom +// - cropleft: controls how much of the face is cropped from the left +// - cropright: controls how much of the face is cropped from the right +void drawOffsetPaddedWFace(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int B, double tint, int offset, int croptop, int cropbottom, int cropleft, int cropright) +{ + int tilesize = 2 * B; + // draw N face + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + offset, drect.y + B - deinterpolate(offset, 2*tilesize, tilesize), 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize >= croptop && dstit.pos % tilesize < tilesize - cropbottom && dstit.pos / tilesize >= cropleft && dstit.pos / tilesize < tilesize - cropright) + { + dest(dstit.x, dstit.y) = tile(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), tint, tint, tint); + } + } +} +void drawOffsetPaddedSFace(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int B, double tint, int offset, int croptop, int cropbottom, int cropleft, int cropright) +{ + int tilesize = 2 * B; + // draw W face + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + 2*B - offset, drect.y + 2*B - deinterpolate(offset, 2*tilesize, tilesize), -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if(dstit.pos % tilesize >= croptop && dstit.pos % tilesize < tilesize - cropbottom && dstit.pos / tilesize >= cropleft && dstit.pos / tilesize < tilesize - cropright) { + dest(dstit.x, dstit.y) = tile(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), tint, tint, tint); + } + } +} +void drawOffsetPaddedUFace(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int B, int offset, int croptop, int cropbottom, int cropleft, int cropright) +{ + int tilesize = 2 * B; + // draw U face + TopFaceIterator tdstit(drect.x + 2*B-1, drect.y + offset, tilesize); + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + int adjust = 0; + if (croptop % 2 == 0) + adjust += ((tdstit.pos / tilesize) % 2 == 0) ? -1 : 1; // adjust for missing pixels + if(tdstit.pos % tilesize >= croptop + adjust && tdstit.pos % tilesize < tilesize - cropbottom + adjust && tdstit.pos / tilesize >= cropleft && tdstit.pos / tilesize < tilesize - cropright) + blend(dest(tdstit.x, tdstit.y),tile(srcit.x, srcit.y)); + } +} +// draw simple fence post (that actually works for different zoom levels) +void drawFencePost(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int B) +{ + // draw a 2x2 top at [2B-1,B-1] + for (int y = 0; y < 2; y++) + for (int x = 0; x < 2; x++) + dest(drect.x + 2*B - 1 + x, drect.y + B - 1 + y) = tile(x, y); + + // draw a 1x2B side at [2B-1,B+1] + for (int y = 0; y < 2*B; y++) + dest(drect.x + 2*B - 1, drect.y + B + 1 + y) = tile(0, y); + + // draw a 1x2B side at [2B,B+1] + for (int y = 0; y < 2*B; y++) + dest(drect.x + 2*B, drect.y + B + 1 + y) = tile(0, y); +} + +// draw simple fence: post and four rails, each optional +void drawFence(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, bool N, bool S, bool W, bool E, bool post, int B) // re-oriented +{ + // first, E and S rails, since the post should be in front of them + int tilesize = 2*B; + if (N) + { + // N/S face starting at [B,0.5B]; left half, one strip + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + B, drect.y + B/2, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos / tilesize < B && (((dstit.pos % tilesize) * 2 / B) % 4) == 1) + dest(dstit.x, dstit.y) = tile(srcit.x, srcit.y); + } + } + if (E) + { + // E/W face starting at [B,1.5B]; right half, one strip + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + B, drect.y + B*3/2, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos / tilesize >= B && (((dstit.pos % tilesize) * 2 / B) % 4) == 1) + dest(dstit.x, dstit.y) = tile(srcit.x, srcit.y); + } + } + + // now the post + if (post) + drawFencePost(dest, drect, tile, B); + + // now the N and W rails + if (S) + { + // N/S face starting at [B,0.5B]; right half, one strip + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + B, drect.y + B/2, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos / tilesize >= B && (((dstit.pos % tilesize) * 2 / B) % 4) == 1) + dest(dstit.x, dstit.y) = tile(srcit.x, srcit.y); + } + } + if (W) + { + // E/W face starting at [B,1.5B]; left half, one strip + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + B, drect.y + B*3/2, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos / tilesize < B && (((dstit.pos % tilesize) * 2 / B) % 4) == 1) + dest(dstit.x, dstit.y) = tile(srcit.x, srcit.y); + } + } +} +// draw cobblestone/moss wall post +void drawStoneWallPost(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int B) +{ + int tilesize = 2*B; + + int CUTOFF_4_16 = deinterpolate(4, 16, tilesize); // quarter + + drawOffsetPaddedWFace(dest, drect, tile, B, 0.8, CUTOFF_4_16, 0, 0, CUTOFF_4_16, CUTOFF_4_16); // offset W face + drawOffsetPaddedSFace(dest, drect, tile, B, 0.6, CUTOFF_4_16, 0, 0, CUTOFF_4_16, CUTOFF_4_16); // offset S face + drawOffsetPaddedUFace(dest, drect, tile, B, 0, CUTOFF_4_16, CUTOFF_4_16, CUTOFF_4_16, CUTOFF_4_16); // offset U face +} + +// draw solid moss/cobblestone wall +void drawStoneWall(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, bool N, int B) +{ + int tilesize = 2*B; + + int CUTOFF_3_16 = deinterpolate(3, 16, tilesize); // wall cutoff + int CUTOFF_5_16 = deinterpolate(5, 16, tilesize); // wall offset + + if(N) // cobblestone wall going NS + { + drawOffsetPaddedWFace(dest, drect, tile, B, 0.8, CUTOFF_5_16, CUTOFF_3_16, 0, 0, 0); // offset W face + drawOffsetPaddedSFace(dest, drect, tile, B, 0.6, 0, CUTOFF_3_16, 0, CUTOFF_5_16, CUTOFF_5_16); // offset S face + drawOffsetPaddedUFace(dest, drect, tile, B, CUTOFF_3_16, CUTOFF_5_16, CUTOFF_5_16, 0, 0); // offset U face + } + else // cobblestone wall going EW + { + drawOffsetPaddedWFace(dest, drect, tile, B, 0.8, 0, CUTOFF_3_16, 0, CUTOFF_5_16, CUTOFF_5_16); // offset W face + drawOffsetPaddedSFace(dest, drect, tile, B, 0.6, CUTOFF_5_16, CUTOFF_3_16, 0, 0, 0); // offset S face + drawOffsetPaddedUFace(dest, drect, tile, B, CUTOFF_3_16, 0, -1, CUTOFF_5_16, CUTOFF_5_16); // offset U face + } +} +// draw cobblestone/moss stone post(optional) and any combination of wall rails (N/S/E/W) +void drawStoneWallConnected(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, bool N, bool S, bool E, bool W, int B) +{ + int tilesize = 2*B; + + int CUTOFF_3_16 = deinterpolate(3, 16, tilesize); // wall cutoff + int CUTOFF_4_16 = deinterpolate(4, 16, tilesize); // quarter + int CUTOFF_5_16 = deinterpolate(5, 16, tilesize); // wall offset + + // first, N and E rails, since the post should be in front of them + if (N) // draw N rail of the wall + { + drawOffsetPaddedWFace(dest, drect, tile, B, 0.8, CUTOFF_5_16, CUTOFF_3_16, 0, 0, tilesize - CUTOFF_4_16); // offset W face + drawOffsetPaddedUFace(dest, drect, tile, B, CUTOFF_3_16, CUTOFF_5_16, CUTOFF_5_16, 0, tilesize - CUTOFF_4_16); // offset U face + } + if (E) // draw E rail of the wall + { + drawOffsetPaddedSFace(dest, drect, tile, B, 0.6, CUTOFF_5_16, CUTOFF_3_16, 0, tilesize - CUTOFF_4_16, 0); // offset S face + drawOffsetPaddedUFace(dest, drect, tile, B, CUTOFF_3_16, 0, tilesize - CUTOFF_4_16, CUTOFF_5_16, CUTOFF_5_16); // offset U face + } + + // now the post + drawStoneWallPost(dest, drect, tile, B); + + // now the S and W rails + if (S) // draw S rail of the wall + { + drawOffsetPaddedWFace(dest, drect, tile, B, 0.8, CUTOFF_5_16, CUTOFF_3_16, 0, tilesize - CUTOFF_4_16, 0); // offset W face + drawOffsetPaddedSFace(dest, drect, tile, B, 0.6, 0, CUTOFF_3_16, 0, CUTOFF_5_16, CUTOFF_5_16); // offset S face + drawOffsetPaddedUFace(dest, drect, tile, B, CUTOFF_3_16, CUTOFF_5_16, CUTOFF_5_16, tilesize - CUTOFF_4_16, 0); // offset U face + } + if (W) // draw W rail of the wall + { + drawOffsetPaddedWFace(dest, drect, tile, B, 0.8, 0, CUTOFF_3_16, 0, CUTOFF_5_16, CUTOFF_5_16); // offset W face + drawOffsetPaddedSFace(dest, drect, tile, B, 0.6, CUTOFF_5_16, CUTOFF_3_16, 0, 0, tilesize - CUTOFF_4_16); // offset S face + drawOffsetPaddedUFace(dest, drect, tile, B, CUTOFF_3_16, tilesize - CUTOFF_4_16, -1, CUTOFF_5_16, CUTOFF_5_16); // offset U face + } +} + +// draw heart of the beacon +void drawBeacon(RGBAImage& dest, const ImageRect& drect, const RGBAImage& pedestalTile, const RGBAImage& heartTile, const ImageRect& glassrect, int B) +{ + int tilesize = 2*B; + + int CUTOFF_2_16 = deinterpolate(2, 16, tilesize); // eighth + int CUTOFF_3_16 = deinterpolate(3, 16, tilesize); // pedestal height, heart offset + + // draw obsidion pedestal + drawOffsetPaddedWFace(dest, drect, pedestalTile, B, 0.9, CUTOFF_2_16, tilesize - CUTOFF_3_16, 0, CUTOFF_2_16, CUTOFF_2_16); // offset N face + drawOffsetPaddedSFace(dest, drect, pedestalTile, B, 0.8, CUTOFF_2_16, tilesize - CUTOFF_3_16, 0, CUTOFF_2_16, CUTOFF_2_16); // offset W face + drawOffsetPaddedUFace(dest, drect, pedestalTile, B, 2*B - CUTOFF_3_16, CUTOFF_2_16, CUTOFF_2_16, CUTOFF_2_16, CUTOFF_2_16); // offset U face + + // draw nether star heart + drawOffsetPaddedWFace(dest, drect, heartTile, B, 0.9, CUTOFF_3_16, CUTOFF_3_16, CUTOFF_3_16, CUTOFF_3_16, CUTOFF_3_16); // offset N face + drawOffsetPaddedSFace(dest, drect, heartTile, B, 0.8, CUTOFF_3_16, CUTOFF_3_16, CUTOFF_3_16, CUTOFF_3_16, CUTOFF_3_16); // offset W face + drawOffsetPaddedUFace(dest, drect, heartTile, B, CUTOFF_3_16, CUTOFF_3_16, CUTOFF_3_16, CUTOFF_3_16, CUTOFF_3_16); // offset U face + + // blit glass over the drawn beacon heart + alphablit(dest, glassrect, dest, drect.x, drect.y); +} + +// draw oriented anvil; orientation = 0 for NS, 1 - EW orientation +// function does ignore the fact, that anvil facing N is different from one facing S; though difference is so insignificant, that it is deliberately omitted +void drawAnvil(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, const RGBAImage& faceTile, int orientation, int B) +{ + int tilesize = 2*B; + + int CUTOFF_2_16 = deinterpolate(2, 16, tilesize); // base crop + int CUTOFF_3_16 = deinterpolate(3, 16, tilesize); // second base crop, face crop + int CUTOFF_4_16 = deinterpolate(4, 16, tilesize); // quarter + int CUTOFF_5_16 = deinterpolate(5, 16, tilesize); // second base crop on another side, pillar crop + int CUTOFF_6_16 = deinterpolate(6, 16, tilesize); // pillar height crops + int CUTOFF_10_16 = deinterpolate(10, 16, tilesize); // face crop (from bottom) + + // draw anvil base (orientation independent) + drawOffsetPaddedUFace(dest, drect, tile, B, 2*B - CUTOFF_4_16, CUTOFF_2_16, CUTOFF_2_16, CUTOFF_2_16, CUTOFF_2_16); // offset U face + drawOffsetPaddedWFace(dest, drect, tile, B, 0.85, CUTOFF_2_16, tilesize - CUTOFF_4_16, 0, CUTOFF_2_16, CUTOFF_2_16); // offset W face + drawOffsetPaddedSFace(dest, drect, tile, B, 0.7, CUTOFF_2_16, tilesize - CUTOFF_4_16, 0, CUTOFF_2_16, CUTOFF_2_16); // offset S face + + // draw anvil second base (oriented) + int noffset = 0; + int woffset = 0; + if(orientation == 0) + { + noffset = CUTOFF_3_16; + woffset = CUTOFF_5_16; + } + else + { + noffset = CUTOFF_5_16; + woffset = CUTOFF_3_16; + } + drawOffsetPaddedUFace(dest, drect, tile, B, 2*B - CUTOFF_5_16, woffset, woffset, noffset, noffset); // offset U face + drawOffsetPaddedWFace(dest, drect, tile, B, 0.85, woffset, tilesize - CUTOFF_5_16, CUTOFF_4_16, noffset, noffset); // offset W face + drawOffsetPaddedSFace(dest, drect, tile, B, 0.7, noffset, tilesize - CUTOFF_5_16, CUTOFF_4_16, woffset, woffset); // offset S face + + // draw anvil pillar (oriented) + if(orientation == 0) + { + noffset = 0; + woffset = CUTOFF_2_16; + } + else + { + noffset = CUTOFF_2_16; + woffset = 0; + } + drawOffsetPaddedWFace(dest, drect, tile, B, 0.85, CUTOFF_4_16 + woffset, CUTOFF_6_16, CUTOFF_5_16, CUTOFF_4_16 + noffset, CUTOFF_4_16 + noffset); // offset W face + drawOffsetPaddedSFace(dest, drect, tile, B, 0.7, CUTOFF_4_16 + noffset, CUTOFF_6_16, CUTOFF_5_16, CUTOFF_4_16 + woffset, CUTOFF_4_16 + woffset); // offset S face + + // draw anvil face (oriented) + if(orientation == 0) + { + noffset = 0; + woffset = CUTOFF_3_16; + } + else + { + noffset = CUTOFF_3_16; + woffset = 0; + } + int rot = 0; + if(orientation == 0) + rot = 1; + else + rot = 0; + // draw U bottom layer (cropped-texture-fix) + drawOffsetPaddedUFace(dest, drect, tile, B, 0, woffset, woffset, noffset, noffset); // cropped U face + + // draw U face + TopFaceIterator tdstit = TopFaceIterator(drect.x + 2*B-1, drect.y, tilesize); + for (RotatedFaceIterator srcit(0, 0, rot, tilesize, 0); !srcit.end; srcit.advance(), tdstit.advance()) + { + if(ALPHA(faceTile(srcit.x, srcit.y)) != 0) + dest(tdstit.x, tdstit.y) = faceTile(srcit.x, srcit.y); + } + drawOffsetPaddedWFace(dest, drect, tile, B, 0.85, woffset, 0, CUTOFF_10_16, noffset, noffset); // offset W face + drawOffsetPaddedSFace(dest, drect, tile, B, 0.7, noffset, 0, CUTOFF_10_16, woffset, woffset); // offset S face +} + +// draw hopper +void drawHopper(RGBAImage& dest, const ImageRect& drect, const RGBAImage& baseTile, const RGBAImage& insideTile, int B) +{ + int tilesize = 2*B; + + int CUTOFF_4_16 = deinterpolate(4, 16, tilesize); // middle offset, bottom height + int CUTOFF_6_16 = deinterpolate(6, 16, tilesize); // top and middle height + int CUTOFF_10_16 = deinterpolate(10, 16, tilesize); // top height from bottom fix + + // draw funnel + drawOffsetPaddedWFace(dest, drect, baseTile, B, 0.85, CUTOFF_6_16, 2*CUTOFF_6_16, 0, CUTOFF_6_16, CUTOFF_6_16); // offset W face + drawOffsetPaddedSFace(dest, drect, baseTile, B, 0.7, CUTOFF_6_16, 2*CUTOFF_6_16, 0, CUTOFF_6_16, CUTOFF_6_16); // offset S face + + // draw middle part + drawOffsetPaddedWFace(dest, drect, baseTile, B, 0.85, CUTOFF_4_16, CUTOFF_6_16, CUTOFF_4_16, CUTOFF_4_16, CUTOFF_4_16); // offset W face + drawOffsetPaddedSFace(dest, drect, baseTile, B, 0.7, CUTOFF_4_16, CUTOFF_6_16, CUTOFF_4_16, CUTOFF_4_16, CUTOFF_4_16); // offset S face + + // draw hopper top back part + drawPartialSingleFaceBlockImage(dest, drect, baseTile, 3, B, 0, CUTOFF_10_16, 0, 0); // facing S (N wall) + drawPartialSingleFaceBlockImage(dest, drect, baseTile, 0, B, 0, CUTOFF_10_16, 0, 0); // facing W (E wall) + // draw hopper inside + drawOffsetPaddedUFace(dest, drect, insideTile, B, CUTOFF_4_16, 0, 0, 0, 0); // inside offset U face + // draw hopper top front part + drawOffsetPaddedWFace(dest, drect, baseTile, B, 0.85, 0, 0, CUTOFF_10_16, 0, 0); // offset W face + drawOffsetPaddedSFace(dest, drect, baseTile, B, 0.7, 0, 0, CUTOFF_10_16, 0, 0); // offset S face + +} + +// draw empty flower pot +void drawFlowerPot(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, const RGBAImage& fillerTile, bool drawContent, const RGBAImage& contentTile, int contentType, int B) +{ + int tilesize = 2*B; + + int CUTOFF_4_16 = deinterpolate(4, 16, tilesize); // offset, side crop + int CUTOFF_5_16 = deinterpolate(5, 16, tilesize); // offset, side crop + int CUTOFF_6_16 = deinterpolate(6, 16, tilesize); // dirt crop + int CUTOFF_10_16 = deinterpolate(10, 16, tilesize); // top crop + int CUTOFF_11_16 = deinterpolate(11, 16, tilesize); // inner side offset + int CUTOFF_12_16 = deinterpolate(12, 16, tilesize); // dirt offset + + // draw background pot faces + drawOffsetPaddedWFace(dest, drect, tile, B, 0.9, CUTOFF_11_16, CUTOFF_10_16, 0, CUTOFF_5_16, CUTOFF_5_16); // offset W face + drawOffsetPaddedSFace(dest, drect, tile, B, 0.8, CUTOFF_11_16, CUTOFF_10_16, 0, CUTOFF_5_16, CUTOFF_5_16); // offset S face + drawOffsetPaddedUFace(dest, drect, fillerTile, B, CUTOFF_12_16, CUTOFF_6_16, CUTOFF_6_16, CUTOFF_6_16, CUTOFF_6_16); // cropped U face + // draw cactus, if possible + if(drawContent && contentType == 1) { + drawOffsetPaddedWFace(dest, drect, contentTile, B, 0.9, CUTOFF_6_16, 0, CUTOFF_4_16, CUTOFF_6_16, CUTOFF_6_16); // offset W face + drawOffsetPaddedSFace(dest, drect, contentTile, B, 0.8, CUTOFF_6_16, 0, CUTOFF_4_16, CUTOFF_6_16, CUTOFF_6_16); // offset S face + drawOffsetPaddedUFace(dest, drect, contentTile, B, 0, CUTOFF_6_16, CUTOFF_6_16, CUTOFF_6_16, CUTOFF_6_16); // cropped U face + } + // draw front pot faces + drawOffsetPaddedWFace(dest, drect, tile, B, 0.9, CUTOFF_5_16, CUTOFF_10_16, 0, CUTOFF_5_16, CUTOFF_5_16); // offset W face + drawOffsetPaddedSFace(dest, drect, tile, B, 0.8, CUTOFF_5_16, CUTOFF_10_16, 0, CUTOFF_5_16, CUTOFF_5_16); // offset S face + + // draw multipart/sprite contents of the pot + if(drawContent && contentType == 0) + drawItemBlockImage(dest, ImageRect(drect.x, drect.y - CUTOFF_4_16, drect.w, drect.h), contentTile, B); +} + +// draw crappy sign facing out towards the viewer +void drawSign(RGBAImage& dest, const ImageRect& drect, const RGBAImage& faceTile, const RGBAImage& poleTile, int B) +{ + // start with fence post + drawFencePost(dest, drect, poleTile, B); // fence post + + int tilesize = 2*B; + // draw the top half of a tile at [B,B] + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + B, drect.y + B, 0, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize < B) + dest(dstit.x, dstit.y) = faceTile(srcit.x, srcit.y); + } +} + +// draw crappy wall lever +void drawWallLever(RGBAImage& dest, const ImageRect& drect, const RGBAImage& baseTile, const RGBAImage& leverTile, int face, int B) +{ + int CUTOFF_8_16 = deinterpolate(8, 16, 2*B); + int CUTOFF_5_16 = deinterpolate(5, 16, 2*B); + drawPartialSingleFaceBlockImage(dest, drect, baseTile, face, B, CUTOFF_8_16, 0, CUTOFF_5_16, CUTOFF_5_16); + drawSingleFaceBlockImage(dest, drect, leverTile, face, B); +} + +void drawFloorLeverNS(RGBAImage& dest, const ImageRect& drect, const RGBAImage& baseTile, const RGBAImage& leverTile, int B) +{ + int CUTOFF_5_16 = deinterpolate(5, 16, 2*B); + int CUTOFF_4_16 = deinterpolate(4, 16, 2*B); + int CUTOFF_16_16 = deinterpolate(16, 16, 2*B); + drawOffsetPaddedUFace(dest, drect, baseTile, B, CUTOFF_16_16, CUTOFF_5_16, CUTOFF_5_16, CUTOFF_4_16, CUTOFF_4_16); + drawItemBlockImage(dest, drect, leverTile, B); +} + +void drawFloorLeverEW(RGBAImage& dest, const ImageRect& drect, const RGBAImage& baseTile, const RGBAImage& leverTile, int B) +{ + int CUTOFF_5_16 = deinterpolate(5, 16, 2*B); + int CUTOFF_4_16 = deinterpolate(4, 16, 2*B); + int CUTOFF_16_16 = deinterpolate(16, 16, 2*B); + drawOffsetPaddedUFace(dest, drect, baseTile, B, CUTOFF_16_16, CUTOFF_4_16, CUTOFF_4_16, CUTOFF_5_16, CUTOFF_5_16); + drawItemBlockImage(dest, drect, leverTile, B); +} + +void drawCeilLever(RGBAImage& dest, const ImageRect& drect, const RGBAImage& leverTile, int B) +{ + drawItemBlockImage(dest, drect, blockTile(leverTile, 2, false), true, true, true, true, B); +} + +void drawRepeater(RGBAImage& dest, const ImageRect& drect, const RGBAImage& baseTile, const RGBAImage& torchTile, int rot, int B) +{ + drawFloorBlockImage(dest, drect, baseTile, rot, B); + drawItemBlockImage(dest, drect, torchTile, B); +} + +// draw crappy brewing stand: full base tile plus item-shaped stand +void drawBrewingStand(RGBAImage& dest, const ImageRect& drect, RGBAImage& base, RGBAImage& stand, int B) +{ + drawFloorBlockImage(dest, drect, blockTile(base), B); + drawItemBlockImage(dest, drect, stand, B); +} + +void drawCauldron(RGBAImage& dest, const ImageRect& drect, RGBAImage& side, RGBAImage& liquid, int cutoff, int B) +{ + SourceTile sideTile = blockTile(side); + // start with E/S sides, since liquid goes in front of them + drawSingleFaceBlockImage(dest, drect, sideTile, 0, B); + drawSingleFaceBlockImage(dest, drect, sideTile, 3, B); + + // draw the liquid + if (cutoff > 0) + drawPartialBlockImage(dest, drect, liquid, liquid, liquid, B, false, false, true, cutoff, 0, 0, 0, true); + + // now the N/W sides + drawSingleFaceBlockImage(dest, drect, sideTile, 1, B); + drawSingleFaceBlockImage(dest, drect, sideTile, 2, B); +} + +void drawAnchoredFace(RGBAImage& dest, const ImageRect& drect, const RGBAImage& tile, int B, bool N, bool S, bool W, bool E, bool U) +{ + if (N) + drawSingleFaceBlockImage(dest, drect, tile, 3, B); + if (S) + drawSingleFaceBlockImage(dest, drect, tile, 2, B); + if (W) + drawSingleFaceBlockImage(dest, drect, tile, 1, B); + if (E) + drawSingleFaceBlockImage(dest, drect, tile, 0, B); + if (U) + drawCeilBlockImage(dest, drect, tile, 0, B); +} + +// draw crappy dragon egg--just a half-size block +void drawDragonEgg(RGBAImage& dest, const ImageRect& drect, RGBAImage& tile, int B) +{ + int tilesize = 2*B; + // N face at [0,0.5B]; draw the bottom-right quarter of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x, drect.y + B/2, 1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize >= B && dstit.pos / tilesize >= B) + { + dest(dstit.x, dstit.y) = tile(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.9, 0.9, 0.9); + } + } + // W face at [2B,1.5B]; draw the bottom-left quarter of it + for (FaceIterator srcit(0, 0, 0, tilesize), + dstit(drect.x + 2*B, drect.y + 3*B/2, -1, tilesize); !srcit.end; srcit.advance(), dstit.advance()) + { + if (dstit.pos % tilesize >= B && dstit.pos / tilesize < B) + { + dest(dstit.x, dstit.y) = tile(srcit.x, srcit.y); + darken(dest(dstit.x, dstit.y), 0.8, 0.8, 0.8); + } + } + // draw the bottom-right quarter of a U face at [2B-1,0.5B] + TopFaceIterator tdstit(drect.x + 2*B-1, drect.y + B/2, tilesize); + for (FaceIterator srcit(0, 0, 0, tilesize); !srcit.end; srcit.advance(), tdstit.advance()) + { + // again, if B is odd, take B pixels from each column; if even, take B-1 or B+1 + int cutoff = B; + if (B % 2 == 0) + cutoff += ((tdstit.pos / tilesize) % 2 == 0) ? -1 : 1; + if (tdstit.pos % tilesize >= cutoff && tdstit.pos / tilesize >= cutoff) + { + dest(tdstit.x, tdstit.y) = tile(srcit.x, srcit.y); + } + } +} + + + + +int offsetIdx(uint16_t blockID, uint8_t blockData) +{ + return blockID * 16 + blockData; +} + +void setOffsetsForID(uint16_t blockID, int offset, BlockImages& bi) +{ + int start = blockID * 16; + int end = start + 16; + fill(bi.blockOffsets + start, bi.blockOffsets + end, offset); +} + +void BlockImages::setBlockDescriptors(ifstream& descriptorlist) +{ + // parse block descriptor list + int desversion = 0; + descriptorlist.clear(); + descriptorlist.seekg(0, ios_base::beg); + string descriptorline; + + while(getline(descriptorlist, descriptorline)) + { + vector blockDescriptor; + if(descriptorline.size() > 0 && descriptorline[0] != '#') + { + istringstream descriptorlinestream(descriptorline); + while (!descriptorlinestream.eof()) + { + string descriptorfield; + getline( descriptorlinestream, descriptorfield, ' ' ); + if(descriptorfield[0] == '#') + break; + blockDescriptor.push_back(descriptorfield); + desversion++; + } + blockDescriptors.push_back(blockDescriptor); + } + } +} + +int BlockImages::setOffsets() +{ + // default is the dummy image + fill(blockOffsets, blockOffsets + 4096*16, 0); + + // Fill in offset depending on block type + int offsetIterator = 1; + int64_t blockid; + for(vector< vector >::iterator it = blockDescriptors.begin(); it != blockDescriptors.end(); ++it) + { + vector descriptor = (*it); + if(fromstring(descriptor[0], blockid)) + { + int descriptorsize = descriptor.size(); + if(descriptor[1] == "SOLID") + { + if(descriptorsize == 3 || descriptorsize == 5) + setOffsetsForID(blockid, offsetIterator, *this); + } + else if(descriptor[1] == "SOLIDORIENTED") + { + if(descriptorsize == 9) + { + // orientation bits order: N S W E - descriptor[2 3 4 5] + int orientationoffset; + setOffsetsForID(blockid, offsetIterator, *this); // default orientation assumed N, so skip checking N offset descriptor + if(!fromstring(descriptor[5], orientationoffset)) + orientationoffset = 5; + blockOffsets[offsetIdx(blockid, orientationoffset)] = offsetIterator; // oriented E, since front is facing from viewer, it is same as N facing block + if(!fromstring(descriptor[3], orientationoffset)) + orientationoffset = 3; + blockOffsets[offsetIdx(blockid, orientationoffset)] = ++offsetIterator; // oriented S + if(!fromstring(descriptor[4], orientationoffset)) + orientationoffset = 4; + blockOffsets[offsetIdx(blockid, orientationoffset)] = ++offsetIterator; // oriented W + } + } + else if(descriptor[1] == "SOLIDROTATED") + { + if(descriptorsize == 5) + { + // piston value order: down / top / N / S / W / E + // piston extension bit: 0x8 (top bit) + setOffsetsForID(blockid, offsetIterator, *this); + blockOffsets[offsetIdx(blockid, 0)] = blockOffsets[offsetIdx(blockid, 8)] = offsetIterator; // facing Down + blockOffsets[offsetIdx(blockid, 1)] = blockOffsets[offsetIdx(blockid, 9)] = ++offsetIterator; // facing Top + blockOffsets[offsetIdx(blockid, 2)] = blockOffsets[offsetIdx(blockid, 10)] = ++offsetIterator; // facing N + blockOffsets[offsetIdx(blockid, 3)] = blockOffsets[offsetIdx(blockid, 11)] = ++offsetIterator; // facing S + blockOffsets[offsetIdx(blockid, 4)] = blockOffsets[offsetIdx(blockid, 12)] = ++offsetIterator; // facing W + blockOffsets[offsetIdx(blockid, 5)] = blockOffsets[offsetIdx(blockid, 13)] = ++offsetIterator; // facing E + } + } + else if(descriptor[1] == "SOLIDDATA") + { + if(descriptorsize > 2) + { + setOffsetsForID(blockid, offsetIterator, *this); + for(int i = 0; i < descriptorsize - 2; i++, offsetIterator++) + { + blockOffsets[offsetIdx(blockid, i)] = offsetIterator; + } + continue; + } + } + else if(descriptor[1] == "SOLIDDATAFILL") + { + if(descriptorsize > 2) + { + int datasize = descriptorsize - 2; + for(int i = 0; i < descriptorsize - 2; i++, offsetIterator++) + { + for(int di = i; di < 16; di += datasize) + blockOffsets[offsetIdx(blockid, di)] = offsetIterator; + } + continue; + } + } + else if(descriptor[1] == "SOLIDDATATRUNK") + { + if(descriptorsize % 2 == 0 && descriptorsize > 3) + { + setOffsetsForID(blockid, offsetIterator, *this); + for(int i = 0; i < descriptorsize - 2; i += 2, offsetIterator++) + blockOffsets[offsetIdx(blockid, i/2)] = offsetIterator; + continue; + } + } + else if(descriptor[1] == "SOLIDDATATRUNKROTATED") + { + if((descriptorsize - 3) % 3 == 0 && descriptorsize > 3) + { + int orientationFlag; + int groupSize = (descriptorsize - 3)/3; + bool dataOrdered = (descriptor[2] == "O") ? true : false; + setOffsetsForID(blockid, offsetIterator, *this); + for(int i = 0, j = 0; i < descriptorsize - 3 && j < 16; i += 3, j++, offsetIterator++) + { + blockOffsets[offsetIdx(blockid, j)] = offsetIterator; + + if(!fromstring(descriptor[i + 3], orientationFlag)) + orientationFlag = 0; + if(dataOrdered && orientationFlag == 1) + { + blockOffsets[offsetIdx(blockid, ++j)] = ++offsetIterator; // trunk EW + blockOffsets[offsetIdx(blockid, ++j)] = ++offsetIterator; // trunk NS + } + else if(orientationFlag == 1) + { + blockOffsets[offsetIdx(blockid, j + groupSize)] = ++offsetIterator; // trunk EW + blockOffsets[offsetIdx(blockid, j + 2*groupSize)] = ++offsetIterator; // trunk NS + } + } + continue; + } + } + else if(descriptor[1] == "SOLIDOBSTRUCTED") + { + if(descriptorsize == 3 || descriptorsize == 5) + { + setOffsetsForID(blockid, offsetIterator, *this); + offsetIterator += 3;// reserve space for special cases of missing faces facing viewer (W, S, W+S) + } + } + else if(descriptor[1] == "SOLIDPARTIAL") + { + if(descriptorsize == 7) + { + setOffsetsForID(blockid, offsetIterator, *this); + } + } + else if(descriptor[1] == "SOLIDDATAPARTIAL") + { + int datasize = descriptorsize - 4; + if(datasize > 0) + { + setOffsetsForID(blockid, offsetIterator, *this); + for(int i = 0; i < datasize && i < 16; i++, offsetIterator++) + { + blockOffsets[offsetIdx(blockid, i)] = offsetIterator; + } + continue; + } + } + else if(descriptor[1] == "SOLIDDATAPARTIALFILL") + { + int datasize = descriptorsize - 5; + if(datasize % 2 == 0) + { + for(int i = 0; i < datasize/2; i++, offsetIterator++) + for(int di = i; di < 16; di += datasize) + { + blockOffsets[offsetIdx(blockid, di)] = offsetIterator; + } + continue; + } + } + else if(descriptor[1] == "SOLIDTRANSPARENT") + { + if(descriptorsize == 8) + { + setOffsetsForID(blockid, offsetIterator, *this); + } + } + else if(descriptor[1] == "SLABDATA") + { + if(descriptorsize > 2) + { + for(int i = 0; i < descriptorsize - 2; i++, offsetIterator++) + { + // bottom slab + blockOffsets[offsetIdx(blockid, i)] = offsetIterator; + // inverted slab + blockOffsets[offsetIdx(blockid, i + 8)] = ++offsetIterator; + } + continue; + } + } + else if(descriptor[1] == "SLABDATATRUNK") + { + if(descriptorsize % 2 == 0 && descriptorsize > 3) + { + setOffsetsForID(blockid, offsetIterator, *this); + for(int i = 0; i < descriptorsize - 2; i += 2, offsetIterator++) + { + // bottom slab + blockOffsets[offsetIdx(blockid, i/2)] = offsetIterator; + // inverted slab + blockOffsets[offsetIdx(blockid, i/2 + 8)] = ++offsetIterator; + } + continue; + } + } + else if(descriptor[1] == "ITEMDATA" || descriptor[1] == "MULTIITEMDATA") + { + if(descriptorsize > 2) + { + setOffsetsForID(blockid, offsetIterator, *this); + for(int i = 0; i < descriptorsize - 2; i++, offsetIterator++) + { + blockOffsets[offsetIdx(blockid, i)] = offsetIterator; + } + continue; + } + } + else if(descriptor[1] == "ITEMDATAORIENTED") + { + if(descriptorsize > 6) + { + int datasize = descriptorsize - 6; + // orientation bits order: N S W E - descriptor[2 3 4 5] + int orientationoffset; + setOffsetsForID(blockid, offsetIterator, *this); + for(int i = 0; i < datasize && i < 4; i++, offsetIterator++) + { + if(!fromstring(descriptor[2], orientationoffset)) + orientationoffset = 0; + blockOffsets[offsetIdx(blockid, orientationoffset + 4*i)] = offsetIterator; // N + if(!fromstring(descriptor[3], orientationoffset)) + orientationoffset = 2; + blockOffsets[offsetIdx(blockid, orientationoffset + 4*i)] = ++offsetIterator; // S + if(!fromstring(descriptor[4], orientationoffset)) + orientationoffset = 3; + blockOffsets[offsetIdx(blockid, orientationoffset + 4*i)] = ++offsetIterator; // W + if(!fromstring(descriptor[5], orientationoffset)) + orientationoffset = 1; + blockOffsets[offsetIdx(blockid, orientationoffset + 4*i)] = ++offsetIterator; // E + } + continue; + } + } + else if(descriptor[1] == "ITEMDATAFILL") + { + if(descriptorsize > 2) + { + int datasize = descriptorsize - 2; + for(int i = 0; i < datasize; i++, offsetIterator++) + { + for(int di = i; di < 16; di += datasize) + blockOffsets[offsetIdx(blockid, di)] = offsetIterator; + } + continue; + } + + } + else if(descriptor[1] == "STAIR") + { + if(descriptorsize > 2) + { + setOffsetsForID(blockid, offsetIterator, *this); // asc E + blockOffsets[offsetIdx(blockid, 1)] = ++offsetIterator; // asc W + blockOffsets[offsetIdx(blockid, 2)] = ++offsetIterator; // asc S + blockOffsets[offsetIdx(blockid, 3)] = ++offsetIterator; // asc N + blockOffsets[offsetIdx(blockid, 4)] = ++offsetIterator; // asc E inverted + blockOffsets[offsetIdx(blockid, 5)] = ++offsetIterator; // asc W inverted + blockOffsets[offsetIdx(blockid, 6)] = ++offsetIterator; // asc S inverted + blockOffsets[offsetIdx(blockid, 7)] = ++offsetIterator; // asc N inverted + } + } + else if(descriptor[1] == "FENCE") + { + setOffsetsForID(blockid, offsetIterator, *this); // fence post + offsetIterator += 15; // reserve space for fence connections + } + else if(descriptor[1] == "WALLDATA") + { + setOffsetsForID(blockid, offsetIterator, *this); + for(int i = 0; i < descriptorsize - 2; i++, offsetIterator++) + { + blockOffsets[offsetIdx(blockid, i)] = offsetIterator; // wall post + offsetIterator += 17; // reserve space for wall connections for each wall type (per data bit) + plain walls + } + continue; + + } + else if(descriptor[1] == "FENCEGATE") + { + setOffsetsForID(blockid, offsetIterator, *this); // fence gate EW + // fence gate NS + blockOffsets[offsetIdx(blockid, 1)] = blockOffsets[offsetIdx(blockid, 3)] = blockOffsets[offsetIdx(blockid, 5)] = blockOffsets[offsetIdx(blockid, 7)] = ++offsetIterator; + } + else if(descriptor[1] == "MUSHROOM") + { + setOffsetsForID(blockid, offsetIterator, *this); // pores on all sides + blockOffsets[offsetIdx(blockid, 15)] = ++offsetIterator; // all stem + blockOffsets[offsetIdx(blockid, 7)] = blockOffsets[offsetIdx(blockid, 14)] = ++offsetIterator; // all cap + blockOffsets[offsetIdx(blockid, 1)] = blockOffsets[offsetIdx(blockid, 4)] = ++offsetIterator; // cap @ UWN + UW + blockOffsets[offsetIdx(blockid, 2)] = blockOffsets[offsetIdx(blockid, 3)] = blockOffsets[offsetIdx(blockid, 5)] = blockOffsets[offsetIdx(blockid, 6)] = ++offsetIterator; // only top visible - cap @ U + UN + UE + UNE + blockOffsets[offsetIdx(blockid, 8)] = blockOffsets[offsetIdx(blockid, 9)] = ++offsetIterator; // cap @ US + USE + blockOffsets[offsetIdx(blockid, 10)] = ++offsetIterator; // stem + } + else if(descriptor[1] == "CHEST") + { + // single chest + setOffsetsForID(blockid, offsetIterator, *this); + blockOffsets[offsetIdx(blockid, 2)] = blockOffsets[offsetIdx(blockid, 5)] = offsetIterator; // facing N,E + blockOffsets[offsetIdx(blockid, 3)] = ++offsetIterator; // facing S + blockOffsets[offsetIdx(blockid, 4)] = ++offsetIterator; // facing W + if(descriptorsize == 4) // double chests + { + offsetIterator += 8; // reserve space for connected chests variations + } + } + else if(descriptor[1] == "RAIL") + { + setOffsetsForID(blockid, offsetIterator, *this); // flat NS + blockOffsets[offsetIdx(blockid, 1)] = ++offsetIterator; // flat WE + blockOffsets[offsetIdx(blockid, 2)] = ++offsetIterator; // asc E + blockOffsets[offsetIdx(blockid, 3)] = ++offsetIterator; // asc W + blockOffsets[offsetIdx(blockid, 4)] = ++offsetIterator; // asc N + blockOffsets[offsetIdx(blockid, 5)] = ++offsetIterator; // asc S + if(descriptorsize == 4) // rail turns + { + blockOffsets[offsetIdx(blockid, 6)] = ++offsetIterator; // NW corner (->SE) + blockOffsets[offsetIdx(blockid, 7)] = ++offsetIterator; // NE corner (->SW) + blockOffsets[offsetIdx(blockid, 8)] = ++offsetIterator; // SE corner (->NW) + blockOffsets[offsetIdx(blockid, 9)] = ++offsetIterator; // SW corner (->NE) + } + } + else if(descriptor[1] == "RAILPOWERED") + { + setOffsetsForID(blockid, offsetIterator, *this); // flat NS + blockOffsets[offsetIdx(blockid, 1)] = ++offsetIterator; // flat WE + blockOffsets[offsetIdx(blockid, 2)] = ++offsetIterator; // asc E + blockOffsets[offsetIdx(blockid, 3)] = ++offsetIterator; // asc W + blockOffsets[offsetIdx(blockid, 4)] = ++offsetIterator; // asc N + blockOffsets[offsetIdx(blockid, 5)] = ++offsetIterator; // asc S + blockOffsets[offsetIdx(blockid, 8)] = ++offsetIterator; // flat NS powered + blockOffsets[offsetIdx(blockid, 9)] = ++offsetIterator; // flat WE powered + blockOffsets[offsetIdx(blockid, 10)] = ++offsetIterator; // asc E powered + blockOffsets[offsetIdx(blockid, 11)] = ++offsetIterator; // asc W powered + blockOffsets[offsetIdx(blockid, 12)] = ++offsetIterator; // asc N powered + blockOffsets[offsetIdx(blockid, 13)] = ++offsetIterator; // asc S powered + } + else if(descriptor[1] == "PANEDATA") + { + setOffsetsForID(blockid, offsetIterator, *this); // basic pane + for(int i = 0; i < descriptorsize - 2; i++, offsetIterator++) + { + blockOffsets[offsetIdx(blockid, i)] = offsetIterator; // basic pane + offsetIterator += 14; // reserve space for pane connections for each pane type (per data bit) + } + continue; + } + else if(descriptor[1] == "DOOR") + { + setOffsetsForID(blockid, offsetIterator, *this); // door + offsetIterator += 7; // reserve space for different orientations of top and down door parts + } + else if(descriptor[1] == "TRAPDOOR") + { + // closed on the bottom half of block + blockOffsets[offsetIdx(blockid, 0)] = blockOffsets[offsetIdx(blockid, 1)] = blockOffsets[offsetIdx(blockid, 2)] = blockOffsets[offsetIdx(blockid, 3)] = offsetIterator; + // closed on the top half of block + blockOffsets[offsetIdx(blockid, 8)] = blockOffsets[offsetIdx(blockid, 9)] = blockOffsets[offsetIdx(blockid, 10)] = blockOffsets[offsetIdx(blockid, 11)] = ++offsetIterator; + blockOffsets[offsetIdx(blockid, 4)] = blockOffsets[offsetIdx(blockid, 12)] = ++offsetIterator; // attached to S + blockOffsets[offsetIdx(blockid, 5)] = blockOffsets[offsetIdx(blockid, 13)] = ++offsetIterator; // attached to N + blockOffsets[offsetIdx(blockid, 6)] = blockOffsets[offsetIdx(blockid, 14)] = ++offsetIterator; // attached to E + blockOffsets[offsetIdx(blockid, 7)] = blockOffsets[offsetIdx(blockid, 15)] = ++offsetIterator; // attached to W + } + else if(descriptor[1] == "TORCH") + { + setOffsetsForID(blockid, offsetIterator, *this); + blockOffsets[offsetIdx(blockid, 1)] = ++offsetIterator; // pointing E + blockOffsets[offsetIdx(blockid, 2)] = ++offsetIterator; // pointing W + blockOffsets[offsetIdx(blockid, 3)] = ++offsetIterator; // pointing S + blockOffsets[offsetIdx(blockid, 4)] = ++offsetIterator; // pointing N + } + else if(descriptor[1] == "ONWALLPARTIALFILL") + { + if(descriptorsize == 11) + { + int64_t dataoffset; + for(int i = 0; i < 4; i++, offsetIterator++) // for each wall side + { + if(!fromstring(descriptor[2 + i], dataoffset)) + dataoffset = i; + for(int di = dataoffset; di < 16; di += 4) // fill until end of blockdata + blockOffsets[offsetIdx(blockid, di)] = offsetIterator; + } + continue; + } + } + else if(descriptor[1] == "WIRE") + { + setOffsetsForID(blockid, offsetIterator, *this); + offsetIterator += 11; // reserve space for different wire connections + } + else if(descriptor[1] == "BITANCHOR") + { + for(int i = 0; i < 16; i++, offsetIterator++) + blockOffsets[offsetIdx(blockid, i)] = offsetIterator; + continue; + } + else if(descriptor[1] == "STEM") + { + setOffsetsForID(blockid, offsetIterator, *this); + for(int i = 0; i < 8; i++, offsetIterator++) + blockOffsets[offsetIdx(blockid, i)] = offsetIterator; + offsetIterator += 3; // reserve space for different stem connections (N S W E) + } + else if(descriptor[1] == "REPEATER") + { + blockOffsets[offsetIdx(blockid, 0)] = blockOffsets[offsetIdx(blockid, 4)] = blockOffsets[offsetIdx(blockid, 8)] = blockOffsets[offsetIdx(blockid, 12)] = offsetIterator; + blockOffsets[offsetIdx(blockid, 1)] = blockOffsets[offsetIdx(blockid, 5)] = blockOffsets[offsetIdx(blockid, 9)] = blockOffsets[offsetIdx(blockid, 13)] = ++offsetIterator; + blockOffsets[offsetIdx(blockid, 2)] = blockOffsets[offsetIdx(blockid, 6)] = blockOffsets[offsetIdx(blockid, 10)] = blockOffsets[offsetIdx(blockid, 14)] = ++offsetIterator; + blockOffsets[offsetIdx(blockid, 3)] = blockOffsets[offsetIdx(blockid, 7)] = blockOffsets[offsetIdx(blockid, 11)] = blockOffsets[offsetIdx(blockid, 15)] = ++offsetIterator; + } + else if(descriptor[1] == "LEVER") + { + blockOffsets[offsetIdx(blockid, 1)] = blockOffsets[offsetIdx(blockid, 9)] = offsetIterator; // facing E + blockOffsets[offsetIdx(blockid, 2)] = blockOffsets[offsetIdx(blockid, 10)] = ++offsetIterator; // facing W + blockOffsets[offsetIdx(blockid, 3)] = blockOffsets[offsetIdx(blockid, 11)] = ++offsetIterator; // facing S + blockOffsets[offsetIdx(blockid, 4)] = blockOffsets[offsetIdx(blockid, 12)] = ++offsetIterator; // facing N + blockOffsets[offsetIdx(blockid, 5)] = blockOffsets[offsetIdx(blockid, 13)] = ++offsetIterator; // ground NS + blockOffsets[offsetIdx(blockid, 6)] = blockOffsets[offsetIdx(blockid, 14)] = ++offsetIterator; // ground WE + blockOffsets[offsetIdx(blockid, 0)] = blockOffsets[offsetIdx(blockid, 7)] = ++offsetIterator; // ceil NS + blockOffsets[offsetIdx(blockid, 8)] = blockOffsets[offsetIdx(blockid, 15)] = offsetIterator; // ceil WE + } + else if(blockid == 8) // water + { + setOffsetsForID(8, offsetIterator, *this); + setOffsetsForID(9, offsetIterator, *this); + offsetIterator += 3; // reserve space for water special cases (surface, missing W face, missing S face) + blockOffsets[offsetIdx(8, 1)] = blockOffsets[offsetIdx(9, 1)] = ++offsetIterator; // draw water levels + blockOffsets[offsetIdx(8, 2)] = blockOffsets[offsetIdx(9, 2)] = ++offsetIterator; + blockOffsets[offsetIdx(8, 3)] = blockOffsets[offsetIdx(9, 3)] = ++offsetIterator; + blockOffsets[offsetIdx(8, 4)] = blockOffsets[offsetIdx(9, 4)] = ++offsetIterator; + blockOffsets[offsetIdx(8, 5)] = blockOffsets[offsetIdx(9, 5)] = ++offsetIterator; + blockOffsets[offsetIdx(8, 6)] = blockOffsets[offsetIdx(9, 6)] = ++offsetIterator; + blockOffsets[offsetIdx(8, 7)] = blockOffsets[offsetIdx(9, 7)] = ++offsetIterator; + } + else if(blockid == 10) // lava + { + setOffsetsForID(10, offsetIterator, *this); + setOffsetsForID(11, offsetIterator, *this); + blockOffsets[offsetIdx(10, 2)] = blockOffsets[offsetIdx(11, 2)] = ++offsetIterator; + blockOffsets[offsetIdx(10, 4)] = blockOffsets[offsetIdx(11, 4)] = ++offsetIterator; + blockOffsets[offsetIdx(10, 6)] = blockOffsets[offsetIdx(11, 6)] = ++offsetIterator; + } + else if(blockid == 26) // bed + { + blockOffsets[offsetIdx(blockid, 0)] = blockOffsets[offsetIdx(blockid, 4)] = offsetIterator; // bed foot pointing S + blockOffsets[offsetIdx(blockid, 1)] = blockOffsets[offsetIdx(blockid, 5)] = ++offsetIterator; // bed foot pointing W + blockOffsets[offsetIdx(blockid, 2)] = blockOffsets[offsetIdx(blockid, 6)] = ++offsetIterator; // bed foot pointing N + blockOffsets[offsetIdx(blockid, 3)] = blockOffsets[offsetIdx(blockid, 7)] = ++offsetIterator; // bed foot pointing E + blockOffsets[offsetIdx(blockid, 8)] = blockOffsets[offsetIdx(blockid, 12)] = ++offsetIterator; // bed head pointing S + blockOffsets[offsetIdx(blockid, 9)] = blockOffsets[offsetIdx(blockid, 13)] = ++offsetIterator; // bed head pointing W + blockOffsets[offsetIdx(blockid, 10)] = blockOffsets[offsetIdx(blockid, 14)] = ++offsetIterator; // bed head pointing N + blockOffsets[offsetIdx(blockid, 11)] = blockOffsets[offsetIdx(blockid, 15)] = ++offsetIterator; // bed head pointing E + } + else if(blockid == 117 || blockid == 122 || blockid == 138 || blockid == 154 || descriptor[1] == "SIGNPOST") // single special blocks: brewing stand, dragon egg, beacon, hopper + { + setOffsetsForID(blockid, offsetIterator, *this); + } + else if(blockid == 118) // cauldron + { + setOffsetsForID(blockid, offsetIterator, *this); + blockOffsets[offsetIdx(blockid, 1)] = ++offsetIterator; // cauldron 1/3 full + blockOffsets[offsetIdx(blockid, 2)] = ++offsetIterator; // cauldron 2/3 full + blockOffsets[offsetIdx(blockid, 3)] = ++offsetIterator; // cauldron full + } + else if(blockid == 140) // flower pot + { + setOffsetsForID(blockid, offsetIterator, *this); // flower pot + for(int i = 0; i < descriptorsize - 4; i++) + blockOffsets[offsetIdx(blockid, i)] = ++offsetIterator; + } + else if(blockid == 145) // anvil + { + setOffsetsForID(blockid, offsetIterator, *this); // anvil NS + blockOffsets[offsetIdx(blockid, 1)] = blockOffsets[offsetIdx(blockid, 3)] = ++offsetIterator; // anvil EW + blockOffsets[offsetIdx(blockid, 4)] = blockOffsets[offsetIdx(blockid, 6)] = ++offsetIterator; // slightly damaged anvil NS + blockOffsets[offsetIdx(blockid, 5)] = blockOffsets[offsetIdx(blockid, 7)] = ++offsetIterator; // slightly damaged anvil EW + blockOffsets[offsetIdx(blockid, 8)] = blockOffsets[offsetIdx(blockid, 10)] = ++offsetIterator; // very damaged anvil NS + blockOffsets[offsetIdx(blockid, 9)] = blockOffsets[offsetIdx(blockid, 11)] = ++offsetIterator; // very damaged anvil EW + } + offsetIterator++; + } + } + + return offsetIterator; +} + +void BlockImages::checkOpacityAndTransparency(int B) +{ + opacity.clear(); + opacity.resize(blockversion, true); + transparency.clear(); + transparency.resize(blockversion, true); + + for (int i = 0; i < blockversion; i++) + { + ImageRect rect = getRect(i); + // use the face iterators to examine the N, W, and U faces; any non-100% alpha makes + // the block non-opaque, and any non-0% alpha makes the block non-transparent + int tilesize = 2*B; + // N face starts at [0,B] + for (FaceIterator it(rect.x, rect.y + B, 1, tilesize); !it.end; it.advance()) + { + int a = ALPHA(img(it.x, it.y)); + if (a < 255) + opacity[i] = false; + if (a > 0) + transparency[i] = false; + if (!opacity[i] && !transparency[i]) + break; + } + if (!opacity[i] && !transparency[i]) + continue; + // W face starts at [2B,2B] + for (FaceIterator it(rect.x + 2*B, rect.y + 2*B, -1, tilesize); !it.end; it.advance()) + { + int a = ALPHA(img(it.x, it.y)); + if (a < 255) + opacity[i] = false; + if (a > 0) + transparency[i] = false; + if (!opacity[i] && !transparency[i]) + break; + } + if (!opacity[i] && !transparency[i]) + continue; + // U face starts at [2B-1,0] + for (TopFaceIterator it(rect.x + 2*B-1, rect.y, tilesize); !it.end; it.advance()) + { + int a = ALPHA(img(it.x, it.y)); + if (a < 255) + opacity[i] = false; + if (a > 0) + transparency[i] = false; + if (!opacity[i] && !transparency[i]) + break; + } + } +} + +void BlockImages::retouchAlphas(int B) +{ + for (int i = 0; i < blockversion; i++) + { + ImageRect rect = getRect(i); + // use the face iterators to examine the N, W, and U faces; any alpha under 10 is changed + // to 0, and any alpha above 245 is changed to 255 + int tilesize = 2*B; + // N face starts at [0,B] + for (FaceIterator it(rect.x, rect.y + B, 1, tilesize); !it.end; it.advance()) + { + int a = ALPHA(img(it.x, it.y)); + if (a < 10) + setAlpha(img(it.x, it.y), 0); + else if (a > 245) + setAlpha(img(it.x, it.y), 255); + } + // W face starts at [2B,2B] + for (FaceIterator it(rect.x + 2*B, rect.y + 2*B, -1, tilesize); !it.end; it.advance()) + { + int a = ALPHA(img(it.x, it.y)); + if (a < 10) + setAlpha(img(it.x, it.y), 0); + else if (a > 245) + setAlpha(img(it.x, it.y), 255); + } + // U face starts at [2B-1,0] + for (TopFaceIterator it(rect.x + 2*B-1, rect.y, tilesize); !it.end; it.advance()) + { + int a = ALPHA(img(it.x, it.y)); + if (a < 10) + setAlpha(img(it.x, it.y), 0); + else if (a > 245) + setAlpha(img(it.x, it.y), 255); + } + } +} + +bool BlockImages::construct(int B, ifstream& texturelist, ifstream& descriptorlist, const string& imgpath) +{ + if (B < 2) + return false; + + int32_t tileSize = 2*B; + + // determine some cutoff values for partial block images: given a particular pixel offset in texture tile--for + // example, the end portal frame texture is missing its top 3 (out of 16) pixels--we need to know which pixel + // in the resized tile is the first one past that offset + // ...if the texture tile size isn't a multiple of 16 for some reason, this may break down and be ugly + int CUTOFFS_16[17]; + for(int i = 0; i < 17; i++) + { + CUTOFFS_16[i] = deinterpolate(i, 16, tileSize); + } + + // Load block textures into hashmap based on their name + unordered_map blockTextures; // vector holding source textures + string blocktexturespath = imgpath + "/textures/blocks"; // default path + string textureline; + string texturename; + int32_t textureSize; + unsigned textureiterator = 0; + bool missingtextures = false; + RGBAImage iblockimage; + + // Reset file stream + texturelist.clear(); + texturelist.seekg(0, ios_base::beg); + // Read list of textures (each texture file on new line) + while(getline(texturelist, textureline)) + { + vector textureDirectives; + // read texture list + if(textureline.size() > 0 && textureline[0] != '#') + { + istringstream texturelinestream(textureline); + while (!texturelinestream.eof()) + { + string texturedirective; + getline( texturelinestream, texturedirective, ' ' ); + if(texturedirective[0] == '#') + break; + if(texturedirective.size() == 0) // skip empty directive + continue; + textureDirectives.push_back(texturedirective); + } + } + // process directives + if(textureDirectives.size() == 2 && textureDirectives[0] == "$") // switch texture directory + { + blocktexturespath = imgpath + textureDirectives[1]; + } + else if(textureDirectives.size() > 2 && textureDirectives[0] == "/") // process texture with directives + { + if(!iblockimage.readPNG(blocktexturespath + "/" + textureDirectives[1])) // texture read error + { + cerr << "[blocktexture.list]" << textureiterator + 1 << " - " << blocktexturespath << "/" << textureDirectives[1] << " is missing or invalid (check, if it has an alpha channel!)" << endl; + missingtextures = true; + } + else // process texture + { + // make a tile + RGBAImage iblocktile; + iblocktile.create(tileSize, tileSize); + textureSize = min(iblockimage.w, iblockimage.h); // assume block textures are square, and choose smallest of the sides if they aren't + resize(iblockimage, ImageRect(0, 0, textureSize, textureSize), iblocktile, ImageRect(0, 0, tileSize, tileSize)); + + // directive: RENAME DARKEN OFFSET OFFSETTILE EXPAND CROP FLIPX CHEST LCHEST + texturename = textureDirectives[1]; + for(unsigned i = 2; i < textureDirectives.size(); i++) + { + if(textureDirectives[i] == "RENAME") // assign different name to texture + { + texturename = textureDirectives[++i]; + } + else if(textureDirectives[i] == "DARKEN") // make texture darker; can be used for colorizing monochrome textures aswell + { + double darkenR = strtod(textureDirectives[++i].c_str(), NULL); + double darkenG = strtod(textureDirectives[++i].c_str(), NULL); + double darkenB = strtod(textureDirectives[++i].c_str(), NULL); + darken(iblocktile, ImageRect(0, 0, tileSize, tileSize), darkenR, darkenG, darkenB); + } + else if(textureDirectives[i] == "OFFSET") + { + int xOffset = (fromstring(textureDirectives[++i], xOffset)) ? xOffset : 0; + int yOffset = (fromstring(textureDirectives[++i], yOffset)) ? yOffset : 0; + int xSign = (xOffset > 0) - (xOffset < 0); + int ySign = (yOffset > 0) - (yOffset < 0); + imgoffset(iblocktile, xSign * CUTOFFS_16[xSign * xOffset%17], ySign * CUTOFFS_16[ySign * yOffset%17]); + } + else if(textureDirectives[i] == "OFFSETTILE") + { + int xOffset = (fromstring(textureDirectives[++i], xOffset)) ? xOffset : 0; + int yOffset = (fromstring(textureDirectives[++i], yOffset)) ? yOffset : 0; + int xSign = (xOffset > 0) - (xOffset < 0); + int ySign = (yOffset > 0) - (yOffset < 0); + imgtileoffset(iblocktile, xSign * CUTOFFS_16[xSign * xOffset%17], ySign * CUTOFFS_16[ySign * yOffset%17]); + } + else if(textureDirectives[i] == "EXPAND") + { + int xExpansion = (fromstring(textureDirectives[++i], xExpansion)) ? xExpansion % 17 : 0; + int yExpansion = (fromstring(textureDirectives[++i], yExpansion)) ? yExpansion % 17 : 0; + resize(iblockimage, ImageRect(CUTOFFS_16[xExpansion], CUTOFFS_16[yExpansion], iblockimage.w - 2 * CUTOFFS_16[xExpansion], iblockimage.h - 2 * CUTOFFS_16[yExpansion]), iblocktile, ImageRect(0, 0, tileSize, tileSize)); + } + else if(textureDirectives[i] == "CROP") + { + int tCrop = (fromstring(textureDirectives[++i], tCrop)) ? tCrop % 17 : 0; + int rCrop = (fromstring(textureDirectives[++i], rCrop)) ? rCrop % 17 : 0; + int bCrop = (fromstring(textureDirectives[++i], bCrop)) ? bCrop % 17 : 0; + int lCrop = (fromstring(textureDirectives[++i], lCrop)) ? lCrop % 17 : 0; + imgcrop(iblocktile, ImageRect(CUTOFFS_16[lCrop], CUTOFFS_16[tCrop], tileSize - (CUTOFFS_16[lCrop] + CUTOFFS_16[rCrop]), tileSize - (CUTOFFS_16[tCrop] + CUTOFFS_16[bCrop]))); + } + else if(textureDirectives[i] == "FLIPX") + { + flipX(iblocktile, ImageRect(0, 0, tileSize, tileSize)); + } + else if(textureDirectives[i] == "CHEST") + { + textureiterator += generateChestTiles(blockTextures, iblockimage, texturename, B); + } + else if(textureDirectives[i] == "LCHEST") + { + textureiterator += generateLargeChestTiles(blockTextures, iblockimage, texturename, B); + } + } + blockTextures[texturename] = iblocktile; + } + } + else if(textureDirectives.size() == 1) // just load the texture + { + if(!iblockimage.readPNG(blocktexturespath + "/" + textureDirectives[0])) // texture read error + { + cerr << "[texture.list]" << textureiterator + 1 << " - " << blocktexturespath << "/" << textureDirectives[0] << " is missing or invalid" << endl; + missingtextures = true; + } + else + { + texturename = textureDirectives[0]; + RGBAImage iblocktile; + iblocktile.create(tileSize, tileSize); + textureSize = min(iblockimage.w, iblockimage.h); // assume block textures are square, and choose smallest of the sides if they aren't + resize(iblockimage, ImageRect(0, 0, textureSize, textureSize), iblocktile, ImageRect(0, 0, tileSize, tileSize)); + blockTextures[texturename] = iblocktile; + } + } + textureiterator++; + } + texturelist.close(); + if(missingtextures) + return false; + + RGBAImage emptyTile; + emptyTile.create(tileSize, tileSize); // create empty/dummy texture + blockTextures["/.png"] = emptyTile; + + // initialize image + img.create(rectsize * 16, (blockversion/16 + 1) * rectsize); + + // build all block images based on block descriptors + // Fill in offset depending on block type + unsigned offsetIterator = 1; + int64_t blockid; + for(vector< vector >::iterator it = blockDescriptors.begin(); it != blockDescriptors.end(); ++it) + { + vector descriptor = (*it); + if(fromstring(descriptor[0], blockid)) + { + int descriptorsize = descriptor.size(); + if(descriptor[1] == "SOLID") + { + if(descriptorsize == 3) + { + // all faces have the same texture + RGBAImage& facetexture = blockTextures.at((descriptor[2] + ".png")); + drawBlockImage(img, getRect(offsetIterator), facetexture, facetexture, facetexture, B); + } + else if(descriptorsize == 5) + { + // separate textures for separate faces + drawBlockImage(img, getRect(offsetIterator), blockTextures.at((descriptor[2] + ".png")), blockTextures.at((descriptor[3] + ".png")), blockTextures.at((descriptor[4] + ".png")), B); + } + } + else if(descriptor[1] == "SOLIDORIENTED") + { + if(descriptorsize == 9) + { + // texture order: face side top - descriptor[6 7 8] + drawBlockImage(img, getRect(offsetIterator), blockTextures.at((descriptor[7] + ".png")), blockTextures.at((descriptor[7] + ".png")), blockTextures.at((descriptor[8] + ".png")), B); // this will server for both N and E orientations + drawBlockImage(img, getRect(++offsetIterator), blockTextures.at((descriptor[7] + ".png")), blockTextures.at((descriptor[6] + ".png")), blockTextures.at((descriptor[8] + ".png")), B); + drawBlockImage(img, getRect(++offsetIterator), blockTextures.at((descriptor[6] + ".png")), blockTextures.at((descriptor[7] + ".png")), blockTextures.at((descriptor[8] + ".png")), B); + } + } + else if(descriptor[1] == "SOLIDROTATED") + { + if(descriptorsize == 5) + { + // piston value order: down / top / N / S / W / E + // piston extension bit: 0x8 (top bit) + // texture order: top side bottom - descriptor[2 3 4] + RGBAImage& topface = blockTextures.at((descriptor[2] + ".png")); + RGBAImage& sideface = blockTextures.at((descriptor[3] + ".png")); + RGBAImage& bottomface = blockTextures.at((descriptor[4] + ".png")); + drawRotatedBlockImage(img, getRect(offsetIterator), blockTile(sideface, 2, false), blockTile(sideface, 2, false), blockTile(bottomface), B); // facing Down + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(sideface), blockTile(sideface), blockTile(topface), B); // facing Up + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(sideface, 1, false), blockTile(bottomface), blockTile(sideface, 1, false), B); // facing N + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(sideface, 3, false), blockTile(topface), blockTile(sideface, 3, false), B); // facing S + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(topface), blockTile(sideface, 1, false), blockTile(sideface, 2, false), B); // facing W + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(bottomface), blockTile(sideface, 3, false), blockTile(sideface), B); // facing E + } + } + else if(descriptor[1] == "SOLIDDATA" || descriptor[1] == "SOLIDDATAFILL") + { + if(descriptorsize > 2) + { + for(int i = 2; i < descriptorsize; i++, offsetIterator++) + drawBlockImage(img, getRect(offsetIterator), blockTextures.at((descriptor[i] + ".png")), blockTextures.at((descriptor[i] + ".png")), blockTextures.at((descriptor[i] + ".png")), B); + continue; + } + } + else if(descriptor[1] == "SOLIDDATATRUNK") + { + if(descriptorsize % 2 == 0 && descriptorsize > 3) + { + for(int i = 2; i < descriptorsize; i += 2, offsetIterator++) + drawBlockImage(img, getRect(offsetIterator), blockTextures.at((descriptor[i+1] + ".png")), blockTextures.at((descriptor[i+1] + ".png")), blockTextures.at((descriptor[i] + ".png")), B); + continue; + } + } + else if(descriptor[1] == "SOLIDDATATRUNKROTATED") + { + if((descriptorsize - 3) % 3 == 0 && descriptorsize > 3) + { + int orientationFlag; + + RGBAImage trunkTop; + RGBAImage trunkSide; + for(int i = 0; i < descriptorsize - 3; i += 3, offsetIterator++) + { + trunkTop = blockTextures.at((descriptor[i+4] + ".png")); + trunkSide = blockTextures.at((descriptor[i+5] + ".png")); + drawBlockImage(img, getRect(offsetIterator), trunkSide, trunkSide, trunkTop, B); // trunk UD + + if(!fromstring(descriptor[i + 3], orientationFlag)) + orientationFlag = 0; + if(orientationFlag == 1) + { + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(trunkTop, 0, false), blockTile(trunkSide, 3, false), blockTile(trunkSide, 0, false), B); // trunk EW + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(trunkSide, 1, false), blockTile(trunkTop, 0, false), blockTile(trunkSide, 1, false), B); // trunk NS + } + } + continue; + } + } + else if(descriptor[1] == "SOLIDOBSTRUCTED") + { + if(descriptorsize == 3) + { + // all faces have the same texture + RGBAImage& facetexture = blockTextures.at((descriptor[2] + ".png")); + drawBlockImage(img, getRect(offsetIterator), facetexture, facetexture, facetexture, B); + drawRotatedBlockImage(img, getRect(++offsetIterator), SourceTile(), SourceTile(), blockTile(facetexture), B); // only top surface + drawRotatedBlockImage(img, getRect(++offsetIterator), SourceTile(), blockTile(facetexture), blockTile(facetexture), B); // missing W + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(facetexture), SourceTile(), blockTile(facetexture), B); // missing S + } + else if(descriptorsize == 5) + { + // separate textures for separate faces + RGBAImage& wtexture = blockTextures.at((descriptor[2] + ".png")); + RGBAImage& stexture = blockTextures.at((descriptor[3] + ".png")); + RGBAImage& toptexture = blockTextures.at((descriptor[4] + ".png")); + drawBlockImage(img, getRect(offsetIterator), wtexture, stexture, toptexture, B); + drawRotatedBlockImage(img, getRect(++offsetIterator), SourceTile(), blockTile(stexture), blockTile(toptexture), B); // missing W + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(wtexture), SourceTile(), blockTile(toptexture), B); // missing S + drawRotatedBlockImage(img, getRect(++offsetIterator), SourceTile(), SourceTile(), blockTile(toptexture), B); // only top surface + } + } + else if(descriptor[1] == "SOLIDPARTIAL") + { + int topCutoff; + int bottomCutoff; + if(fromstring(descriptor[2], topCutoff) && fromstring(descriptor[3], bottomCutoff) && descriptorsize == 7) { + topCutoff = min(max(topCutoff, 0), 16); + bottomCutoff = min(max(bottomCutoff, 0), 16); + drawPartialBlockImage(img, getRect(offsetIterator), blockTextures.at((descriptor[4] + ".png")), blockTextures.at((descriptor[5] + ".png")), blockTextures.at((descriptor[6] + ".png")), B, CUTOFFS_16[topCutoff], CUTOFFS_16[bottomCutoff], 0, 0, false); + } + } + else if(descriptor[1] == "SOLIDDATAPARTIAL") + { + int topCutoff; + int bottomCutoff; + int datasize = descriptorsize - 4; + if(fromstring(descriptor[2], topCutoff) && fromstring(descriptor[3], bottomCutoff) && datasize > 0) + { + topCutoff = min(max(topCutoff, 0), 16); + bottomCutoff = min(max(bottomCutoff, 0), 16); + + RGBAImage facetexture; + for(int i = 0; i < datasize; i++, offsetIterator++) + { + facetexture = blockTextures.at((descriptor[i+4] + ".png")); + drawPartialBlockImage(img, getRect(offsetIterator), facetexture, facetexture, facetexture, B, CUTOFFS_16[topCutoff], CUTOFFS_16[bottomCutoff], 0, 0, false); + } + continue; + } + } + else if(descriptor[1] == "SOLIDDATAPARTIALFILL") + { + int datasize = descriptorsize - 5; + if(datasize % 2 == 0) + { + int topCutoff; + int bottomCutoff; + RGBAImage& Wface = blockTextures.at((descriptor[2] + ".png")); + RGBAImage& Sface = blockTextures.at((descriptor[3] + ".png")); + RGBAImage& Uface = blockTextures.at((descriptor[4] + ".png")); + for(int i = 0; i < datasize; i += 2, offsetIterator++) + { + if(!fromstring(descriptor[i+5], topCutoff)) topCutoff = 0; + if(!fromstring(descriptor[i+6], bottomCutoff)) bottomCutoff = 0; + drawPartialBlockImage(img, getRect(offsetIterator), Wface, Sface, Uface, B, CUTOFFS_16[topCutoff], CUTOFFS_16[bottomCutoff], 0, 0, false); + } + continue; + } + } + else if(descriptor[1] == "SOLIDTRANSPARENT") + { + if(descriptorsize == 8) + { + RGBAImage& Dface = blockTextures.at((descriptor[2] + ".png")); + RGBAImage& Nface = blockTextures.at((descriptor[3] + ".png")); + RGBAImage& Eface = blockTextures.at((descriptor[4] + ".png")); + RGBAImage& Wface = blockTextures.at((descriptor[5] + ".png")); + RGBAImage& Sface = blockTextures.at((descriptor[6] + ".png")); + RGBAImage& Uface = blockTextures.at((descriptor[7] + ".png")); + + drawFloorBlockImage(img, getRect(offsetIterator), Dface, 0, B); + drawSingleFaceBlockImage(img, getRect(offsetIterator), Nface, 3, B); + drawSingleFaceBlockImage(img, getRect(offsetIterator), Eface, 0, B); + drawSingleFaceBlockImage(img, getRect(offsetIterator), Wface, 1, B); + drawSingleFaceBlockImage(img, getRect(offsetIterator), Sface, 2, B); + drawOffsetPaddedUFace(img, getRect(offsetIterator), Uface, B, 0, 0, 0, 0, 0); + //drawRotatedBlockImage(img, getRect(offsetIterator), SourceTile(), SourceTile(), blockTile(Uface), B); // closed on the top half of block + } + } + else if(descriptor[1] == "SLABDATA") + { + if(descriptorsize > 2) + { + for(int i = 2; i < descriptorsize; i++, offsetIterator++) + { + // bottom slab + drawPartialBlockImage(img, getRect(offsetIterator), blockTextures.at((descriptor[i] + ".png")), blockTextures.at((descriptor[i] + ".png")), blockTextures.at((descriptor[i] + ".png")), B, CUTOFFS_16[8], 0, 0, 0, true); + // inverted slab + drawPartialBlockImage(img, getRect(++offsetIterator), blockTextures.at((descriptor[i] + ".png")), blockTextures.at((descriptor[i] + ".png")), blockTextures.at((descriptor[i] + ".png")), B, 0, CUTOFFS_16[8], 0, 0, false); + } + continue; + } + } + else if(descriptor[1] == "SLABDATATRUNK") + { + if(descriptorsize % 2 == 0 && descriptorsize > 3) + { + for(int i = 2; i < descriptorsize; i += 2, offsetIterator++) + { + // bottom slab + drawPartialBlockImage(img, getRect(offsetIterator), blockTextures.at((descriptor[i+1] + ".png")), blockTextures.at((descriptor[i+1] + ".png")), blockTextures.at((descriptor[i] + ".png")), B, CUTOFFS_16[8], 0, 0, 0, true); + // inverted slab + drawPartialBlockImage(img, getRect(++offsetIterator), blockTextures.at((descriptor[i+1] + ".png")), blockTextures.at((descriptor[i+1] + ".png")), blockTextures.at((descriptor[i] + ".png")), B, 0, CUTOFFS_16[8], 0, 0, false); + } + continue; + } + } + else if(descriptor[1] == "ITEMDATA" || descriptor[1] == "ITEMDATAFILL") + { + if(descriptorsize > 2) + { + for(int i = 2; i < descriptorsize; i++, offsetIterator++) + { + drawItemBlockImage(img, getRect(offsetIterator), blockTextures.at((descriptor[i] + ".png")), B); + } + continue; + } + } + else if(descriptor[1] == "ITEMDATAORIENTED") + { + if(descriptorsize > 6) + { + int datasize = descriptorsize - 6; + // orientation bits order: N S W E - descriptor[2 3 4 5] + RGBAImage baseTile; + for(int i = 0; i < datasize; i++, offsetIterator++) + { + baseTile = blockTextures[descriptor[i + 6] + ".png"]; + drawPartialItemBlockImage(img, getRect(offsetIterator), baseTile, 0, false, false, true, false, false, B); // attached to N (S side) + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTile, 0, true, true, false, false, false, B); // attached S (N side) + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTile, 0, false, false, false, false, true, B); // attached W (E side) + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTile, 0, true, false, false, true, false, B); // attached E (W side) + } + continue; + } + } + else if(descriptor[1] == "MULTIITEMDATA") + { + if(descriptorsize > 2) + { + for(int i = 2; i < descriptorsize; i++, offsetIterator++) + { + drawMultiItemBlockImage(img, getRect(offsetIterator), blockTextures.at((descriptor[i] + ".png")), B); + } + continue; + } + } + else if(descriptor[1] == "STAIR") + { + if(descriptorsize > 2) + { + RGBAImage sideface; + RGBAImage topface; + if(descriptorsize == 3) + { + sideface = topface = blockTextures.at((descriptor[2] + ".png")); + + } + else if(descriptorsize > 3) + { + sideface = blockTextures.at((descriptor[2] + ".png")); + topface = blockTextures.at((descriptor[3] + ".png")); + + } + drawStairsE(img, getRect(offsetIterator), sideface, topface, B); // stairs asc E + drawStairsW(img, getRect(++offsetIterator), sideface, topface, B); // stairs asc W + drawStairsS(img, getRect(++offsetIterator), sideface, topface, B); // stairs asc S + drawStairsN(img, getRect(++offsetIterator), sideface, topface, B); // stairs asc N + drawInvStairsE(img, getRect(++offsetIterator), sideface, topface, B); // stairs asc E inverted + drawInvStairsW(img, getRect(++offsetIterator), sideface, topface, B); // stairs asc W inverted + drawInvStairsS(img, getRect(++offsetIterator), sideface, topface, B); // stairs asc S inverted + drawInvStairsN(img, getRect(++offsetIterator), sideface, topface, B); // stairs asc N inverted + } + } + else if(descriptor[1] == "FENCE") + { + RGBAImage& baseTexture = blockTextures.at((descriptor[2] + ".png")); + drawFencePost(img, getRect(offsetIterator), baseTexture, B); // fence post + drawFence(img, getRect(++offsetIterator), baseTexture, true, false, false, false, true, B); // fence N + drawFence(img, getRect(++offsetIterator), baseTexture, false, true, false, false, true, B); // fence S + drawFence(img, getRect(++offsetIterator), baseTexture, true, true, false, false, true, B); // fence NS + drawFence(img, getRect(++offsetIterator), baseTexture, false, false, false, true, true, B); // fence E + drawFence(img, getRect(++offsetIterator), baseTexture, true, false, false, true, true, B); // fence NE + drawFence(img, getRect(++offsetIterator), baseTexture, false, true, false, true, true, B); // fence SE + drawFence(img, getRect(++offsetIterator), baseTexture, true, true, false, true, true, B); // fence NSE + drawFence(img, getRect(++offsetIterator), baseTexture, false, false, true, false, true, B); // fence W + drawFence(img, getRect(++offsetIterator), baseTexture, true, false, true, false, true, B); // fence NW + drawFence(img, getRect(++offsetIterator), baseTexture, false, true, true, false, true, B); // fence SW + drawFence(img, getRect(++offsetIterator), baseTexture, true, true, true, false, true, B); // fence NSW + drawFence(img, getRect(++offsetIterator), baseTexture, false, false, true, true, true, B); // fence EW + drawFence(img, getRect(++offsetIterator), baseTexture, true, false, true, true, true, B); // fence NEW + drawFence(img, getRect(++offsetIterator), baseTexture, false, true, true, true, true, B); // fence SEW + drawFence(img, getRect(++offsetIterator), baseTexture, true, true, true, true, true, B); // fence NSEW + } + else if(descriptor[1] == "WALLDATA") + { + RGBAImage baseTexture; + for(int i = 0; i < descriptorsize - 2; i++, offsetIterator++) + { + baseTexture = blockTextures.at((descriptor[2 + i] + ".png")); + drawStoneWallPost(img, getRect(offsetIterator), baseTexture, B); // cobblestone wall post + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, true, false, false, false, B); // cobblestone wall post N + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, false, true, false, false, B); // cobblestone wall post S + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, true, true, false, false, B); // cobblestone wall post NS + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, false, false, true, false, B); // cobblestone wall post E + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, true, false, true, false, B); // cobblestone wall post NE + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, false, true, true, false, B); // cobblestone wall post SE + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, true, true, true, false, B); // cobblestone wall post NSE + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, false, false, false, true, B); // cobblestone wall post W + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, true, false, false, true, B); // cobblestone wall post NW + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, false, true, false, true, B); // cobblestone wall post SW + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, true, true, false, true, B); // cobblestone wall post NSW + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, false, false, true, true, B); // cobblestone wall post EW + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, true, false, true, true, B); // cobblestone wall post NEW + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, false, true, true, true, B); // cobblestone wall post SEW + drawStoneWallConnected(img, getRect(++offsetIterator), baseTexture, true, true, true, true, B); // cobblestone wall post NSEW + drawStoneWall(img, getRect(++offsetIterator), baseTexture, true, B); // cobblestone wall NS + drawStoneWall(img, getRect(++offsetIterator), baseTexture, false, B); // cobblestone wall EW + } + continue; + } + else if(descriptor[1] == "FENCEGATE") + { + RGBAImage& baseTile = blockTextures.at((descriptor[2] + ".png")); + drawOffsetPaddedSFace(img, getRect(offsetIterator), baseTile, B, 0.8, CUTOFFS_16[8], CUTOFFS_16[4], CUTOFFS_16[4], 0, 0); // fence gate EW + drawOffsetPaddedWFace(img, getRect(++offsetIterator), baseTile, B, 0.9, CUTOFFS_16[8], CUTOFFS_16[4], CUTOFFS_16[4], 0, 0); // fence gate NS + } + else if(descriptor[1] == "MUSHROOM") + { + RGBAImage& poreTile = blockTextures.at((descriptor[2] + ".png")); + RGBAImage& stemTile = blockTextures.at((descriptor[3] + ".png")); + RGBAImage& capTile = blockTextures.at((descriptor[4] + ".png")); + drawBlockImage(img, getRect(offsetIterator), poreTile, poreTile, poreTile, B); // pores on all sides + drawBlockImage(img, getRect(++offsetIterator), stemTile, stemTile, stemTile, B); // all stem + drawBlockImage(img, getRect(++offsetIterator), capTile, capTile, capTile, B); // all cap + drawBlockImage(img, getRect(++offsetIterator), capTile, poreTile, capTile, B); // cap @ UWN + UW + drawBlockImage(img, getRect(++offsetIterator), poreTile, poreTile, capTile, B); // only top - cap @ U + UN + UE + UNE + drawBlockImage(img, getRect(++offsetIterator), poreTile, capTile, capTile, B); // cap @ US + USE + drawBlockImage(img, getRect(++offsetIterator), stemTile, stemTile, poreTile, B); // stem + } + else if(descriptor[1] == "CHEST") + { + // single chest + RGBAImage& chestTop = blockTextures.at((descriptor[2] + ".png_0")); + RGBAImage& chestFront = blockTextures.at((descriptor[2] + ".png_1")); + RGBAImage& chestSide = blockTextures.at((descriptor[2] + ".png_2")); + drawBlockImage(img, getRect(offsetIterator), chestSide, chestSide, chestTop, B); // facing N,E + drawBlockImage(img, getRect(++offsetIterator), chestSide, chestFront, chestTop, B); // facing S + drawBlockImage(img, getRect(++offsetIterator), chestFront, chestSide, chestTop, B); // facing W + if(descriptorsize == 4) // double chests + { + RGBAImage largeTile0 = blockTextures.at((descriptor[3] + ".png_0")); + RGBAImage largeTile1 = blockTextures.at((descriptor[3] + ".png_1")); + RGBAImage largeTile2 = blockTextures.at((descriptor[3] + ".png_2")); + RGBAImage largeTile3 = blockTextures.at((descriptor[3] + ".png_3")); + RGBAImage largeTile4 = blockTextures.at((descriptor[3] + ".png_4")); + RGBAImage largeTile5 = blockTextures.at((descriptor[3] + ".png_5")); + RGBAImage largeTile6 = blockTextures.at((descriptor[3] + ".png_6")); + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(largeTile6, 0, false), blockTile(largeTile4, 0, false), blockTile(largeTile0, 1, false), B); // double chest W facing N + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(largeTile6, 0, false), blockTile(largeTile5, 0, false), blockTile(largeTile1, 1, false), B); // double chest E facing N + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(largeTile6, 0, false), blockTile(largeTile2, 0, false), blockTile(largeTile0, 1, false), B); // double chest W facing S + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(largeTile6, 0, false), blockTile(largeTile3, 0, false), blockTile(largeTile1, 1, false), B); // double chest E facing S + drawBlockImage(img, getRect(++offsetIterator), largeTile2, largeTile6, largeTile0, B); // double chest S facing W + drawBlockImage(img, getRect(++offsetIterator), largeTile3, largeTile6, largeTile1, B); // double chest N facing W + drawBlockImage(img, getRect(++offsetIterator), largeTile4, largeTile6, largeTile0, B); // double chest S facing E + drawBlockImage(img, getRect(++offsetIterator), largeTile5, largeTile6, largeTile1, B); // double chest N facing E + } + } + else if(descriptor[1] == "RAIL") + { + RGBAImage& rail = blockTextures.at(descriptor[2] + ".png"); + drawFloorBlockImage(img, getRect(offsetIterator), rail, 1, B); // flat NS + drawFloorBlockImage(img, getRect(++offsetIterator), rail, 0, B); // flat EW + drawAngledFloorBlockImage(img, getRect(++offsetIterator), rail, 0, 0, B); // asc E + drawAngledFloorBlockImage(img, getRect(++offsetIterator), rail, 0, 2, B); // asc W + drawAngledFloorBlockImage(img, getRect(++offsetIterator), rail, 1, 3, B); // asc N + drawAngledFloorBlockImage(img, getRect(++offsetIterator), rail, 1, 1, B); // asc S + if(descriptorsize == 4) + { + RGBAImage& railcorner = blockTextures.at(descriptor[3] + ".png"); + drawFloorBlockImage(img, getRect(++offsetIterator), railcorner, 1, B); // track NW corner + drawFloorBlockImage(img, getRect(++offsetIterator), railcorner, 0, B); // track NE corner + drawFloorBlockImage(img, getRect(++offsetIterator), railcorner, 3, B); // track SE corner + drawFloorBlockImage(img, getRect(++offsetIterator), railcorner, 2, B); // track SW corner + } + } + else if(descriptor[1] == "RAILPOWERED") + { + RGBAImage& rail = blockTextures.at(descriptor[2] + ".png"); + RGBAImage& railpowered = blockTextures.at(descriptor[3] + ".png"); + drawFloorBlockImage(img, getRect(offsetIterator), rail, 1, B); // flat NS + drawFloorBlockImage(img, getRect(++offsetIterator), rail, 0, B); // flat EW + drawAngledFloorBlockImage(img, getRect(++offsetIterator), rail, 0, 0, B); // asc E + drawAngledFloorBlockImage(img, getRect(++offsetIterator), rail, 0, 2, B); // asc W + drawAngledFloorBlockImage(img, getRect(++offsetIterator), rail, 1, 3, B); // asc N + drawAngledFloorBlockImage(img, getRect(++offsetIterator), rail, 1, 1, B); // asc S + + drawFloorBlockImage(img, getRect(++offsetIterator), railpowered, 1, B); // flat NS powered + drawFloorBlockImage(img, getRect(++offsetIterator), railpowered, 0, B); // flat EW powered + drawAngledFloorBlockImage(img, getRect(++offsetIterator), railpowered, 0, 0, B); // asc E powered + drawAngledFloorBlockImage(img, getRect(++offsetIterator), railpowered, 0, 2, B); // asc W powered + drawAngledFloorBlockImage(img, getRect(++offsetIterator), railpowered, 1, 3, B); // asc N powered + drawAngledFloorBlockImage(img, getRect(++offsetIterator), railpowered, 1, 1, B); // asc S powered + } + else if(descriptor[1] == "PANEDATA") + { + RGBAImage baseTexture; + for(int i = 0; i < descriptorsize - 2; i++, offsetIterator++) + { + baseTexture = blockTextures.at((descriptor[2 + i] + ".png")); + drawItemBlockImage(img, getRect(offsetIterator), baseTexture, B); // base pane unconnected / NSWE + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, true, false, false, false, B); // pane N + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, false, true, false, false, B); // pane S + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, true, true, false, false, B); // pane NS + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, false, false, false, true, B); // pane E + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, true, false, false, true, B); // pane NE + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, false, true, false, true, B); // pane SE + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, true, true, false, true, B); // pane NSE + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, false, false, true, false, B); // pane W + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, true, false, true, false, B); // pane NW + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, false, true, true, false, B); // pane SW + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, true, true, true, false, B); // pane NSW + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, false, false, true, true, B); // pane EW + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, true, false, true, true, B); // pane NEW + drawPartialItemBlockImage(img, getRect(++offsetIterator), baseTexture, 0, false, false, true, true, true, B); // pane SEW + } + continue; + } + else if(descriptor[1] == "DOOR") + { + RGBAImage& bottomTile = blockTextures.at((descriptor[2] + ".png")); + RGBAImage& topTile = blockTextures.at((descriptor[3] + ".png")); + drawSingleFaceBlockImage(img, getRect(offsetIterator), bottomTile, 1, B); // door W side + drawSingleFaceBlockImage(img, getRect(++offsetIterator), bottomTile, 3, B); // door N side + drawSingleFaceBlockImage(img, getRect(++offsetIterator), bottomTile, 0, B); // door E side + drawSingleFaceBlockImage(img, getRect(++offsetIterator), bottomTile, 2, B); // door S side + drawSingleFaceBlockImage(img, getRect(++offsetIterator), topTile, 1, B); // door W top side + drawSingleFaceBlockImage(img, getRect(++offsetIterator), topTile, 3, B); // door N top side + drawSingleFaceBlockImage(img, getRect(++offsetIterator), topTile, 0, B); // door E top side + drawSingleFaceBlockImage(img, getRect(++offsetIterator), topTile, 2, B); // door S top side + } + else if(descriptor[1] == "TRAPDOOR") + { + RGBAImage& baseTile = blockTextures.at((descriptor[2] + ".png")); + + drawFloorBlockImage(img, getRect(offsetIterator), blockTile(baseTile), B); // closed on the bottom half of block + drawRotatedBlockImage(img, getRect(++offsetIterator), SourceTile(), SourceTile(), blockTile(baseTile), B); // closed on the top half of block + drawSingleFaceBlockImage(img, getRect(++offsetIterator), baseTile, 2, B); // trapdoor open S + drawSingleFaceBlockImage(img, getRect(++offsetIterator), baseTile, 3, B); // trapdoor open N + drawSingleFaceBlockImage(img, getRect(++offsetIterator), baseTile, 0, B); // trapdoor open E + drawSingleFaceBlockImage(img, getRect(++offsetIterator), baseTile, 1, B); // trapdoor open W + } + else if(descriptor[1] == "TORCH") + { + RGBAImage& torchTile = blockTextures.at((descriptor[2] + ".png")); + drawItemBlockImage(img, getRect(offsetIterator), torchTile, B); // torch floor + drawSingleFaceBlockImage(img, getRect(++offsetIterator), torchTile, 1, B); // torch pointing E + drawSingleFaceBlockImage(img, getRect(++offsetIterator), torchTile, 0, B); // torch pointing W + drawSingleFaceBlockImage(img, getRect(++offsetIterator), torchTile, 3, B); // torch pointing S + drawSingleFaceBlockImage(img, getRect(++offsetIterator), torchTile, 2, B); // torch pointing N + } + else if(descriptor[1] == "ONWALLPARTIALFILL") + { + if(descriptorsize == 11) + { + RGBAImage faceTile = blockTextures[descriptor[6] + ".png"]; + int croptop; + int cropbottom; + int cropleft; + int cropright; + if(!fromstring(descriptor[7], croptop)) croptop = 0; + else croptop = CUTOFFS_16[croptop]; + if(!fromstring(descriptor[8], cropbottom)) cropbottom = 0; + else cropbottom = CUTOFFS_16[cropbottom]; + if(!fromstring(descriptor[9], cropleft)) cropleft = 0; + else cropleft = CUTOFFS_16[cropleft]; + if(!fromstring(descriptor[10], cropright)) cropright = 0; + else cropright = CUTOFFS_16[cropright]; + if((croptop + cropbottom + cropleft + cropright) == 0) + { + drawSingleFaceBlockImage(img, getRect(offsetIterator), faceTile, 3, B); // facing S (N wall) + drawSingleFaceBlockImage(img, getRect(++offsetIterator), faceTile, 2, B); // facing N (S wall) + drawSingleFaceBlockImage(img, getRect(++offsetIterator), faceTile, 1, B); // facing E (W wall) + drawSingleFaceBlockImage(img, getRect(++offsetIterator), faceTile, 0, B); // facing W (E wall) + } + else + { + drawPartialSingleFaceBlockImage(img, getRect(offsetIterator), faceTile, 3, B, croptop, cropbottom, cropleft, cropright); // facing S (N wall) + drawPartialSingleFaceBlockImage(img, getRect(++offsetIterator), faceTile, 2, B, croptop, cropbottom, cropleft, cropright); // facing N (S wall) + drawPartialSingleFaceBlockImage(img, getRect(++offsetIterator), faceTile, 1, B, croptop, cropbottom, cropleft, cropright); // facing E (W wall) + drawPartialSingleFaceBlockImage(img, getRect(++offsetIterator), faceTile, 0, B, croptop, cropbottom, cropleft, cropright); // facing W (E wall) + } + } + } + else if(descriptor[1] == "WIRE") + { + //setOffsetsForID(blockid, offsetIterator, *this); + RGBAImage wireCross; + RGBAImage& wireTile = blockTextures[descriptor[2] + ".png"]; + if(descriptorsize == 4) + { + wireCross = blockTextures[descriptor[3] + ".png"]; + drawOffsetPaddedUFace(img, getRect(offsetIterator), wireCross, B, CUTOFFS_16[16], CUTOFFS_16[5], CUTOFFS_16[5], CUTOFFS_16[5], CUTOFFS_16[5]); // unconnected + } + else + { + // we will need to create crossed wire + RGBAImage rotatedTile; + rotatedTile.create(tileSize, tileSize); + RotatedFaceIterator dstit(0, 0, 1, tileSize, false); + for (FaceIterator srcit(0, 0, 0, tileSize); !srcit.end; srcit.advance(), dstit.advance()) + rotatedTile(dstit.x, dstit.y) = wireTile(srcit.x, srcit.y); + + wireCross.create(tileSize, tileSize); + alphablit(wireTile, ImageRect(0, 0, tileSize, tileSize), wireCross, 0, 0); + alphablit(rotatedTile, ImageRect(0, 0, tileSize, tileSize), wireCross, 0, 0); + + drawFloorBlockImage(img, getRect(offsetIterator), wireTile, 0, B); // unconnected + } + drawFloorBlockImage(img, getRect(++offsetIterator), wireTile, 0, B); // flat NS + drawFloorBlockImage(img, getRect(++offsetIterator), wireTile, 1, B); // flat WE + drawOffsetPaddedUFace(img, getRect(++offsetIterator), wireCross, B, CUTOFFS_16[16], 0, CUTOFFS_16[5], 0, CUTOFFS_16[5]); // NE + drawOffsetPaddedUFace(img, getRect(++offsetIterator), wireCross, B, CUTOFFS_16[16], 0, CUTOFFS_16[5], CUTOFFS_16[5], 0); // SE + drawOffsetPaddedUFace(img, getRect(++offsetIterator), wireCross, B, CUTOFFS_16[16], 0, CUTOFFS_16[5], 0, 0); // NSE + drawOffsetPaddedUFace(img, getRect(++offsetIterator), wireCross, B, CUTOFFS_16[16], CUTOFFS_16[5], 0, 0, CUTOFFS_16[5]); // NW + drawOffsetPaddedUFace(img, getRect(++offsetIterator), wireCross, B, CUTOFFS_16[16], CUTOFFS_16[5], 0, CUTOFFS_16[5], 0); // SW + drawOffsetPaddedUFace(img, getRect(++offsetIterator), wireCross, B, CUTOFFS_16[16], CUTOFFS_16[5], 0, 0, 0); // NSW + drawOffsetPaddedUFace(img, getRect(++offsetIterator), wireCross, B, CUTOFFS_16[16], 0, 0, 0, CUTOFFS_16[5]); // NEW + drawOffsetPaddedUFace(img, getRect(++offsetIterator), wireCross, B, CUTOFFS_16[16], 0, 0, CUTOFFS_16[5], 0); // SEW + drawOffsetPaddedUFace(img, getRect(++offsetIterator), wireCross, B, CUTOFFS_16[16], 0, 0, 0, 0); // NSEW + } + else if(descriptor[1] == "BITANCHOR") + { + RGBAImage& baseTile = blockTextures[descriptor[2] + ".png"]; + drawAnchoredFace(img, getRect(offsetIterator), baseTile, B, false, false, false, false, true); // U face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, false, true, false, false, false); // S face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, false, false, true, false, false); // W face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, false, true, true, false, false); // SW face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, true, false, false, false, false); // N face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, true, true, false, false, false); // NS face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, true, false, true, false, false); // NW face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, true, true, true, false, false); // NSW face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, false, false, false, true, false); // E face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, false, true, false, true, false); // SE face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, false, false, true, true, false); // WE face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, false, true, true, true, false); // SWE face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, true, false, false, true, false); // NE face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, true, true, false, true, false); // NSE face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, true, false, true, true, false); // NWE face + drawAnchoredFace(img, getRect(++offsetIterator), baseTile, B, true, true, true, true, false); // NSWE face + } + else if(descriptor[1] == "STEM") + { + RGBAImage stemTile; + for(int i = 0; i < 8; i++, offsetIterator++) + { + stemTile = blockTextures[descriptor[2] + ".png_" + tostring(i)]; + drawItemBlockImage(img, getRect(offsetIterator), stemTile, B); // draw stem level 0-7 + } + stemTile = blockTextures.at(descriptor[3] + ".png_0"); + RGBAImage& stemBent = blockTextures.at(descriptor[3] + ".png_1"); + drawPartialItemBlockImage(img, getRect(offsetIterator), stemTile, 0, false, true, true, false, false, B); // N + drawPartialItemBlockImage(img, getRect(++offsetIterator), stemBent, 0, false, true, true, false, false, B); // S + drawPartialItemBlockImage(img, getRect(++offsetIterator), stemTile, 0, false, false, false, true, true, B); // W + drawPartialItemBlockImage(img, getRect(++offsetIterator), stemBent, 0, false, false, false, true, true, B); // E + } + else if(descriptor[1] == "REPEATER") + { + RGBAImage& baseTile = blockTextures.at(descriptor[2] + ".png"); + RGBAImage& torchTile = blockTextures.at(descriptor[3] + ".png"); + drawRepeater(img, getRect(offsetIterator), baseTile, torchTile, 1, B); // repeater on N + drawRepeater(img, getRect(++offsetIterator), baseTile, torchTile, 0, B); // repeater on E + drawRepeater(img, getRect(++offsetIterator), baseTile, torchTile, 3, B); // repeater on S + drawRepeater(img, getRect(++offsetIterator), baseTile, torchTile, 2, B); // repeater on W + } + else if(descriptor[1] == "LEVER") + { + RGBAImage& baseTile = blockTextures.at(descriptor[2] + ".png"); + RGBAImage& leverTile = blockTextures.at(descriptor[3] + ".png"); + drawWallLever(img, getRect(offsetIterator), baseTile, leverTile, 1, B); // wall lever facing E + drawWallLever(img, getRect(++offsetIterator), baseTile, leverTile, 0, B); // wall lever facing W + drawWallLever(img, getRect(++offsetIterator), baseTile, leverTile, 3, B); // wall lever facing S + drawWallLever(img, getRect(++offsetIterator), baseTile, leverTile, 2, B); // wall lever facing N + drawFloorLeverNS(img, getRect(++offsetIterator), baseTile, leverTile, B); // ground lever NS + drawFloorLeverEW(img, getRect(++offsetIterator), baseTile, leverTile, B); // ground lever WE + drawCeilLever(img, getRect(++offsetIterator), leverTile, B); // ground lever NS / WE +//# 34 LEVER (specify lever base texture, lever handle texture; like lever) +/* + blockOffsets[offsetIdx(blockid, 1)] = blockOffsets[offsetIdx(blockid, 9)] = offsetIterator; // facing E + blockOffsets[offsetIdx(blockid, 2)] = blockOffsets[offsetIdx(blockid, 10)] = ++offsetIterator; // facing W + blockOffsets[offsetIdx(blockid, 3)] = blockOffsets[offsetIdx(blockid, 11)] = ++offsetIterator; // facing S + blockOffsets[offsetIdx(blockid, 4)] = blockOffsets[offsetIdx(blockid, 12)] = ++offsetIterator; // facing N + blockOffsets[offsetIdx(blockid, 5)] = blockOffsets[offsetIdx(blockid, 13)] = ++offsetIterator; // ground NS + blockOffsets[offsetIdx(blockid, 6)] = blockOffsets[offsetIdx(blockid, 14)] = ++offsetIterator; // ground WE + blockOffsets[offsetIdx(blockid, 0)] = blockOffsets[offsetIdx(blockid, 7)] = ++offsetIterator; // ceil NS + blockOffsets[offsetIdx(blockid, 8)] = blockOffsets[offsetIdx(blockid, 15)] = offsetIterator; // ceil WE + */ + } + else if(descriptor[1] == "SIGNPOST") + { + drawSign(img, getRect(offsetIterator), blockTextures.at((descriptor[2] + ".png")), blockTextures.at((descriptor[3] + ".png")), B); // generic sign post + } + else if(blockid == 8) // water + { + if(blockTextures.count((descriptor[2] + ".png")) == 0) + { + offsetIterator += 11; + continue; + } + + RGBAImage& waterTile = blockTextures.at((descriptor[2] + ".png")); + drawBlockImage(img, getRect(offsetIterator), waterTile, waterTile, waterTile, B); // full water + drawRotatedBlockImage(img, getRect(++offsetIterator), SourceTile(), SourceTile(), blockTile(waterTile), B); // water surface + drawRotatedBlockImage(img, getRect(++offsetIterator), SourceTile(), blockTile(waterTile), blockTile(waterTile), B); // water missing W + drawRotatedBlockImage(img, getRect(++offsetIterator), blockTile(waterTile), SourceTile(), blockTile(waterTile), B); // water missing S + + drawPartialBlockImage(img, getRect(++offsetIterator), waterTile, waterTile, waterTile, B, CUTOFFS_16[2], 0, 0, 0, true); // water level 7 + drawPartialBlockImage(img, getRect(++offsetIterator), waterTile, waterTile, waterTile, B, CUTOFFS_16[4], 0, 0, 0, true); // water level 6 + drawPartialBlockImage(img, getRect(++offsetIterator), waterTile, waterTile, waterTile, B, CUTOFFS_16[6], 0, 0, 0, true); // water level 5 + drawPartialBlockImage(img, getRect(++offsetIterator), waterTile, waterTile, waterTile, B, CUTOFFS_16[8], 0, 0, 0, true); // water level 4 + drawPartialBlockImage(img, getRect(++offsetIterator), waterTile, waterTile, waterTile, B, CUTOFFS_16[10], 0, 0, 0, true); // water level 3 + drawPartialBlockImage(img, getRect(++offsetIterator), waterTile, waterTile, waterTile, B, CUTOFFS_16[12], 0, 0, 0, true); // water level 2 + drawPartialBlockImage(img, getRect(++offsetIterator), waterTile, waterTile, waterTile, B, CUTOFFS_16[14], 0, 0, 0, true); // water level 1 + + } + else if(blockid == 10) // lava + { + if(blockTextures.count((descriptor[2] + ".png")) == 0) + { + offsetIterator += 4; + continue; + } + + RGBAImage& lavaTile = blockTextures.at((descriptor[2] + ".png")); + drawBlockImage(img, getRect(offsetIterator), lavaTile, lavaTile, lavaTile, B); // full lava + drawPartialBlockImage(img, getRect(++offsetIterator), lavaTile, lavaTile, lavaTile, B, CUTOFFS_16[4], 0, 0, 0, true); // lava level 3 + drawPartialBlockImage(img, getRect(++offsetIterator), lavaTile, lavaTile, lavaTile, B, CUTOFFS_16[8], 0, 0, 0, true); // lava level 2 + drawPartialBlockImage(img, getRect(++offsetIterator), lavaTile, lavaTile, lavaTile, B, CUTOFFS_16[12], 0, 0, 0, true); // lava level 1 + } + else if(blockid == 26) // bed + { + RGBAImage& footFront = blockTextures.at((descriptor[2] + ".png")); + RGBAImage& footSide = blockTextures.at((descriptor[3] + ".png")); + RGBAImage& footTop = blockTextures.at((descriptor[4] + ".png")); + RGBAImage& headFront = blockTextures.at((descriptor[5] + ".png")); + RGBAImage& headSide = blockTextures.at((descriptor[6] + ".png")); + RGBAImage& headTop = blockTextures.at((descriptor[7] + ".png")); + drawPartialBlockImage(img, getRect(offsetIterator), footSide, footFront, footTop, B, true, false, true, CUTOFFS_16[8], 0, 0, 0, false); // bed foot pointing S + drawPartialBlockImage(img, getRect(++offsetIterator), footFront, footSide, footTop, B, false, true, true, CUTOFFS_16[8], 0, 3, 2, false); // bed foot pointing W + drawPartialBlockImage(img, getRect(++offsetIterator), footSide, footFront, footTop, B, true, true, true, CUTOFFS_16[8], 0, 2, 1, false); // bed foot pointing N + drawPartialBlockImage(img, getRect(++offsetIterator), footFront, footSide, footTop, B, true, true, true, CUTOFFS_16[8], 0, 1, 0, false); // bed foot pointing E + drawPartialBlockImage(img, getRect(++offsetIterator), headSide, headFront, headTop, B, true, true, true, CUTOFFS_16[8], 0, 0, 0, false); // bed head pointing S + drawPartialBlockImage(img, getRect(++offsetIterator), headFront, headSide, headTop, B, true, true, true, CUTOFFS_16[8], 0, 3, 2, false); // bed head pointing W + drawPartialBlockImage(img, getRect(++offsetIterator), headSide, headFront, headTop, B, true, false, true, CUTOFFS_16[8], 0, 2, 1, false); // bed head pointing N + drawPartialBlockImage(img, getRect(++offsetIterator), headFront, headSide, headTop, B, false, true, true, CUTOFFS_16[8], 0, 1, 0, false); // bed head pointing E + } + else if(blockid == 117) // brewing stand + { + drawBrewingStand(img, getRect(offsetIterator), blockTextures.at((descriptor[2] + ".png")), blockTextures.at((descriptor[3] + ".png")), B); // brewing stand + } + else if(blockid == 118) // cauldron + { + RGBAImage& cauldronSide = blockTextures.at((descriptor[2] + ".png")); + RGBAImage& waterTile = blockTextures.at((descriptor[3] + ".png")); + drawCauldron(img, getRect(offsetIterator), cauldronSide, waterTile, 0, B); // cauldron empty + drawCauldron(img, getRect(++offsetIterator), cauldronSide, waterTile, CUTOFFS_16[10], B); // cauldron 1/3 full + drawCauldron(img, getRect(++offsetIterator), cauldronSide, waterTile, CUTOFFS_16[6], B); // cauldron 2/3 full + drawCauldron(img, getRect(++offsetIterator), cauldronSide, waterTile, CUTOFFS_16[2], B); // cauldron full + } + else if(blockid == 122) // dragon egg + { + drawDragonEgg(img, getRect(offsetIterator), blockTextures.at((descriptor[2] + ".png")), B); // dragon egg + } + else if(blockid == 138) // beacon + { + int coverid; + if(!fromstring(descriptor[3], coverid)) + coverid = 20; // glass + drawBeacon(img, getRect(offsetIterator), blockTextures.at((descriptor[3] + ".png")), blockTextures.at((descriptor[2] + ".png")), getRect(blockOffsets[offsetIdx(coverid, 0)]), B); + } + else if(blockid == 140) // flower pot + { + RGBAImage& flowerpotTile = blockTextures.at((descriptor[2] + ".png")); + RGBAImage& fillerTile = blockTextures.at((descriptor[3] + ".png")); + int contenttype = 0; + drawFlowerPot(img, getRect(offsetIterator), flowerpotTile, fillerTile, false, fillerTile, contenttype, B); // flower pot [empty] + for(int i = 4; i < descriptorsize; i++) + { + if(i == 12) + contenttype = 1; + else + contenttype = 0; + drawFlowerPot(img, getRect(++offsetIterator), flowerpotTile, fillerTile, true, blockTextures.at((descriptor[i] + ".png")), contenttype, B); + } + } + else if(blockid == 145) // anvil + { + RGBAImage& basetexture = blockTextures.at((descriptor[2] + ".png")); + RGBAImage& anvildamage0 = blockTextures.at((descriptor[3] + ".png")); + RGBAImage& anvildamage1 = blockTextures.at((descriptor[4] + ".png")); + RGBAImage& anvildamage2 = blockTextures.at((descriptor[5] + ".png")); + drawAnvil(img, getRect(offsetIterator), basetexture, anvildamage0, 0, B); + drawAnvil(img, getRect(++offsetIterator), basetexture, anvildamage0, 1, B); + drawAnvil(img, getRect(++offsetIterator), basetexture, anvildamage1, 0, B); + drawAnvil(img, getRect(++offsetIterator), basetexture, anvildamage1, 1, B); + drawAnvil(img, getRect(++offsetIterator), basetexture, anvildamage2, 0, B); + drawAnvil(img, getRect(++offsetIterator), basetexture, anvildamage2, 1, B); + } + else if(blockid == 154) + { + drawHopper(img, getRect(offsetIterator), blockTextures.at((descriptor[2] + ".png")), blockTextures.at((descriptor[3] + ".png")), B); // hopper + } + offsetIterator++; + } + } + + return true; +} diff --git a/blockimages.h b/blockimages.h old mode 100755 new mode 100644 index 7266fcc..2e35872 --- a/blockimages.h +++ b/blockimages.h @@ -1,294 +1,132 @@ -// Copyright 2010-2012 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#ifndef BLOCKIMAGES_H -#define BLOCKIMAGES_H - -#include - -#include "rgba.h" - -// IMPORTANT NOTE: -// This program was written before the location of the sun moved in Minecraft Beta 1.9 or so, -// therefore all of the N/S/E/W directions here are now wrong--rotated 90 degrees from what they -// should be. For example, the positive X direction used to be South, and is called South here, -// but is now East in the game (as of Minecraft 1.0, anyway). -// I decided to leave the old direction names here, because it would be pretty easy to mess -// something up trying to go through and change everything. Apologies for the confusion! - -// this structure holds the block images used to build the map; each block image is a hexagonal shape within -// a 4Bx4B rectangle, with the unused area around it set to fully transparent -// -// example of hexagon shape for B = 3, where U represents pixels belonging to the U-facing side of the block, etc.: -// -// UU -// UUUUUU -// UUUUUUUUUU -// NUUUUUUUUUUW -// NNNUUUUUUWWW -// NNNNNUUWWWWW -// NNNNNNWWWWWW -// NNNNNNWWWWWW -// NNNNNNWWWWWW -// NNNNNWWWWW -// NNNWWW -// NW -// -// when supplying your own block images, there's nothing to stop you from going "out of bounds" and having -// non-transparent pixels outside the hexagon, but you'll just get a messed-up image, since the renderer -// uses only the hexagon to determine visibility, etc. -// -// note that translucent blocks require the most work to render, simply because you can see what's behind them; -// if every block in the world was translucent, for example, then every block would be considered visible -// ...so if you're editing the block images for special purposes like X-ray vision, the fastest results are -// obtained by making unwanted blocks fully transparent, not just translucent -// ...also, any pixels in the block images with alphas < 10 will have their alphas set to 0, and similarly -// any alphas > 245 will be set to 255; this is to prevent massive slowdown from accidental image-editing -// cock-ups, like somehow setting the transparency of the whole image to 99% instead of 100%, etc. -// -// most block images are created by resizing the relevant terrain.png images from 16x16 to 2Bx2B, then painting -// their columns onto the faces of the block image thusly (example is for B = 3 again): -// -// a f -// abcdef ab abc def -// abcdef aabbcd abcde bcdef -// abcdef ---> aabbccddef or abcdef or abcdef -// abcdef abccddeeff abcdef abcdef -// abcdef cdeeff abcdef abcdef -// abcdef ef bcdef abcde -// def abc -// f a - -struct BlockImages -{ - // this image holds all the block images, in rows of 16 (so its width is 4B*16; height depends on number of rows) - // ...the very first block image is a dummy one, fully transparent, for use with unrecognized blocks - RGBAImage img; - int rectsize; // size of block image bounding boxes - - // for every possible 12-bit block id/4-bit block data combination, this holds the offset into the image - // (unrecognized id/data values are pointed at the dummy block image) - // this doesn't handle some things like fences and double chests where the rendering doesn't depend solely - // on the blockID/blockData; for those, the renderer just has to know the proper offsets on its own - int blockOffsets[4096 * 16]; - int getOffset(uint16_t blockID, uint8_t blockData) const {return blockOffsets[blockID * 16 + blockData];} - - // check whether a block image is opaque (this is a function of the block images computed from the terrain, - // not of the actual block data; if a block image has 100% alpha everywhere, it's considered opaque) - std::vector opacity; // size is NUMBLOCKIMAGES; indexed by offset - bool isOpaque(int offset) const {return opacity[offset];} - bool isOpaque(uint16_t blockID, uint8_t blockData) const {return opacity[getOffset(blockID, blockData)];} - - // ...and the same thing for complete transparency (0% alpha everywhere) - std::vector transparency; // size is NUMBLOCKIMAGES; indexed by offset - bool isTransparent(int offset) const {return transparency[offset];} - bool isTransparent(uint16_t blockID, uint8_t blockData) const {return transparency[getOffset(blockID, blockData)];} - - // get the rectangle in img corresponding to an offset - ImageRect getRect(int offset) const {return ImageRect((offset%16)*rectsize, (offset/16)*rectsize, rectsize, rectsize);} - ImageRect getRect(uint16_t blockID, uint8_t blockData) const {return getRect(getOffset(blockID, blockData));} - - // attempt to create a BlockImages structure: look for blocks-B.png in the imgpath, where B is the block size - // parameter; failing that, look for terrain.png and construct a new blocks-B.png from it; failing that, uh, fail - bool create(int B, const std::string& imgpath); - - // set the offsets - void setOffsets(); - - // fill in the opacity and transparency members - void checkOpacityAndTransparency(int B); - - // scan the block images looking for not-quite-transparent or not-quite-opaque pixels; if they're close enough, - // push them all the way - void retouchAlphas(int B); - - // build block images from terrain.png, etc. - 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); -}; - -// block image offsets: -// -// 0 dummy/air (transparent) 32 brown mushroom 64 wheat level 2 96 cobble stairs asc S -// 1 stone 33 red mushroom 65 wheat level 1 97 cobble stairs asc N -// 2 grass 34 gold block 66 wheat level 0 98 cobble stairs asc W -// 3 dirt 35 iron block 67 farmland 99 cobble stairs asc E -// 4 cobblestone 36 double stone slab 68 UNUSED 100 wall sign facing E -// 5 planks 37 stone slab 69 UNUSED 101 wall sign facing W -// 6 sapling 38 brick 70 sign facing N/S 102 wall sign facing N -// 7 bedrock 39 TNT 71 sign facing NE/SW 103 wall sign facing S -// 8 water full/falling 40 bookshelf 72 sign facing E/W 104 UNUSED -// 9 water level 7 41 mossy cobblestone 73 sign facing SE/NW 105 UNUSED -// 10 water level 6 42 obsidian 74 wood door S side 106 UNUSED -// 11 water level 5 43 torch floor 75 wood door N side 107 UNUSED -// 12 water level 4 44 torch pointing S 76 wood door W side 108 UNUSED -// 13 water level 3 45 torch pointing N 77 wood door E side 109 UNUSED -// 14 water level 2 46 torch pointing W 78 wood door top S 110 stone pressure plate -// 15 water level 1 47 torch pointing E 79 wood door top N 111 iron door S side -// 16 lava full/falling 48 UNUSED 80 wood door top W 112 iron door N side -// 17 lava level 3 49 spawner 81 wood door top E 113 iron door W side -// 18 lava level 2 50 wood stairs asc S 82 ladder E side 114 iron door E side -// 19 lava level 1 51 wood stairs asc N 83 ladder W side 115 iron door top S -// 20 sand 52 wood stairs asc W 84 ladder N side 116 iron door top N -// 21 UNUSED 53 wood stairs asc E 85 ladder S side 117 iron door top W -// 22 gold ore 54 UNUSED 86 track EW 118 iron door top E -// 23 iron ore 55 redstone wire NSEW 87 track NS 119 wood pressure plate -// 24 coal ore 56 diamond ore 88 UNUSED 120 redstone ore -// 25 log 57 diamond block 89 UNUSED 121 red torch floor off -// 26 leaves 58 workbench 90 UNUSED 122 red torch floor on -// 27 sponge 59 wheat level 7 91 UNUSED 123 UNUSED -// 28 glass 60 wheat level 6 92 track NE corner 124 UNUSED -// 29 white wool 61 wheat level 5 93 track SE corner 125 UNUSED -// 30 yellow flower 62 wheat level 4 94 track SW corner 126 UNUSED -// 31 red rose 63 wheat level 3 95 track NW corner 127 snow -// -// 128 ice 160 fence NS 192 stone button facing W 224 dispenser N -// 129 snow block 161 fence E 193 stone button facing E 225 dispenser E/S -// 130 cactus 162 fence NE 194 wall lever facing S 226 sandstone -// 131 clay 163 fence SE 195 wall lever facing N 227 note block -// 132 reeds 164 fence NSE 196 wall lever facing W 228 UNUSED -// 133 jukebox 165 fence W 197 wall lever facing E 229 sandstone slab -// 134 fence post 166 fence NW 198 ground lever EW 230 wooden slab -// 135 pumpkin facing W 167 fence SW 199 ground lever NS 231 cobble slab -// 136 netherrack 168 fence NSW 200 track asc S 232 UNUSED -// 137 soul sand 169 fence EW 201 track asc N 233 UNUSED -// 138 glowstone 170 fence NEW 202 track asc E 234 UNUSED -// 139 portal 171 fence SEW 203 track asc W 235 UNUSED -// 140 jack-o-lantern W 172 fence NSEW 204 orange wool 236 UNUSED -// 141 red torch S on 173 UNUSED 205 magenta wool 237 UNUSED -// 142 red torch N on 174 UNUSED 206 light blue wool 238 UNUSED -// 143 red torch E on 175 UNUSED 207 yellow wool 239 UNUSED -// 144 red torch W on 176 UNUSED 208 lime wool 240 repeater on N -// 145 red torch S off 177 UNUSED 209 pink wool 241 repeater on S -// 146 red torch N off 178 water missing W 210 gray wool 242 repeater on E -// 147 red torch E off 179 water missing N 211 light gray wool 243 repeater on W -// 148 red torch W off 180 ice surface 212 cyan wool 244 repeater off N -// 149 UNUSED 181 ice missing W 213 purple wool 245 repeater off S -// 150 UNUSED 182 ice missing N 214 blue wool 246 repeater off E -// 151 UNUSED 183 furnace W 215 brown wool 247 repeater off W -// 152 UNUSED 184 furnace N 216 green wool 248 pine leaves -// 153 pumpkin facing E/S 185 furnace E/S 217 red wool 249 birch leaves -// 154 pumpkin facing N 186 lit furnace W 218 black wool 250 pine sapling -// 155 jack-o-lantern E/S 187 lit furnace N 219 pine log 251 birch sapling -// 156 jack-o-lantern N 188 lit furnace E/S 220 birch log 252 booster on EW -// 157 water surface 189 fire 221 lapis ore 253 booster on NS -// 158 fence N 190 stone button facing S 222 lapis block 254 booster on asc S -// 159 fence S 191 stone button facing N 223 dispenser W 255 booster on asc N -// -// 256 booster on asc E 288 bed foot S 320 nether fence E 352 cauldron 1/3 full -// 257 booster on asc W 289 cake 321 nether fence NE 353 cauldron 2/3 full -// 258 booster off EW 290 melon 322 nether fence SE 354 cauldron full -// 259 booster off NS 291 mycelium 323 nether fence NSE 355 iron bars NSEW -// 260 booster off asc S 292 nether brick 324 nether fence W 356 iron bars NS -// 261 booster off asc N 293 end stone 325 nether fence NW 357 iron bars NE -// 262 booster off asc E 294 stone brick 326 nether fence SW 358 iron bars NW -// 263 booster off asc W 295 mossy stone brick 327 nether fence NSW 359 iron bars SE -// 264 detector EW 296 cracked stone brick 328 nether fence EW 360 iron bars SW -// 265 detector NS 297 UNUSED 329 nether fence NEW 361 iron bars EW -// 266 detector asc S 298 UNUSED 330 nether fence SEW 362 iron bars SEW -// 267 detector asc N 299 UNUSED 331 nether fence NSEW 363 iron bars NEW -// 268 detector asc E 300 UNUSED 332 nether fence post 364 iron bars NSW -// 269 detector asc W 301 UNUSED 333 netherwart small 365 iron bars NSE -// 270 locked chest facing W 302 brick slab 334 netherwart medium 366 glass pane NSEW -// 271 locked chest facing N 303 stone brick slab 335 netherwart large 367 glass pane NS -// 272 web 304 brick stairs asc S 336 mushroom flesh 368 glass pane NE -// 273 tall grass 305 brick stairs asc N 337 red cap top only 369 glass pane NW -// 274 fern 306 brick stairs asc W 338 red cap N 370 glass pane SE -// 275 dead shrub 307 brick stairs asc E 339 red cap W 371 glass pane SW -// 276 trapdoor closed 308 stone brick stairs S 340 red cap NW 372 glass pane EW -// 277 trapdoor open W 309 stone brick stairs N 341 brown cap top only 373 glass pane SEW -// 278 trapdoor open E 310 stone brick stairs W 342 brown cap N 374 glass pane NEW -// 279 trapdoor open S 311 stone brick stairs E 343 brown cap W 375 glass pane NSW -// 280 trapdoor open N 312 nether stairs asc S 344 brown cap NW 376 glass pane NSE -// 281 bed head W 313 nether stairs asc N 345 mushroom stem 377 end portal -// 282 bed head N 314 nether stairs asc W 346 fence gate EW 378 dragon egg -// 283 bed head E 315 nether stairs asc E 347 fence gate NS 379 vines top only -// 284 bed head S 316 lily pad 348 enchantment table 380 vines N -// 285 bed foot W 317 nether fence N 349 end portal frame 381 vines S -// 286 bed foot N 318 nether fence S 350 brewing stand 382 vines NS -// 287 bed foot E 319 nether fence NS 351 cauldron empty 383 vines E -// -// 384 vines NE 416 closed sticky piston S 448 brick stairs inv W 480 ender chest facing N -// 385 vines SE 417 closed sticky piston W 449 brick stairs inv E 481 ender chest facing E/S -// 386 vines NSE 418 closed sticky piston E 450 stone brick stairs inv S 482 emerald block -// 387 vines W 419 iron bars N 451 stone brick stairs inv N 483 gravel -// 388 vines NW 420 iron bars S 452 stone brick stairs inv W 484 chest facing W -// 389 vines SW 421 iron bars E 453 stone brick stairs inv E 485 chest facing N -// 390 vines NSW 422 iron bars W 454 nether stairs inv S 486 chest facing E/S -// 391 vines EW 423 glass pane N 455 nether stairs inv N 487 double chest N facing W -// 392 vines NEW 424 glass pane S 456 nether stairs inv W 488 double chest S facing W -// 393 vines SEW 425 glass pane E 457 nether stairs inv E 489 double chest E facing N -// 394 vines NSEW 426 glass pane W 458 stone slab inv 490 double chest W facing N -// 395 stem level 0 427 jungle log 459 sandstone slab inv 491 double chest N facing E -// 396 stem level 1 428 jungle leaves 460 wooden slab inv 492 double chest S facing E -// 397 stem level 2 429 jungle sapling 461 cobblestone slab inv 493 double chest E facing S -// 398 stem level 3 430 circle stone brick 462 brick slab inv 494 double chest W facing S -// 399 stem level 4 431 hieroglyphic sandstone 463 stone brick slab inv 495 pine stairs asc S -// 400 stem level 5 432 smooth sandstone 464 pine slab 496 pine stairs asc N -// 401 stem level 6 433 redstone lamp on 465 pine slab inv 497 pine stairs asc W -// 402 stem level 7 434 redstone lamp off 466 birch slab 498 pine stairs asc E -// 403 stem pointing N 435 pine planks 467 birch slab inv 499 pine stairs inv S -// 404 stem pointing S 436 birch planks 468 jungle slab 500 pine stairs inv N -// 405 stem pointing E 437 jungle planks 469 jungle slab inv 501 pine stairs inv W -// 406 stem pointing W 438 wood stairs inv S 470 sandstone stairs asc S 502 pine stairs inv E -// 407 closed piston D 439 wood stairs inv N 471 sandstone stairs asc N 503 birch stairs asc S -// 408 closed piston U 440 wood stairs inv W 472 sandstone stairs asc W 504 birch stairs asc N -// 409 closed piston N 441 wood stairs inv E 473 sandstone stairs asc E 505 birch stairs asc W -// 410 closed piston S 442 cobble stairs inv S 474 sandstone stairs inv S 506 birch stairs asc E -// 411 closed piston W 443 cobble stairs inv N 475 sandstone stairs inv N 507 birch stairs inv S -// 412 closed piston E 444 cobble stairs inv W 476 sandstone stairs inv W 508 birch stairs inv N -// 413 closed sticky piston D 445 cobble stairs inv E 477 sandstone stairs inv E 509 birch stairs inv W -// 414 closed sticky piston U 446 brick stairs inv S 478 emerald ore 510 birch stairs inv E -// 415 closed sticky piston N 447 brick stairs inv N 479 ender chest facing W 511 jungle stairs asc S -// -// 512 jungle stairs asc N 544 tripwire NS -// 513 jungle stairs asc W 545 tripwire NE -// 514 jungle stairs asc E 546 tripwire NW -// 515 jungle stairs inv S 547 tripwire SE -// 516 jungle stairs inv N 548 tripwire SW -// 517 jungle stairs inv W 549 tripwire EW -// 518 jungle stairs inv E 550 tripwire SEW -// 519 cocoa level 0 stem N 551 tripwire NEW -// 520 cocoa level 0 stem S 552 tripwire NSW -// 521 cocoa level 0 stem E 553 tripwire NSE -// 522 cocoa level 0 stem W -// 523 cocoa level 1 stem N -// 524 cocoa level 1 stem S -// 525 cocoa level 1 stem E -// 526 cocoa level 1 stem W -// 527 cocoa level 2 stem N -// 528 cocoa level 2 stem S -// 529 cocoa level 2 stem E -// 530 cocoa level 2 stem W -// 531 log EW -// 532 log NS -// 533 pine log EW -// 534 pine log NS -// 535 birch log EW -// 536 birch log NS -// 537 jungle log EW -// 538 jungle log NS -// 539 tripwire hook S -// 540 tripwire hook N -// 541 tripwire hook W -// 542 tripwire hook E -// 543 tripwire NSEW - -#define NUMBLOCKIMAGES 554 - - - -#endif // BLOCKIMAGES_H \ No newline at end of file +// Copyright 2010-2012 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#ifndef BLOCKIMAGES_H +#define BLOCKIMAGES_H + +#include + +#include "rgba.h" + +// IMPORTANT NOTE: +// This program was written before the location of the sun moved in Minecraft Beta 1.9 or so, +// therefore all of the N/S/E/W directions here are now wrong--rotated 90 degrees from what they +// should be. For example, the positive X direction used to be South, and is called South here, +// but is now East in the game (as of Minecraft 1.0, anyway). +// I decided to leave the old direction names here, because it would be pretty easy to mess +// something up trying to go through and change everything. Apologies for the confusion! +// EDIT: (Akudeu Kie) +// Since in 1.5 terrain.png will be deprecated and new texture format will be introduced, +// programm requires some rewriting anyway, so orientation is changed accordingly (N -> W, W -> S, S -> E, E -> N) + +// this structure holds the block images used to build the map; each block image is a hexagonal shape within +// a 4Bx4B rectangle, with the unused area around it set to fully transparent +// +// example of hexagon shape for B = 3, where U represents pixels belonging to the U-facing side of the block, etc.: +// +// UU +// UUUUUU +// UUUUUUUUUU +// WUUUUUUUUUUS +// WWWUUUUUUSSS +// WWWWWUUSSSSS +// WWWWWWSSSSSS +// WWWWWWSSSSSS +// WWWWWWSSSSSS +// WWWWWSSSSS +// WWWSSS +// WS +// +// when supplying your own block images, there's nothing to stop you from going "out of bounds" and having +// non-transparent pixels outside the hexagon, but you'll just get a messed-up image, since the renderer +// uses only the hexagon to determine visibility, etc. +// +// note that translucent blocks require the most work to render, simply because you can see what's behind them; +// if every block in the world was translucent, for example, then every block would be considered visible +// ...so if you're editing the block images for special purposes like X-ray vision, the fastest results are +// obtained by making unwanted blocks fully transparent, not just translucent +// ...also, any pixels in the block images with alphas < 10 will have their alphas set to 0, and similarly +// any alphas > 245 will be set to 255; this is to prevent massive slowdown from accidental image-editing +// cock-ups, like somehow setting the transparency of the whole image to 99% instead of 100%, etc. +// +// most block images are created by resizing the relevant terrain.png images from 16x16 to 2Bx2B, then painting +// their columns onto the faces of the block image thusly (example is for B = 3 again): +// +// a f +// abcdef ab abc def +// abcdef aabbcd abcde bcdef +// abcdef ---> aabbccddef or abcdef or abcdef +// abcdef abccddeeff abcdef abcdef +// abcdef cdeeff abcdef abcdef +// abcdef ef bcdef abcde +// def abc +// f a + +struct BlockImages +{ + // this image holds all the block images, in rows of 16 (so its width is 4B*16; height depends on number of rows) + // ...the very first block image is a dummy one, fully transparent, for use with unrecognized blocks + RGBAImage img; + int rectsize; // size of block image bounding boxes + int blockversion; // version of the blocks-B.png + + std::vector< std::vector< std::string > > blockDescriptors; // vector holding block type and block texture descriptions + + // for every possible 12-bit block id/4-bit block data combination, this holds the offset into the image + // (unrecognized id/data values are pointed at the dummy block image) + // this doesn't handle some things like fences and double chests where the rendering doesn't depend solely + // on the blockID/blockData; for those, the renderer just has to know the proper offsets on its own + int blockOffsets[4096 * 16]; + int getOffset(uint16_t blockID, uint8_t blockData) const {return blockOffsets[blockID * 16 + blockData];} + + // check whether a block image is opaque (this is a function of the block images computed from the terrain, + // not of the actual block data; if a block image has 100% alpha everywhere, it's considered opaque) + std::vector opacity; // size is blockversion; indexed by offset + bool isOpaque(int offset) const {return opacity[offset];} + bool isOpaque(uint16_t blockID, uint8_t blockData) const {return opacity[getOffset(blockID, blockData)];} + + // ...and the same thing for complete transparency (0% alpha everywhere) + std::vector transparency; // size is blockversion; indexed by offset + bool isTransparent(int offset) const {return transparency[offset];} + bool isTransparent(uint16_t blockID, uint8_t blockData) const {return transparency[getOffset(blockID, blockData)];} + + // get the rectangle in img corresponding to an offset + ImageRect getRect(int offset) const {return ImageRect((offset%16)*rectsize, (offset/16)*rectsize, rectsize, rectsize);} + ImageRect getRect(uint16_t blockID, uint8_t blockData) const {return getRect(getOffset(blockID, blockData));} + + // attempt to create a BlockImages structure: look for blocks-B.png in the imgpath, where B is the block size + // parameter; failing that, look for terrain.png and construct a new blocks-B.png from it; failing that, uh, fail + bool create(int B, const std::string& imgpath); + + // create vector with block descriptors, used for offset assignment and block images construction + void setBlockDescriptors(std::ifstream& descriptorlist); + + // set the offsets + int setOffsets(); + + // fill in the opacity and transparency members + void checkOpacityAndTransparency(int B); + + // scan the block images looking for not-quite-transparent or not-quite-opaque pixels; if they're close enough, + // push them all the way + void retouchAlphas(int B); + + // build block images from terrain.png, etc. + bool construct(int B, std::ifstream& texturelist, std::ifstream& descriptorlist, const std::string& imgpath); +}; + +#endif // BLOCKIMAGES_H diff --git a/blocktextures.list b/blocktextures.list new file mode 100644 index 0000000..f783775 --- /dev/null +++ b/blocktextures.list @@ -0,0 +1,337 @@ +# blocktextures.list +# +# List of textures to include for processing +# Enter full name of the texture (should be PNG images) on separate line +# You can specify from which directory to read texture (switch current directory, as to say). To do that use the following syntax: +# $ /new/path +# $ / +# Directories should be relative to the image path, or path where renderer is being executed from. +# +# You can prefix texture with a / (slash), to apply different filters to the texture. +# !!!NOTE!!! Filters will be applied only to the copies of textures stored in memory. Original textures will be untouched. +# If you do so, you can use a couple of directive after a texture name. +# !!!NOTE!!! Directive are executed in order of appearance. EXPAND, CHEST and LCHEST create new textures. If you want to apply other filters, +# they (filters) should go -AFTER- EXPAND directive, and -BEFORE- CHEST and LCHEST directives. +# !!!NOTE!!! CHEST and LCHEST are similar, but should not be used together. +# Here is a list of available directives and their syntax: +# - RENAME newtexturename +# Assigns different name to a texture. You can, for example, read the same texture twice, but second one a different name and apply +# few more other filters. Renamed texture then can be used in block descriptor list. +# =Example= +# / leaves.png RENAME leaves_birch.png +# - DARKEN darkenRed darkenGreen darkenBlue +# Darkens each separate color channel of the texture based on given amount. Amount is specified as a decimal number[0-1]. +# =Example= +# / leaves.png DARKEN 0.3 1.0 0.1 +# - OFFSET offsetX offsetY +# Offset texture by specified offset. Offset is specified in pixels (x out 16). +# If offset exceeds the width or height of the texture, modulus of an offset will be taken. +# Offset texture is not tiled. If you want to offset texture, so it leaves it's size area, just use blank texture (/). +# =Example= +# / stem_straight.png OFFSET 0 14 +# - OFFSETTILE offsetX offsetY +# Same as OFFSET, but texture is tiled - it repeats it's pattern from opposite side where it is being offset. +# - EXPAND expandX expandY +# Expands texture by a specified amount by resizing it. Using this directive results in new texture. Use this directive before other directives. +# =Example= +# / cake_top.png EXPAND 1 1 +# - CROP top right bottom left +# Crops texture from each side by a specified amount. Amount is specified in pixels (x out 16). +# =Example= +# / stem_straight.png CROP 14 0 0 0 +# - FLIPX +# Flips the texture along the x axis. The result is mirrored texture. +# =Example= +# / stem_straight.png FLIPX +# - CHEST +# - LCHEST +# CHEST and LCHEST are directives for generating special tile, in this case - specific tiles for small and large chests. +# Since original textures are chest model textures, they don't fit in tile size (e.g. 16x16). Renderer will create required tiles for chest blocks, +# and then will use them to build block tiles. +# To use textures with this directive, just prefix the original name with / (slash) in block descriptor list. +# +$ /textures/blocks +activatorRail.png +activatorRail_powered.png +anvil_base.png +anvil_top.png +anvil_top_damaged_1.png +anvil_top_damaged_2.png +beacon.png +bed_feet_end.png +bed_feet_side.png +bed_feet_top.png +bed_head_end.png +bed_head_side.png +bed_head_top.png +bedrock.png +blockCoal.png +blockDiamond.png +blockEmerald.png +blockGold.png +blockIron.png +blockLapis.png +blockRedstone.png +bookshelf.png +brewingStand.png +brewingStand_base.png +brick.png +/ cactus_bottom.png EXPAND 1 1 +/ cactus_side.png EXPAND 1 1 +/ cactus_top.png EXPAND 1 1 +/ cake_bottom.png EXPAND 1 1 +/ cake_inner.png EXPAND 1 1 +/ cake_side.png EXPAND 1 1 +/ cake_top.png EXPAND 1 1 +carrots_0.png +carrots_1.png +carrots_2.png +carrots_3.png +cauldron_bottom.png +cauldron_inner.png +cauldron_side.png +cauldron_top.png +clay.png +clayHardened.png +clayHardenedStained_0.png +clayHardenedStained_1.png +clayHardenedStained_2.png +clayHardenedStained_3.png +clayHardenedStained_4.png +clayHardenedStained_5.png +clayHardenedStained_6.png +clayHardenedStained_7.png +clayHardenedStained_8.png +clayHardenedStained_9.png +clayHardenedStained_10.png +clayHardenedStained_11.png +clayHardenedStained_12.png +clayHardenedStained_13.png +clayHardenedStained_14.png +clayHardenedStained_15.png +cloth_0.png +cloth_1.png +cloth_10.png +cloth_11.png +cloth_12.png +cloth_13.png +cloth_14.png +cloth_15.png +cloth_2.png +cloth_3.png +cloth_4.png +cloth_5.png +cloth_6.png +cloth_7.png +cloth_8.png +cloth_9.png +cocoa_0.png +cocoa_1.png +cocoa_2.png +commandBlock.png +comparator.png +comparator_lit.png +crops_0.png +crops_1.png +crops_2.png +crops_3.png +crops_4.png +crops_5.png +crops_6.png +crops_7.png +daylightDetector_side.png +daylightDetector_top.png +deadbush.png +destroy_0.png +destroy_1.png +destroy_2.png +destroy_3.png +destroy_4.png +destroy_5.png +destroy_6.png +destroy_7.png +destroy_8.png +destroy_9.png +detectorRail.png +dirt.png +dispenser_front.png +doorIron_lower.png +doorIron_upper.png +doorWood_lower.png +doorWood_upper.png +dragonEgg.png +dropper_front.png +enchantment_bottom.png +enchantment_side.png +enchantment_top.png +endframe_eye.png +endframe_side.png +endframe_top.png +farmland_dry.png +farmland_wet.png +fenceIron.png +/ fern.png DARKEN 0.6 0.95 0.3 +fire_0.png +fire_1.png +flower.png +flowerPot.png +furnace_front.png +furnace_front_lit.png +furnace_side.png +furnace_top.png +glass.png +goldenRail.png +goldenRail_powered.png +grass_side.png +grass_side_overlay.png +/ grass_top.png DARKEN 0.6 0.95 0.3 +gravel.png +hayBlock.png +hayBlock_top.png +hellrock.png +hellsand.png +hopper.png +hopper_inside.png +hopper_top.png +ice.png +itemframe_back.png +jukebox_top.png +ladder.png +lava.png +/ leaves.png DARKEN 0.3 1.0 0.1 +#/ leaves_opaque.png DARKEN 0.3 1.0 0.1 +/ leaves.png RENAME leaves_birch.png DARKEN 0.55 0.9 0.1 +/ leaves_jungle.png DARKEN 0.35, 1.0, 0.05 +#/ leaves_jungle_opaque.png DARKEN 0.35, 1.0, 0.05 +/ leaves_spruce.png DARKEN 0.3 1.0 0.45 +#/ leaves_spruce_opaque.png DARKEN 0.3 1.0 0.45 +lever.png +lightgem.png +melon_side.png +melon_top.png +mobSpawner.png +mushroom_brown.png +mushroom_inside.png +mushroom_red.png +mushroom_skin_brown.png +mushroom_skin_red.png +mushroom_skin_stem.png +musicBlock.png +mycel_side.png +mycel_top.png +netherBrick.png +netherStalk_0.png +netherStalk_1.png +netherStalk_2.png +netherquartz.png +obsidian.png +oreCoal.png +oreDiamond.png +oreEmerald.png +oreGold.png +oreIron.png +oreLapis.png +oreRedstone.png +piston_bottom.png +piston_inner_top.png +piston_side.png +piston_top.png +piston_top_sticky.png +portal.png +potatoes_0.png +potatoes_1.png +potatoes_2.png +potatoes_3.png +pumpkin_face.png +pumpkin_jack.png +pumpkin_side.png +pumpkin_top.png +quartzblock_bottom.png +quartzblock_chiseled.png +quartzblock_chiseled_top.png +quartzblock_lines.png +quartzblock_lines_top.png +quartzblock_side.png +quartzblock_top.png +rail.png +rail_turn.png +/ redstoneDust_cross.png DARKEN 0.9 0.1 0.1 +#redstoneDust_cross_overlay.png +/ redstoneDust_line.png DARKEN 0.9 0.1 0.1 +#redstoneDust_line_overlay.png +redstoneLight.png +redstoneLight_lit.png +redtorch.png +redtorch_lit.png +reeds.png +repeater.png +repeater_lit.png +results.png +rose.png +sand.png +sandstone_bottom.png +sandstone_carved.png +sandstone_side.png +sandstone_smooth.png +sandstone_top.png +sapling.png +sapling_birch.png +sapling_jungle.png +sapling_spruce.png +snow.png +snow_side.png +sponge.png +/ stem_bent.png RENAME /stem_bent.png_0 DARKEN 0.75 0.6 0.3 +/ stem_bent.png RENAME /stem_bent.png_1 DARKEN 0.75 0.6 0.3 FLIPX +/ stem_straight.png RENAME /stem_straight.png_0 DARKEN 0.45 0.95 0.4 OFFSET 0 14 +/ stem_straight.png RENAME /stem_straight.png_1 DARKEN 0.45 0.95 0.4 OFFSET 0 12 +/ stem_straight.png RENAME /stem_straight.png_2 DARKEN 0.45 0.95 0.4 OFFSET 0 10 +/ stem_straight.png RENAME /stem_straight.png_3 DARKEN 0.45 0.95 0.4 OFFSET 0 8 +/ stem_straight.png RENAME /stem_straight.png_4 DARKEN 0.45 0.95 0.4 OFFSET 0 6 +/ stem_straight.png RENAME /stem_straight.png_5 DARKEN 0.45 0.95 0.4 OFFSET 0 4 +/ stem_straight.png RENAME /stem_straight.png_6 DARKEN 0.45 0.95 0.4 OFFSET 0 2 +/ stem_straight.png RENAME /stem_straight.png_7 DARKEN 0.75 0.6 0.3 +stone.png +stoneMoss.png +stonebrick.png +stonebricksmooth.png +stonebricksmooth_carved.png +stonebricksmooth_cracked.png +stonebricksmooth_mossy.png +stoneslab_side.png +stoneslab_top.png +/ tallgrass.png DARKEN 0.6 0.95 0.3 +thinglass_top.png +tnt_bottom.png +tnt_side.png +tnt_top.png +torch.png +trapdoor.png +tree_birch.png +tree_jungle.png +tree_side.png +tree_spruce.png +tree_top.png +/ tripWire.png OFFSET 0 6 +tripWireSource.png +/ vine.png DARKEN 0.35 1.0 0.15 +water.png +/ waterlily.png DARKEN 0.3 0.95 0.3 +web.png +whiteStone.png +wood.png +wood_birch.png +wood_jungle.png +wood_spruce.png +workbench_front.png +workbench_side.png +workbench_top.png +$ /item +/ chest.png CHEST +/ largechest.png LCHEST +/ enderchest.png CHEST +$ /item/chests +/ trap_small.png CHEST +/ trap_large.png LCHEST +$ / +endportal.png \ No newline at end of file diff --git a/chunk.cpp b/chunk.cpp old mode 100755 new mode 100644 index a9288c2..549b786 --- a/chunk.cpp +++ b/chunk.cpp @@ -1,411 +1,457 @@ -// Copyright 2010-2012 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#include -#include -#include -#include -#include - -#include "chunk.h" -#include "utils.h" - -using namespace std; - - - -//--------------------------------------------------------------------------------------------------- - - -bool ChunkData::loadFromOldFile(const vector& filebuf) -{ - anvil = false; - // the hell with parsing this whole godforsaken NBT format; just look for the arrays we need - uint8_t idsTag[13] = {7, 0, 6, 'B', 'l', 'o', 'c', 'k', 's', 0, 0, 128, 0}; - uint8_t dataTag[11] = {7, 0, 4, 'D', 'a', 't', 'a', 0, 0, 64, 0}; - bool foundIDs = false, foundData = false; - for (vector::const_iterator it = filebuf.begin(); it != filebuf.end(); it++) - { - if (*it != 7) - continue; - if (!foundIDs && it + 13 + 32768 <= filebuf.end() && equal(it, it + 13, idsTag)) - { - copy(it + 13, it + 13 + 32768, blockIDs); - it += 13 + 32768 - 1; // one less because of the loop we're in - foundIDs = true; - } - else if (!foundData && it + 11 + 16384 <= filebuf.end() && equal(it, it + 11, dataTag)) - { - copy(it + 11, it + 11 + 16384, blockData); - it += 11 + 16384 - 1; // one less because of the loop we're in - foundData = true; - } - if (foundIDs && foundData) - return true; - } - return false; -} - - -//--------------------------------------------------------------------------------------------------- - - -// quasi-NBT-parsing stuff for Anvil format: doesn't actually bother trying to read the whole thing, -// just skips through the data looking for what we're interested in -#define TAG_END 0 -#define TAG_BYTE 1 -#define TAG_SHORT 2 -#define TAG_INT 3 -#define TAG_LONG 4 -#define TAG_FLOAT 5 -#define TAG_DOUBLE 6 -#define TAG_BYTE_ARRAY 7 -#define TAG_STRING 8 -#define TAG_LIST 9 -#define TAG_COMPOUND 10 -#define TAG_INT_ARRAY 11 - -// although tag names are UTF8, we'll just pretend they're ASCII--we don't really care about how the -// actual string data breaks down into characters, as long as we know where the end of the string is -void parseTypeAndName(const uint8_t*& ptr, uint8_t& type, string& name) -{ - type = *ptr; - ptr++; - if (type != TAG_END) - { - uint16_t len = fromBigEndian(*((uint16_t*)ptr)); - name.resize(len); - copy(ptr + 2, ptr + 2 + len, name.begin()); - ptr += 2 + len; - } -} - -// structure for locating the block data for a 16x16x16 section--the compound tags on the "Sections" list will pass -// this down to their immediate children, so they can fill in pointers to their payloads if appropriate -// ...after the whole structure is parsed, the block data will be copied into the ChunkData -// (note that we can't read the block data immediately upon finding it, because we have to know the Y value -// for the section first, and the tags may appear in any order) -struct chunkSection -{ - int y; // or -1 for "not found yet" - const uint8_t *blockIDs; // pointer into the file buffer, or NULL for "not found yet" - const uint8_t *blockData; // pointer into the file buffer, or NULL for "not found yet" - const uint8_t *blockAdd; // pointer into the file buffer, or NULL for "not found" (this one may not be present at all) - - chunkSection() : y(-1), blockIDs(NULL), blockData(NULL), blockAdd(NULL) {} - bool complete() const {return y >= 0 && y < 16 && blockIDs != NULL && blockData != NULL;} - - void extract(ChunkData& chunkdata) const - { - copy(blockIDs, blockIDs + 4096, chunkdata.blockIDs + (y * 4096)); - copy(blockData, blockData + 2048, chunkdata.blockData + (y * 2048)); - if (blockAdd != NULL) - copy(blockAdd, blockAdd + 2048, chunkdata.blockAdd + (y * 2048)); - } -}; - -bool isSection(const vector& names) -{ - return names.size() == 4 && - names[3] == "" && - names[2] == "Sections" && - names[1] == "Level" && - names[0] == ""; -} - -// if section != NULL, then the immediate parent of this tag is one of the compound tags in the "Sections" -// list, so the block data tags will fill in their locations -bool parsePayload(const uint8_t*& ptr, uint8_t type, vector& names, chunkSection *section, vector& completedSections) -{ - switch (type) - { - case TAG_END: - { - return true; - } - case TAG_BYTE: - { - if (section != NULL && names.back() == "Y") - section->y = *ptr; - ptr++; - return true; - } - case TAG_SHORT: - { - ptr += 2; - return true; - } - case TAG_INT: - case TAG_FLOAT: - { - ptr += 4; - return true; - } - case TAG_LONG: - case TAG_DOUBLE: - { - ptr += 8; - return true; - } - case TAG_BYTE_ARRAY: - { - uint32_t len = fromBigEndian(*((uint32_t*)ptr)); - ptr += 4; - if (section != NULL) - { - if (names.back() == "Blocks" && len == 4096) - section->blockIDs = ptr; - else if (names.back() == "Data" && len == 2048) - section->blockData = ptr; - else if (names.back() == "Add" && len == 2048) - section->blockAdd = ptr; - } - ptr += len; - return true; - } - case TAG_INT_ARRAY: - { - uint32_t len = fromBigEndian(*((uint32_t*)ptr)); - ptr += 4 + len*4; - return true; - } - case TAG_STRING: - { - uint16_t len = fromBigEndian(*((uint16_t*)ptr)); - ptr += 2 + len; - return true; - } - case TAG_LIST: - { - uint8_t listtype = *ptr; - ptr++; - uint32_t len = fromBigEndian(*((uint32_t*)ptr)); - ptr += 4; - stackPusher sp(names, ""); - for (uint32_t i = 0; i < len; i++) - if (!parsePayload(ptr, listtype, names, NULL, completedSections)) - return false; - return true; - } - case TAG_COMPOUND: - { - chunkSection section; - chunkSection *sectionPtr = isSection(names) ? §ion : NULL; - - uint8_t nexttype; - string nextname; - parseTypeAndName(ptr, nexttype, nextname); - while (nexttype != TAG_END) - { - stackPusher sp(names, nextname); - if (!parsePayload(ptr, nexttype, names, sectionPtr, completedSections)) - return false; - parseTypeAndName(ptr, nexttype, nextname); - } - - if (sectionPtr != NULL) - { - if (section.complete()) - completedSections.push_back(section); - else - { - cerr << "incomplete chunk section!" << endl; - return false; - } - } - - return true; - } - default: - { - // unknown tag--since we have no idea how large it is, we must abort - cerr << "unknown NBT tag: type " << type << endl; - return false; - } - } - return false; // shouldn't be able to reach here -} - -bool ChunkData::loadFromAnvilFile(const vector& filebuf) -{ - anvil = true; - fill(blockIDs, blockIDs + 65536, 0); - fill(blockAdd, blockAdd + 32768, 0); - fill(blockData, blockData + 32768, 0); - - const uint8_t *ptr = &(filebuf[0]); - uint8_t type; - string name; - parseTypeAndName(ptr, type, name); - if (type != TAG_COMPOUND || !name.empty()) - { - cerr << "unrecognized NBT chunk file: top tag has type " << (int)type << " and name " << name << endl; - return false; - } - - vector names(1, name); - vector completedSections; - if (!parsePayload(ptr, type, names, NULL, completedSections)) - return false; - - for (vector::const_iterator it = completedSections.begin(); it != completedSections.end(); it++) - it->extract(*this); - - return true; -} - - -//--------------------------------------------------------------------------------------------------- - - -ChunkCacheStats& ChunkCacheStats::operator+=(const ChunkCacheStats& ccs) -{ - hits += ccs.hits; - misses += ccs.misses; - read += ccs.read; - skipped += ccs.skipped; - missing += ccs.missing; - reqmissing += ccs.reqmissing; - corrupt += ccs.corrupt; - return *this; -} - -ChunkData* ChunkCache::getData(const PosChunkIdx& ci) -{ - int e = getEntryNum(ci); - int state = chunktable.getDiskState(ci); - - if (state == ChunkSet::CHUNK_UNKNOWN) - stats.misses++; - else - stats.hits++; - - // if we've already tried and failed to read the chunk, don't try again - if (state == ChunkSet::CHUNK_CORRUPTED || state == ChunkSet::CHUNK_MISSING) - return &blankdata; - - // if the chunk is in the cache, return it - if (state == ChunkSet::CHUNK_CACHED) - { - if (entries[e].ci != ci) - { - cerr << "grievous chunk cache failure!" << endl; - cerr << "[" << ci.x << "," << ci.z << "] [" << entries[e].ci.x << "," << entries[e].ci.z << "]" << endl; - exit(-1); - } - return &entries[e].data; - } - - // if this is a full render and the chunk is not required, we already know it doesn't exist - bool req = chunktable.isRequired(ci); - if (fullrender && !req) - { - stats.skipped++; - chunktable.setDiskState(ci, ChunkSet::CHUNK_MISSING); - return &blankdata; - } - - // okay, we actually have to read the chunk from disk - if (regionformat) - readFromRegionCache(ci); - else - readChunkFile(ci); - - // check whether the read succeeded; return the data if so - state = chunktable.getDiskState(ci); - if (state == ChunkSet::CHUNK_CORRUPTED) - { - stats.corrupt++; - return &blankdata; - } - if (state == ChunkSet::CHUNK_MISSING) - { - if (req) - stats.reqmissing++; - else - stats.missing++; - return &blankdata; - } - if (state != ChunkSet::CHUNK_CACHED || entries[e].ci != ci) - { - cerr << "grievous chunk cache failure!" << endl; - cerr << "[" << ci.x << "," << ci.z << "] [" << entries[e].ci.x << "," << entries[e].ci.z << "]" << endl; - exit(-1); - } - stats.read++; - return &entries[e].data; -} - -void ChunkCache::readChunkFile(const PosChunkIdx& ci) -{ - // read the gzip file from disk, if it's there - string filename = inputpath + "/" + ci.toChunkIdx().toFilePath(); - int result = readGzFile(filename, readbuf); - if (result == -1) - { - chunktable.setDiskState(ci, ChunkSet::CHUNK_MISSING); - return; - } - if (result == -2) - { - chunktable.setDiskState(ci, ChunkSet::CHUNK_CORRUPTED); - return; - } - - // gzip read was successful; extract the data we need from the chunk - // and put it in the cache - parseReadBuf(ci, false); -} - -void ChunkCache::readFromRegionCache(const PosChunkIdx& ci) -{ - // try to decompress the chunk data - bool anvil; - int result = regioncache.getDecompressedChunk(ci, readbuf, anvil); - if (result == -1) - { - chunktable.setDiskState(ci, ChunkSet::CHUNK_MISSING); - return; - } - if (result == -2) - { - chunktable.setDiskState(ci, ChunkSet::CHUNK_CORRUPTED); - return; - } - - // decompression was successful; extract the data we need from the chunk - // and put it in the cache - parseReadBuf(ci, anvil); -} - -void ChunkCache::parseReadBuf(const PosChunkIdx& ci, bool anvil) -{ - // evict current tenant of chunk's cache slot - int e = getEntryNum(ci); - if (entries[e].ci.valid()) - chunktable.setDiskState(entries[e].ci, ChunkSet::CHUNK_UNKNOWN); - entries[e].ci = PosChunkIdx(-1,-1); - // ...and put this chunk's data into the slot, assuming the data can actually be parsed - bool result = anvil ? entries[e].data.loadFromAnvilFile(readbuf) : entries[e].data.loadFromOldFile(readbuf); - if (result) - { - entries[e].ci = ci; - chunktable.setDiskState(ci, ChunkSet::CHUNK_CACHED); - } - else - chunktable.setDiskState(ci, ChunkSet::CHUNK_CORRUPTED); -} +// Copyright 2010-2012 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#include +#include +#include +#include +#include + +#include "chunk.h" +#include "utils.h" + +using namespace std; + + + +//--------------------------------------------------------------------------------------------------- + + +bool ChunkData::loadFromOldFile(const vector& filebuf) +{ + anvil = false; + // the hell with parsing this whole godforsaken NBT format; just look for the arrays we need + uint8_t idsTag[13] = {7, 0, 6, 'B', 'l', 'o', 'c', 'k', 's', 0, 0, 128, 0}; + uint8_t dataTag[11] = {7, 0, 4, 'D', 'a', 't', 'a', 0, 0, 64, 0}; + bool foundIDs = false, foundData = false; + + fill(blockIDs, blockIDs + 65536, 0); + fill(blockData, blockData + 32768, 0); + + for (vector::const_iterator it = filebuf.begin(); it != filebuf.end(); it++) + { + if (*it != 7) + continue; + if (!foundIDs && it + 13 + 32768 <= filebuf.end() && equal(it, it + 13, idsTag)) + { + for (unsigned x = 0; x < 16; ++x) + { + for (unsigned z = 0; z < 16; ++z) + { + for (unsigned y = 0; y < 128; ++y) + { + unsigned oldloc = (x * 16 + z) * 128 + y; + unsigned newloc = (y * 16 + z) * 16 + x; + blockIDs[newloc] = *(it + 13 + oldloc); + } + } + } + it += 13 + 32768 - 1; // one less because of the loop we're in + foundIDs = true; + } + else if (!foundData && it + 11 + 16384 <= filebuf.end() && equal(it, it + 11, dataTag)) + { + for (unsigned x = 0; x < 16; ++x) + { + for (unsigned z = 0; z < 16; ++z) + { + for (unsigned y = 0; y < 128; ++y) + { + unsigned oldloc = (x * 16 + z) * 128 + y; + uint8_t data = *(it + 11 + oldloc / 2); + if (oldloc % 2 == 0) + data &= 0xf; + else + data = (data & 0xf0) >> 4; + + unsigned newloc = (y * 16 + z) * 16 + x; + uint8_t& writeloc = blockData[newloc / 2]; + if (newloc % 2 == 0) + writeloc = (writeloc & 0xf0) | data; + else + writeloc = (writeloc & 0xf) | (data << 4); + } + } + } + it += 11 + 16384 - 1; // one less because of the loop we're in + foundData = true; + } + if (foundIDs && foundData) + return true; + } + return false; +} + + +//--------------------------------------------------------------------------------------------------- + + +// quasi-NBT-parsing stuff for Anvil format: doesn't actually bother trying to read the whole thing, +// just skips through the data looking for what we're interested in +#define TAG_END 0 +#define TAG_BYTE 1 +#define TAG_SHORT 2 +#define TAG_INT 3 +#define TAG_LONG 4 +#define TAG_FLOAT 5 +#define TAG_DOUBLE 6 +#define TAG_BYTE_ARRAY 7 +#define TAG_STRING 8 +#define TAG_LIST 9 +#define TAG_COMPOUND 10 +#define TAG_INT_ARRAY 11 + +// although tag names are UTF8, we'll just pretend they're ASCII--we don't really care about how the +// actual string data breaks down into characters, as long as we know where the end of the string is +void parseTypeAndName(const uint8_t*& ptr, uint8_t& type, string& name) +{ + type = *ptr; + ptr++; + if (type != TAG_END) + { + uint16_t len = fromBigEndian(*((uint16_t*)ptr)); + name.resize(len); + copy(ptr + 2, ptr + 2 + len, name.begin()); + ptr += 2 + len; + } +} + +// structure for locating the block data for a 16x16x16 section--the compound tags on the "Sections" list will pass +// this down to their immediate children, so they can fill in pointers to their payloads if appropriate +// ...after the whole structure is parsed, the block data will be copied into the ChunkData +// (note that we can't read the block data immediately upon finding it, because we have to know the Y value +// for the section first, and the tags may appear in any order) +struct chunkSection +{ + int y; // or -1 for "not found yet" + const uint8_t *blockIDs; // pointer into the file buffer, or NULL for "not found yet" + const uint8_t *blockData; // pointer into the file buffer, or NULL for "not found yet" + const uint8_t *blockAdd; // pointer into the file buffer, or NULL for "not found" (this one may not be present at all) + + chunkSection() : y(-1), blockIDs(NULL), blockData(NULL), blockAdd(NULL) {} + bool complete() const {return y >= 0 && y < 16 && blockIDs != NULL && blockData != NULL;} + + void extract(ChunkData& chunkdata) const + { + uint16_t* destination = chunkdata.blockIDs + (y * 4096); + for (unsigned i = 0; i < 4096; ++i) + { + uint16_t val = blockIDs[i]; + if (blockAdd) + { + if (i % 2 == 0) + val |= (blockAdd[i / 2] & 0xf) << 8; + else + val |= (blockAdd[i / 2] & 0xf0) << 4; + } + destination[i] = val; + } + copy(blockData, blockData + 2048, chunkdata.blockData + (y * 2048)); + } +}; + +bool isSection(const vector& names) +{ + return names.size() == 4 && + names[3] == "" && + names[2] == "Sections" && + names[1] == "Level" && + names[0] == ""; +} + +// if section != NULL, then the immediate parent of this tag is one of the compound tags in the "Sections" +// list, so the block data tags will fill in their locations +bool parsePayload(const uint8_t*& ptr, uint8_t type, vector& names, chunkSection *section, vector& completedSections) +{ + switch (type) + { + case TAG_END: + { + return true; + } + case TAG_BYTE: + { + if (section != NULL && names.back() == "Y") + section->y = *ptr; + ptr++; + return true; + } + case TAG_SHORT: + { + ptr += 2; + return true; + } + case TAG_INT: + case TAG_FLOAT: + { + ptr += 4; + return true; + } + case TAG_LONG: + case TAG_DOUBLE: + { + ptr += 8; + return true; + } + case TAG_BYTE_ARRAY: + { + uint32_t len = fromBigEndian(*((uint32_t*)ptr)); + ptr += 4; + if (section != NULL) + { + if (names.back() == "Blocks" && len == 4096) + section->blockIDs = ptr; + else if (names.back() == "Data" && len == 2048) + section->blockData = ptr; + else if (names.back() == "Add" && len == 2048) + section->blockAdd = ptr; + } + ptr += len; + return true; + } + case TAG_INT_ARRAY: + { + uint32_t len = fromBigEndian(*((uint32_t*)ptr)); + ptr += 4 + len*4; + return true; + } + case TAG_STRING: + { + uint16_t len = fromBigEndian(*((uint16_t*)ptr)); + ptr += 2 + len; + return true; + } + case TAG_LIST: + { + uint8_t listtype = *ptr; + ptr++; + uint32_t len = fromBigEndian(*((uint32_t*)ptr)); + ptr += 4; + stackPusher sp(names, ""); + for (uint32_t i = 0; i < len; i++) + if (!parsePayload(ptr, listtype, names, NULL, completedSections)) + return false; + return true; + } + case TAG_COMPOUND: + { + chunkSection section; + chunkSection *sectionPtr = isSection(names) ? §ion : NULL; + + uint8_t nexttype; + string nextname; + parseTypeAndName(ptr, nexttype, nextname); + while (nexttype != TAG_END) + { + stackPusher sp(names, nextname); + if (!parsePayload(ptr, nexttype, names, sectionPtr, completedSections)) + return false; + parseTypeAndName(ptr, nexttype, nextname); + } + + if (sectionPtr != NULL) + { + if (section.complete()) + completedSections.push_back(section); + else + { + cerr << "incomplete chunk section!" << endl; + return false; + } + } + + return true; + } + default: + { + // unknown tag--since we have no idea how large it is, we must abort + cerr << "unknown NBT tag: type " << type << endl; + return false; + } + } + return false; // shouldn't be able to reach here +} + +bool ChunkData::loadFromAnvilFile(const vector& filebuf) +{ + anvil = true; + fill(blockIDs, blockIDs + 65536, 0); + fill(blockData, blockData + 32768, 0); + + const uint8_t *ptr = &(filebuf[0]); + uint8_t type; + string name; + parseTypeAndName(ptr, type, name); + if (type != TAG_COMPOUND || !name.empty()) + { + cerr << "unrecognized NBT chunk file: top tag has type " << (int)type << " and name " << name << endl; + return false; + } + + vector names(1, name); + vector completedSections; + if (!parsePayload(ptr, type, names, NULL, completedSections)) + return false; + + for (vector::const_iterator it = completedSections.begin(); it != completedSections.end(); it++) + it->extract(*this); + + return true; +} + + +//--------------------------------------------------------------------------------------------------- + + +ChunkCacheStats& ChunkCacheStats::operator+=(const ChunkCacheStats& ccs) +{ + hits += ccs.hits; + misses += ccs.misses; + read += ccs.read; + skipped += ccs.skipped; + missing += ccs.missing; + reqmissing += ccs.reqmissing; + corrupt += ccs.corrupt; + return *this; +} + +ChunkData* ChunkCache::getData(const PosChunkIdx& ci) +{ + int state = chunktable.getDiskState(ci); + if (state == ChunkSet::CHUNK_UNKNOWN) + stats.misses++; + else + stats.hits++; + + // if the chunk is in the cache, return it + if (state == ChunkSet::CHUNK_CACHED) + { + int e = getEntryNum(ci); + ChunkCacheEntry& entry = entries[e]; + if (entry.ci != ci) + { + cerr << "grievous chunk cache failure!" << endl; + cerr << "[" << ci.x << "," << ci.z << "] [" << entries[e].ci.x << "," << entries[e].ci.z << "]" << endl; + exit(-1); + } + return &entry.data; + } + + // if we've already tried and failed to read the chunk, don't try again + if (state == ChunkSet::CHUNK_MISSING || state == ChunkSet::CHUNK_CORRUPTED) + return &blankdata; + + // if this is a full render and the chunk is not required, we already know it doesn't exist + bool req = chunktable.isRequired(ci); + if (fullrender && !req) + { + stats.skipped++; + chunktable.setDiskState(ci, ChunkSet::CHUNK_MISSING); + return &blankdata; + } + + // okay, we actually have to read the chunk from disk + if (regionformat) + readFromRegionCache(ci); + else + readChunkFile(ci); + + // check whether the read succeeded; return the data if so + state = chunktable.getDiskState(ci); + if (state == ChunkSet::CHUNK_CORRUPTED) + { + stats.corrupt++; + return &blankdata; + } + if (state == ChunkSet::CHUNK_MISSING) + { + if (req) + stats.reqmissing++; + else + stats.missing++; + return &blankdata; + } + int e = getEntryNum(ci); + if (state != ChunkSet::CHUNK_CACHED || entries[e].ci != ci) + { + cerr << "grievous chunk cache failure!" << endl; + cerr << "[" << ci.x << "," << ci.z << "] [" << entries[e].ci.x << "," << entries[e].ci.z << "]" << endl; + exit(-1); + } + stats.read++; + return &entries[e].data; +} + +void ChunkCache::readChunkFile(const PosChunkIdx& ci) +{ + // read the gzip file from disk, if it's there + string filename = inputpath + "/" + ci.toChunkIdx().toFilePath(); + int result = readGzFile(filename, readbuf); + if (result == -1) + { + chunktable.setDiskState(ci, ChunkSet::CHUNK_MISSING); + return; + } + if (result == -2) + { + chunktable.setDiskState(ci, ChunkSet::CHUNK_CORRUPTED); + return; + } + + // gzip read was successful; extract the data we need from the chunk + // and put it in the cache + parseReadBuf(ci, false); +} + +void ChunkCache::readFromRegionCache(const PosChunkIdx& ci) +{ + // try to decompress the chunk data + bool anvil; + int result = regioncache.getDecompressedChunk(ci, readbuf, anvil); + if (result == -1) + { + chunktable.setDiskState(ci, ChunkSet::CHUNK_MISSING); + return; + } + if (result == -2) + { + chunktable.setDiskState(ci, ChunkSet::CHUNK_CORRUPTED); + return; + } + + // decompression was successful; extract the data we need from the chunk + // and put it in the cache + parseReadBuf(ci, anvil); +} + +void ChunkCache::parseReadBuf(const PosChunkIdx& ci, bool anvil) +{ + // evict current tenant of chunk's cache slot + int e = getEntryNum(ci); + if (entries[e].ci.valid()) + chunktable.setDiskState(entries[e].ci, ChunkSet::CHUNK_UNKNOWN); + entries[e].ci = PosChunkIdx(-1,-1); + // ...and put this chunk's data into the slot, assuming the data can actually be parsed + bool result = anvil ? entries[e].data.loadFromAnvilFile(readbuf) : entries[e].data.loadFromOldFile(readbuf); + if (result) + { + entries[e].ci = ci; + chunktable.setDiskState(ci, ChunkSet::CHUNK_CACHED); + } + else + chunktable.setDiskState(ci, ChunkSet::CHUNK_CORRUPTED); +} diff --git a/chunk.h b/chunk.h old mode 100755 new mode 100644 index 4c4764c..32668e2 --- a/chunk.h +++ b/chunk.h @@ -1,159 +1,149 @@ -// Copyright 2010-2012 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#ifndef CHUNK_H -#define CHUNK_H - -#include -#include -#include -#include - -#include "map.h" -#include "tables.h" -#include "region.h" - - - -// offset into a chunk of a block -struct BlockOffset -{ - int64_t x, z, y; - BlockOffset(const BlockIdx& bi) - { - ChunkIdx ci = bi.getChunkIdx(); - x = bi.x - ci.x*16; - z = bi.z - ci.z*16; - y = bi.y; - } -}; - -struct ChunkData -{ - uint8_t blockIDs[65536]; // one byte per block (only half of this space used for old-style chunks) - uint8_t blockAdd[32768]; // only in Anvil--extra bits for block ID (4 bits per block) - uint8_t blockData[32768]; // 4 bits per block (only half of this space used for old-style chunks) - bool anvil; // whether this data came from an Anvil chunk or an old-style one - - // these guys assume that the BlockIdx actually points to this chunk - // (so they only look at the lower bits) - uint16_t id(const BlockOffset& bo) const - { - if (!anvil) - return (bo.y > 127) ? 0 : blockIDs[(bo.x * 16 + bo.z) * 128 + bo.y]; - int i = (bo.y * 16 + bo.z) * 16 + bo.x; - if ((i % 2) == 0) - return ((blockAdd[i/2] & 0xf) << 8) | blockIDs[i]; - return ((blockAdd[i/2] & 0xf0) << 4) | blockIDs[i]; - } - uint8_t data(const BlockOffset& bo) const - { - int i; - if (!anvil) - { - if (bo.y > 127) - return 0; - i = (bo.x * 16 + bo.z) * 128 + bo.y; - } - else - i = (bo.y * 16 + bo.z) * 16 + bo.x; - if ((i % 2) == 0) - return blockData[i/2] & 0xf; - return (blockData[i/2] & 0xf0) >> 4; - } - - bool loadFromOldFile(const std::vector& filebuf); - bool loadFromAnvilFile(const std::vector& filebuf); -}; - - - -struct ChunkCacheStats -{ - int64_t hits, misses; - // types of misses: - int64_t read; // successfully read from disk - int64_t skipped; // assumed not to exist because not required in a full render - int64_t missing; // non-required chunk not present on disk - int64_t reqmissing; // required chunk not present on disk - int64_t corrupt; // found on disk, but failed to read - - // when in region mode, the miss stats have slightly different meanings: - // read: chunk was successfully read from region cache (which may or may not have triggered an - // actual read of the region file from disk) - // missing: not present in the region file, or region file missing/corrupt - // corrupt: region file itself is okay, but chunk data within it is corrupt - // skipped/reqmissing: unused - - ChunkCacheStats() : hits(0), misses(0), read(0), skipped(0), missing(0), reqmissing(0), corrupt(0) {} - - ChunkCacheStats& operator+=(const ChunkCacheStats& ccs); -}; - -struct ChunkCacheEntry -{ - PosChunkIdx ci; // or [-1,-1] if this entry is empty - ChunkData data; - - ChunkCacheEntry() : ci(-1,-1) {} -}; - -#define CACHEBITSX 5 -#define CACHEBITSZ 5 -#define CACHEXSIZE (1 << CACHEBITSX) -#define CACHEZSIZE (1 << CACHEBITSZ) -#define CACHESIZE (CACHEXSIZE * CACHEZSIZE) -#define CACHEXMASK (CACHEXSIZE - 1) -#define CACHEZMASK (CACHEZSIZE - 1) - -struct ChunkCache : private nocopy -{ - ChunkCacheEntry entries[CACHESIZE]; - ChunkData blankdata; // for use with missing chunks - - ChunkTable& chunktable; - RegionTable& regiontable; - ChunkCacheStats& stats; - RegionCache& regioncache; - std::string inputpath; - bool fullrender; - bool regionformat; - std::vector readbuf; // buffer for decompressing into when reading - ChunkCache(ChunkTable& ctable, RegionTable& rtable, RegionCache& rcache, const std::string& inpath, bool fullr, bool regform, ChunkCacheStats& st) - : chunktable(ctable), regiontable(rtable), regioncache(rcache), inputpath(inpath), fullrender(fullr), regionformat(regform), stats(st) - { - memset(blankdata.blockIDs, 0, 65536); - memset(blankdata.blockData, 0, 32768); - memset(blankdata.blockAdd, 0, 32768); - blankdata.anvil = true; - readbuf.reserve(262144); - } - - // look up a chunk and return a pointer to its data - // ...for missing/corrupt chunks, return a pointer to some blank data - ChunkData* getData(const PosChunkIdx& ci); - - static int getEntryNum(const PosChunkIdx& ci) {return (ci.x & CACHEXMASK) * CACHEZSIZE + (ci.z & CACHEZMASK);} - - void readChunkFile(const PosChunkIdx& ci); - void readFromRegionCache(const PosChunkIdx& ci); - void parseReadBuf(const PosChunkIdx& ci, bool anvil); -}; - - - +// Copyright 2010-2012 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#ifndef CHUNK_H +#define CHUNK_H + +#include +#include +#include +#include +#include + +#include "map.h" +#include "tables.h" +#include "region.h" + + + +// offset into a chunk of a block +struct BlockOffset +{ + int64_t x, z, y; + BlockOffset(const BlockIdx& bi) + { + ChunkIdx ci = bi.getChunkIdx(); + x = bi.x - ci.x*16; + z = bi.z - ci.z*16; + y = bi.y; + + assert(x >= 0 && x < 16); + assert(y >= 0 && y < 256); + assert(z >= 0 && z < 16); + } +}; + +struct ChunkData +{ + uint16_t blockIDs[65536]; // 8 bits in mcr format, 12 in anvil - use 16 for fast access, transform on load. + uint8_t blockData[32768]; // 4 bits per block (only half of this space used for old-style chunks) + bool anvil; // whether this data came from an Anvil chunk or an old-style one + + // these guys assume that the BlockIdx actually points to this chunk + // (so they only look at the lower bits) + uint16_t id(const BlockOffset& bo) const + { + return blockIDs[(bo.y * 16 + bo.z) * 16 + bo.x]; + } + uint8_t data(const BlockOffset& bo) const + { + int i = (bo.y * 16 + bo.z) * 16 + bo.x; + if ((i % 2) == 0) + return blockData[i/2] & 0xf; + return (blockData[i/2] & 0xf0) >> 4; + } + + bool loadFromOldFile(const std::vector& filebuf); + bool loadFromAnvilFile(const std::vector& filebuf); +}; + + + +struct ChunkCacheStats +{ + int64_t hits, misses; + // types of misses: + int64_t read; // successfully read from disk + int64_t skipped; // assumed not to exist because not required in a full render + int64_t missing; // non-required chunk not present on disk + int64_t reqmissing; // required chunk not present on disk + int64_t corrupt; // found on disk, but failed to read + + // when in region mode, the miss stats have slightly different meanings: + // read: chunk was successfully read from region cache (which may or may not have triggered an + // actual read of the region file from disk) + // missing: not present in the region file, or region file missing/corrupt + // corrupt: region file itself is okay, but chunk data within it is corrupt + // skipped/reqmissing: unused + + ChunkCacheStats() : hits(0), misses(0), read(0), skipped(0), missing(0), reqmissing(0), corrupt(0) {} + + ChunkCacheStats& operator+=(const ChunkCacheStats& ccs); +}; + +struct ChunkCacheEntry +{ + PosChunkIdx ci; // or [-1,-1] if this entry is empty + ChunkData data; + + ChunkCacheEntry() : ci(-1,-1) {} +}; + +#define CACHEBITSX 5 +#define CACHEBITSZ 5 +#define CACHEXSIZE (1 << CACHEBITSX) +#define CACHEZSIZE (1 << CACHEBITSZ) +#define CACHESIZE (CACHEXSIZE * CACHEZSIZE) +#define CACHEXMASK (CACHEXSIZE - 1) +#define CACHEZMASK (CACHEZSIZE - 1) + +struct ChunkCache : private nocopy +{ + ChunkCacheEntry entries[CACHESIZE]; + ChunkData blankdata; // for use with missing chunks + + ChunkTable& chunktable; + RegionTable& regiontable; + ChunkCacheStats& stats; + RegionCache& regioncache; + std::string inputpath; + bool fullrender; + bool regionformat; + std::vector readbuf; // buffer for decompressing into when reading + ChunkCache(ChunkTable& ctable, RegionTable& rtable, RegionCache& rcache, const std::string& inpath, bool fullr, bool regform, ChunkCacheStats& st) + : chunktable(ctable), regiontable(rtable), stats(st), regioncache(rcache), inputpath(inpath), fullrender(fullr), regionformat(regform) + { + memset(blankdata.blockIDs, 0, 65536 * 2); + memset(blankdata.blockData, 0, 32768); + blankdata.anvil = true; + readbuf.reserve(262144); + } + + // look up a chunk and return a pointer to its data + // ...for missing/corrupt chunks, return a pointer to some blank data + ChunkData* getData(const PosChunkIdx& ci); + + static int getEntryNum(const PosChunkIdx& ci) {return (ci.x & CACHEXMASK) * CACHEZSIZE + (ci.z & CACHEZMASK);} + + void readChunkFile(const PosChunkIdx& ci); + void readFromRegionCache(const PosChunkIdx& ci); + void parseReadBuf(const PosChunkIdx& ci, bool anvil); +}; + + + #endif // CHUNK_H \ No newline at end of file diff --git a/fire.png b/fire.png deleted file mode 100755 index f230f96..0000000 Binary files a/fire.png and /dev/null differ diff --git a/map.cpp b/map.cpp old mode 100755 new mode 100644 index efd857a..076f213 --- a/map.cpp +++ b/map.cpp @@ -1,348 +1,341 @@ -// Copyright 2010-2012 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#include -#include - -#include "map.h" -#include "utils.h" - -using namespace std; - - - -bool MapParams::valid() const -{ - return B >= 2 && B <= 16 && T >= 1 && T <= 16; -} - -bool MapParams::validZoom() const -{ - return baseZoom >= 0 && baseZoom <= 30; -} - -bool MapParams::validYRange() const -{ - return minY <= maxY && minY >= 0 && maxY <= 255; -} - - -bool buildParamMap(const vector& lines, map& params) -{ - for (vector::const_iterator it = lines.begin(); it != lines.end(); it++) - { - vector tokens = tokenize(*it, ' '); - if (tokens.size() != 2) - return false; - params.insert(make_pair(tokens[0], tokens[1])); - } - return true; -} - -bool readParam(const map& params, const string& key, int& value) -{ - map::const_iterator it = params.find(key); - if (it == params.end()) - return false; - return fromstring(it->second, value); -} - -bool MapParams::readFile(const string& outputpath) -{ - string filename = outputpath + "/pigmap.params"; - vector lines; - map params; - if (!readLines(filename, lines) || !buildParamMap(lines, params)) - return false; - if (!readParam(params, "B", B) || !readParam(params, "T", T) || !readParam(params, "baseZoom", baseZoom)) - return false; - userMinY = readParam(params, "userMinY", minY); - userMaxY = readParam(params, "userMaxY", maxY); - return valid() && validZoom(); -} - -void MapParams::writeFile(const string& outputpath) const -{ - string filename = outputpath + "/pigmap.params"; - ofstream outfile(filename.c_str()); - outfile << "B " << B << endl << "T " << T << endl << "baseZoom " << baseZoom << endl; - if (userMinY) - outfile << "userMinY " << minY << endl; - if (userMaxY) - outfile << "userMaxY " << maxY << endl; -} - - - - - - -Pixel operator+(const Pixel& p1, const Pixel& p2) {Pixel p = p1; return p += p2;} -Pixel operator-(const Pixel& p1, const Pixel& p2) {Pixel p = p1; return p -= p2;} - -TileIdx Pixel::getTile(const MapParams& mp) const -{ - int64_t xx = x + 2*mp.B, yy = y + mp.tileSize() - 17*mp.B; - return TileIdx(floordiv(xx, mp.tileSize()), floordiv(yy, mp.tileSize())); -} - - -bool BBox::includes(const Pixel& p) const {return p.x >= topLeft.x && p.x < bottomRight.x && p.y >= topLeft.y && p.y < bottomRight.y;} -bool BBox::overlaps(const BBox& bb) const -{ - if (bb.topLeft.x >= bottomRight.x || bb.topLeft.y >= bottomRight.y || bb.bottomRight.x <= topLeft.x || bb.bottomRight.y <= topLeft.y) - return false; - return true; -} - - -BlockIdx operator+(const BlockIdx& bi1, const BlockIdx& bi2) {BlockIdx bi = bi1; return bi += bi2;} -BlockIdx operator-(const BlockIdx& bi1, const BlockIdx& bi2) {BlockIdx bi = bi1; return bi -= bi2;} - -bool BlockIdx::occludes(const BlockIdx& bi) const -{ - int64_t dx = bi.x - x, dz = bi.z - z, dy = bi.y - y; - // we cannot occlude anyone to the N, W, or U of us - if (dx < 0 || dz > 0 || dy > 0) - return false; - // see if the other block's center is 0 or 1 steps away from ours on the triangular grid - // (the actual grid size doesn't matter; just use a dummy size of 2x1) - int64_t imgxdiff = dx*2 + dz*2; - int64_t imgydiff = -dx + dz - dy*2; - return imgxdiff <= 2 && imgydiff <= 2; -} - -ChunkIdx BlockIdx::getChunkIdx() const -{ - return ChunkIdx(floordiv16(x), floordiv16(z)); -} - -BlockIdx BlockIdx::topBlock(const Pixel& p, const MapParams& mp) -{ - // x = 2Bbx + 2Bbz - // 2Bbx = x - 2Bbz - // bx = x/2B - bz - // y = -Bbx +Bbz -2Bmaxy - // Bbz = y + Bbx + 2Bmaxy - // bz = y/B + bx + 2maxy - // bx = x/2B - y/B - bx - 2maxy - // 2bx = x/2B - y/B - 2maxy - // bx = x/4B - y/2B - maxy <---- - // bz = y/B + x/4B - y/2B - maxy + 2maxy - // bz = x/4B + y/2B + maxy <---- - return BlockIdx((p.x - 2*p.y)/(4*mp.B) - mp.maxY, (p.x + 2*p.y)/(4*mp.B) + mp.maxY, mp.maxY); -} - - - - -string ChunkIdx::toFileName() const -{ - return "c." + toBase36(x) + "." + toBase36(z) + ".dat"; -} -string ChunkIdx::toFilePath() const -{ - return toBase36(mod64pos(x)) + "/" + toBase36(mod64pos(z)) + "/" + toFileName(); -} - -bool ChunkIdx::fromFilePath(const std::string& filename, ChunkIdx& result) -{ - string::size_type pos3 = filename.rfind('.'); - string::size_type pos2 = filename.rfind('.', pos3 - 1); - string::size_type pos = filename.rfind('.', pos2 - 1); - // must have three dots, must have only "dat" after last dot, must have only "c" - // and possibly some directories before the first dot - if (pos == string::npos || pos2 == string::npos || pos3 == string::npos || - filename.compare(pos3, filename.size() - pos3, ".dat") != 0 || - pos < 1 || filename.compare(pos - 1, 1, "c") != 0 || - (pos > 1 && filename[pos - 2] != '/')) - return false; - return fromBase36(filename, pos + 1, pos2 - pos - 1, result.x) - && fromBase36(filename, pos2 + 1, pos3 - pos2 - 1, result.z); -} - -RegionIdx ChunkIdx::getRegionIdx() const -{ - return RegionIdx(floordiv(x, 32), floordiv(z, 32)); -} - -vector ChunkIdx::getTiles(const MapParams& mp) const -{ - BBox bbchunk = getBBox(mp); - vector tiles; - - // get tile of NED corner - TileIdx tibase = nedCorner(mp).getCenter(mp).getTile(mp); - tiles.push_back(tibase); - - // grab as many tiles down as we need - TileIdx tidown = tibase + TileIdx(0,1); - while (tidown.getBBox(mp).overlaps(bbchunk)) - { - tiles.push_back(tidown); - tidown += TileIdx(0,1); - } - - // grab as many tiles up as we need - TileIdx tiup = tibase - TileIdx(0,1); - while (tiup.getBBox(mp).overlaps(bbchunk)) - { - tiles.push_back(tiup); - tiup -= TileIdx(0,1); - } - - // we may also need the tiles to the right of all the ones we have so far - TileIdx tiright = tibase + TileIdx(1,0); - if (tiright.getBBox(mp).overlaps(bbchunk)) - { - vector::size_type oldsize = tiles.size(); - for (vector::size_type i = 0; i < oldsize; i++) - tiles.push_back(tiles[i] + TileIdx(1,0)); - } - - return tiles; -} - -ChunkIdx operator+(const ChunkIdx& ci1, const ChunkIdx& ci2) {ChunkIdx ci = ci1; return ci += ci2;} -ChunkIdx operator-(const ChunkIdx& ci1, const ChunkIdx& ci2) {ChunkIdx ci = ci1; return ci -= ci2;} - - - -string RegionIdx::toOldFileName() const -{ - return "r." + tostring(x) + "." + tostring(z) + ".mcr"; -} - -string RegionIdx::toAnvilFileName() const -{ - return "r." + tostring(x) + "." + tostring(z) + ".mca"; -} - -bool RegionIdx::fromFilePath(const std::string& filename, RegionIdx& result) -{ - string::size_type pos3 = filename.rfind('.'); - string::size_type pos2 = filename.rfind('.', pos3 - 1); - string::size_type pos = filename.rfind('.', pos2 - 1); - // must have three dots, must have only "mcr" or "mca" after last dot, must have only "r" - // and possibly some directories before the first dot - if (pos == string::npos || pos2 == string::npos || pos3 == string::npos || - (filename.compare(pos3, filename.size() - pos3, ".mcr") != 0 && filename.compare(pos3, filename.size() - pos3, ".mca") != 0) || - pos < 1 || filename.compare(pos - 1, 1, "r") != 0 || - (pos > 1 && filename[pos - 2] != '/')) - return false; - return fromstring(filename.substr(pos + 1, pos2 - pos - 1), result.x) - && fromstring(filename.substr(pos2 + 1, pos3 - pos2 - 1), result.z); -} - - -TileIdx operator+(const TileIdx& t1, const TileIdx& t2) {TileIdx t = t1; return t += t2;} -TileIdx operator-(const TileIdx& t1, const TileIdx& t2) {TileIdx t = t1; return t -= t2;} - -bool TileIdx::valid(const MapParams& mp) const -{ - if (mp.baseZoom == 0) - return x == 0 && y == 0; - int64_t max = (1 << mp.baseZoom); - int64_t offset = max/2; - int64_t gx = x + offset, gy = y + offset; - return gx >= 0 && gx < max && gy >= 0 && gy < max; -} - -string TileIdx::toFilePath(const MapParams& mp) const -{ - if (!valid(mp)) - return string(); - if (mp.baseZoom == 0) - return "base.png"; - int64_t offset = (1 << (mp.baseZoom-1)); - int64_t gx = x + offset, gy = y + offset; - string s; - for (int zoom = mp.baseZoom-1; zoom >= 0; zoom--) - { - int64_t xbit = (gx >> zoom) & 0x1; - int64_t ybit = (gy >> zoom) & 0x1; - s += tostring(xbit + 2*ybit) + "/"; - } - s.resize(s.size() - 1); // drop final slash - s += ".png"; - return s; -} - -BBox TileIdx::getBBox(const MapParams& mp) const -{ - Pixel bco = baseChunk(mp).originBlock().getCenter(mp); - Pixel tl = bco + Pixel(-2*mp.B, 17*mp.B - mp.tileSize()); - return BBox(tl, tl + Pixel(mp.tileSize(), mp.tileSize())); -} - -ZoomTileIdx TileIdx::toZoomTileIdx(const MapParams& mp) const -{ - // adjust by offset - int64_t max = (1 << mp.baseZoom); - int64_t offset = max/2; - return ZoomTileIdx(x + offset, y + offset, mp.baseZoom); -} - - - -bool ZoomTileIdx::valid() const -{ - int64_t max = (1 << zoom); - return x >= 0 && x < max && y >= 0 && y < max && zoom >= 0; -} - -string ZoomTileIdx::toFilePath() const -{ - if (!valid()) - return string(); - if (zoom == 0) - return "base.png"; - string s; - for (int z = zoom-1; z >= 0; z--) - { - int64_t xbit = (x >> z) & 0x1; - int64_t ybit = (y >> z) & 0x1; - s += tostring(xbit + 2*ybit) + "/"; - } - s.resize(s.size() - 1); // drop final slash - s += ".png"; - return s; -} - -TileIdx ZoomTileIdx::toTileIdx(const MapParams& mp) const -{ - // scale coords up to base zoom - int64_t newx = x, newy = y; - int shift = mp.baseZoom - zoom; - newx <<= shift; - newy <<= shift; - // adjust by offset to get TileIdx - int64_t max = (1 << mp.baseZoom); - int64_t offset = max/2; - return TileIdx(newx - offset, newy - offset); -} - -ZoomTileIdx ZoomTileIdx::toZoom(int z) const -{ - if (z > zoom) - { - int shift = z - zoom; - return ZoomTileIdx(x << shift, y << shift, z); - } - int shift = zoom - z; - return ZoomTileIdx(x >> shift, y >> shift, z); -} +// Copyright 2010-2012 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#include +#include + +#include "map.h" +#include "utils.h" + +using namespace std; + + + +bool MapParams::valid() const +{ + return B >= 2 && B <= 16 && T >= 1 && T <= 16; +} + +bool MapParams::validZoom() const +{ + return baseZoom >= 0 && baseZoom <= 30; +} + +bool MapParams::validYRange() const +{ + return minY <= maxY && minY >= 0 && maxY <= 255; +} + + +bool buildParamMap(const vector& lines, map& params) +{ + for (vector::const_iterator it = lines.begin(); it != lines.end(); it++) + { + vector tokens = tokenize(*it, ' '); + if (tokens.size() != 2) + return false; + params.insert(make_pair(tokens[0], tokens[1])); + } + return true; +} + +bool readParam(const map& params, const string& key, int& value) +{ + map::const_iterator it = params.find(key); + if (it == params.end()) + return false; + return fromstring(it->second, value); +} + +bool MapParams::readFile(const string& outputpath) +{ + string filename = outputpath + "/pigmap.params"; + vector lines; + map params; + if (!readLines(filename, lines) || !buildParamMap(lines, params)) + return false; + if (!readParam(params, "B", B) || !readParam(params, "T", T) || !readParam(params, "baseZoom", baseZoom)) + return false; + userMinY = readParam(params, "userMinY", minY); + userMaxY = readParam(params, "userMaxY", maxY); + return valid() && validZoom(); +} + +void MapParams::writeFile(const string& outputpath) const +{ + string filename = outputpath + "/pigmap.params"; + ofstream outfile(filename.c_str()); + outfile << "B " << B << endl << "T " << T << endl << "baseZoom " << baseZoom << endl; + if (userMinY) + outfile << "userMinY " << minY << endl; + if (userMaxY) + outfile << "userMaxY " << maxY << endl; +} + + + + + + +Pixel operator+(const Pixel& p1, const Pixel& p2) {Pixel p = p1; return p += p2;} +Pixel operator-(const Pixel& p1, const Pixel& p2) {Pixel p = p1; return p -= p2;} + +TileIdx Pixel::getTile(const MapParams& mp) const +{ + int64_t xx = x + 2*mp.B, yy = y + mp.tileSize() - 17*mp.B; + return TileIdx(floordiv(xx, mp.tileSize()), floordiv(yy, mp.tileSize())); +} + + +bool BBox::includes(const Pixel& p) const {return p.x >= topLeft.x && p.x < bottomRight.x && p.y >= topLeft.y && p.y < bottomRight.y;} +bool BBox::overlaps(const BBox& bb) const +{ + if (bb.topLeft.x >= bottomRight.x || bb.topLeft.y >= bottomRight.y || bb.bottomRight.x <= topLeft.x || bb.bottomRight.y <= topLeft.y) + return false; + return true; +} + + +BlockIdx operator+(const BlockIdx& bi1, const BlockIdx& bi2) {BlockIdx bi = bi1; return bi += bi2;} +BlockIdx operator-(const BlockIdx& bi1, const BlockIdx& bi2) {BlockIdx bi = bi1; return bi -= bi2;} + +bool BlockIdx::occludes(const BlockIdx& bi) const +{ + int64_t dx = bi.x - x, dz = bi.z - z, dy = bi.y - y; + // we cannot occlude anyone to the N, W, or U of us + if (dx < 0 || dz > 0 || dy > 0) + return false; + // see if the other block's center is 0 or 1 steps away from ours on the triangular grid + // (the actual grid size doesn't matter; just use a dummy size of 2x1) + int64_t imgxdiff = dx*2 + dz*2; + int64_t imgydiff = -dx + dz - dy*2; + return imgxdiff <= 2 && imgydiff <= 2; +} + +BlockIdx BlockIdx::topBlock(const Pixel& p, const MapParams& mp) +{ + // x = 2Bbx + 2Bbz + // 2Bbx = x - 2Bbz + // bx = x/2B - bz + // y = -Bbx +Bbz -2Bmaxy + // Bbz = y + Bbx + 2Bmaxy + // bz = y/B + bx + 2maxy + // bx = x/2B - y/B - bx - 2maxy + // 2bx = x/2B - y/B - 2maxy + // bx = x/4B - y/2B - maxy <---- + // bz = y/B + x/4B - y/2B - maxy + 2maxy + // bz = x/4B + y/2B + maxy <---- + return BlockIdx((p.x - 2*p.y)/(4*mp.B) - mp.maxY, (p.x + 2*p.y)/(4*mp.B) + mp.maxY, mp.maxY); +} + + + + +string ChunkIdx::toFileName() const +{ + return "c." + toBase36(x) + "." + toBase36(z) + ".dat"; +} +string ChunkIdx::toFilePath() const +{ + return toBase36(mod64pos(x)) + "/" + toBase36(mod64pos(z)) + "/" + toFileName(); +} + +bool ChunkIdx::fromFilePath(const std::string& filename, ChunkIdx& result) +{ + string::size_type pos3 = filename.rfind('.'); + string::size_type pos2 = filename.rfind('.', pos3 - 1); + string::size_type pos = filename.rfind('.', pos2 - 1); + // must have three dots, must have only "dat" after last dot, must have only "c" + // and possibly some directories before the first dot + if (pos == string::npos || pos2 == string::npos || pos3 == string::npos || + filename.compare(pos3, filename.size() - pos3, ".dat") != 0 || + pos < 1 || filename.compare(pos - 1, 1, "c") != 0 || + (pos > 1 && filename[pos - 2] != '/')) + return false; + return fromBase36(filename, pos + 1, pos2 - pos - 1, result.x) + && fromBase36(filename, pos2 + 1, pos3 - pos2 - 1, result.z); +} + +RegionIdx ChunkIdx::getRegionIdx() const +{ + return RegionIdx(floordiv(x, 32), floordiv(z, 32)); +} + +vector ChunkIdx::getTiles(const MapParams& mp) const +{ + BBox bbchunk = getBBox(mp); + vector tiles; + + // get tile of NED corner + TileIdx tibase = nedCorner(mp).getCenter(mp).getTile(mp); + tiles.push_back(tibase); + + // grab as many tiles down as we need + TileIdx tidown = tibase + TileIdx(0,1); + while (tidown.getBBox(mp).overlaps(bbchunk)) + { + tiles.push_back(tidown); + tidown += TileIdx(0,1); + } + + // grab as many tiles up as we need + TileIdx tiup = tibase - TileIdx(0,1); + while (tiup.getBBox(mp).overlaps(bbchunk)) + { + tiles.push_back(tiup); + tiup -= TileIdx(0,1); + } + + // we may also need the tiles to the right of all the ones we have so far + TileIdx tiright = tibase + TileIdx(1,0); + if (tiright.getBBox(mp).overlaps(bbchunk)) + { + vector::size_type oldsize = tiles.size(); + for (vector::size_type i = 0; i < oldsize; i++) + tiles.push_back(tiles[i] + TileIdx(1,0)); + } + + return tiles; +} + +ChunkIdx operator+(const ChunkIdx& ci1, const ChunkIdx& ci2) {ChunkIdx ci = ci1; return ci += ci2;} +ChunkIdx operator-(const ChunkIdx& ci1, const ChunkIdx& ci2) {ChunkIdx ci = ci1; return ci -= ci2;} + + + +string RegionIdx::toOldFileName() const +{ + return "r." + tostring(x) + "." + tostring(z) + ".mcr"; +} + +string RegionIdx::toAnvilFileName() const +{ + return "r." + tostring(x) + "." + tostring(z) + ".mca"; +} + +bool RegionIdx::fromFilePath(const std::string& filename, RegionIdx& result) +{ + string::size_type pos3 = filename.rfind('.'); + string::size_type pos2 = filename.rfind('.', pos3 - 1); + string::size_type pos = filename.rfind('.', pos2 - 1); + // must have three dots, must have only "mcr" or "mca" after last dot, must have only "r" + // and possibly some directories before the first dot + if (pos == string::npos || pos2 == string::npos || pos3 == string::npos || + (filename.compare(pos3, filename.size() - pos3, ".mcr") != 0 && filename.compare(pos3, filename.size() - pos3, ".mca") != 0) || + pos < 1 || filename.compare(pos - 1, 1, "r") != 0 || + (pos > 1 && filename[pos - 2] != '/')) + return false; + return fromstring(filename.substr(pos + 1, pos2 - pos - 1), result.x) + && fromstring(filename.substr(pos2 + 1, pos3 - pos2 - 1), result.z); +} + + +TileIdx operator+(const TileIdx& t1, const TileIdx& t2) {TileIdx t = t1; return t += t2;} +TileIdx operator-(const TileIdx& t1, const TileIdx& t2) {TileIdx t = t1; return t -= t2;} + +bool TileIdx::valid(const MapParams& mp) const +{ + if (mp.baseZoom == 0) + return x == 0 && y == 0; + int64_t max = (1 << mp.baseZoom); + int64_t offset = max/2; + int64_t gx = x + offset, gy = y + offset; + return gx >= 0 && gx < max && gy >= 0 && gy < max; +} + +string TileIdx::toFilePath(const MapParams& mp) const +{ + if (!valid(mp)) + return string(); + if (mp.baseZoom == 0) + return "base"; + int64_t offset = (1 << (mp.baseZoom-1)); + int64_t gx = x + offset, gy = y + offset; + string s; + for (int zoom = mp.baseZoom-1; zoom >= 0; zoom--) + { + int64_t xbit = (gx >> zoom) & 0x1; + int64_t ybit = (gy >> zoom) & 0x1; + s += tostring(xbit + 2*ybit) + "/"; + } + s.resize(s.size() - 1); // drop final slash + return s; +} + +BBox TileIdx::getBBox(const MapParams& mp) const +{ + Pixel bco = baseChunk(mp).originBlock().getCenter(mp); + Pixel tl = bco + Pixel(-2*mp.B, 17*mp.B - mp.tileSize()); + return BBox(tl, tl + Pixel(mp.tileSize(), mp.tileSize())); +} + +ZoomTileIdx TileIdx::toZoomTileIdx(const MapParams& mp) const +{ + // adjust by offset + int64_t max = (1 << mp.baseZoom); + int64_t offset = max/2; + return ZoomTileIdx(x + offset, y + offset, mp.baseZoom); +} + + + +bool ZoomTileIdx::valid() const +{ + int64_t max = (1 << zoom); + return x >= 0 && x < max && y >= 0 && y < max && zoom >= 0; +} + +string ZoomTileIdx::toFilePath() const +{ + if (!valid()) + return string(); + if (zoom == 0) + return "base"; + string s; + for (int z = zoom-1; z >= 0; z--) + { + int64_t xbit = (x >> z) & 0x1; + int64_t ybit = (y >> z) & 0x1; + s += tostring(xbit + 2*ybit) + "/"; + } + s.resize(s.size() - 1); // drop final slash + return s; +} + +TileIdx ZoomTileIdx::toTileIdx(const MapParams& mp) const +{ + // scale coords up to base zoom + int64_t newx = x, newy = y; + int shift = mp.baseZoom - zoom; + newx <<= shift; + newy <<= shift; + // adjust by offset to get TileIdx + int64_t max = (1 << mp.baseZoom); + int64_t offset = max/2; + return TileIdx(newx - offset, newy - offset); +} + +ZoomTileIdx ZoomTileIdx::toZoom(int z) const +{ + if (z > zoom) + { + int shift = z - zoom; + return ZoomTileIdx(x << shift, y << shift, z); + } + int shift = zoom - z; + return ZoomTileIdx(x >> shift, y >> shift, z); +} diff --git a/map.h b/map.h old mode 100755 new mode 100644 index 7246669..67e34da --- a/map.h +++ b/map.h @@ -1,358 +1,374 @@ -// Copyright 2010, 2011 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#ifndef MAP_H -#define MAP_H - -#include -#include -#include - -// IMPORTANT NOTE: -// This program was written before the location of the sun moved in Minecraft Beta 1.9 or so, -// therefore all of the N/S/E/W directions here are now wrong--rotated 90 degrees from what they -// should be. For example, the positive X direction used to be South, and is called South here, -// but is now East in the game (as of Minecraft 1.0, anyway). -// I decided to leave the old direction names here, because it would be pretty easy to mess -// something up trying to go through and change everything. Apologies for the confusion! - -// Minecraft coord system: -// -// +x = S +z = W +y = U -// -x = N -z = E -y = D - -// the block size is a parameter B >= 2 -// -// BlockIdx delta image coord delta -// [bx,bz,by] [x,y] -//--------------------------------------------- -// [-1,0,0] (N) [-2B,B] -// [1,0,0] (S) [2B,-B] -// [0,-1,0] (E) [-2B,-B] -// [0,1,0] (W) [2B,B] -// [0,0,-1] (D) [0,2B] -// [0,0,1] (U) [0,-2B] -// -// block size endpoint-exclusive bounding box (from block center) -// B [-2B,-2B] to [2B,2B] (size 4Bx4B) -// -// [2B 2B 0 ] [bx] [x] -// [-B B -2B] * [bz] = [y] -// [by] -// -// 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] -// -// the pixels that correspond to block centers form a hexagonal grid: -// x % 2B = 0 -// y % 2B = 0 (if x % 4B = 0) -// y % 2B = B (if x % 4B = 2B) -// -// example, with B = 1: -// -// X...X...X... -// ..X...X...X. -// X...X...X... -// ..X...X...X. -// X...X...X... -// ..X...X...X. - -// a chunk is 16x16x128 blocks (chunk/region format) or 16x16x256 blocks (Anvil format), but we can -// also specify the min and max Y values we want, and consider a chunk to be 16x16x(MAXY-MINY+1), -// where MINY and MAXY are inclusive--e.g. for an entire Anvil chunk, MINY=0 and MAXY=255 -// -// each chunk covers a hexagonal area of the image; the corners of the hexagon (in clockwise order) -// are the farthest blocks NED, NEU, SEU, SWU, SWD, NWD -// -// SEU -// / . \ -// / . \ -// NEU . SWU -// |\ . /| -// | \ . / | -// | NWU | -// | | | -// | (SED) | -// | . | . | -// |. | .| -// NED | SWD -// \ | / -// \ | / -// NWD -// -// in block coordinates, let the origin of the chunk be the block at [0,0,0] relative to the chunk--i.e. -// the NED corner of the full chunk (as opposed to the slice of it defined by MINY and MAXY) -// -// relative to the origin block of the chunk, the corner blocks are: -// NED NEU SEU SWU SWD NWD -// [0,0,MINY] [0,0,MAXY] [15,0,MAXY] [15,15,MAXY] [15,15,MINY] [0,15,MINY] -// -// ...and the image-coord centers of these blocks, relative to the image-coord center of the origin block, are: -// NED NEU SEU SWU SWD NWD -// [0,-2B*MINY] [0,-2B*MAXY] [30B,-15B-2B*MAXY] [60B,-2B*MAXY] [60B,-2B*MINY] [30B,15B-2B*MINY] -// -// ...and the distances to the origin blocks of the neighboring chunks are: -// N E S W -// [-32B,16B] [-32B,-16B] [32B,-16B] [32B,16B] -// NE SE SW NW -// [-64B,0] [0,-32B] [64B,0] [0,32B] -// -// ...and the endpoint-exclusive bounding box of a chunk, from the center of the origin block, is: -// [-2B,-17B-2B*MAXY] to [62B,17B-2B*MINY] (size 64B x 34B-2B*(MAXY-MINY)) -// -// the center of the origin block of chunk [0,0] is the origin for the absolute pixel -// 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] - -// tile size must be = T * 64B for some T (so it covers the width of at least one chunk) -// ...each tile has a base chunk; the tile's bounding box shares its bottom-left corner with -// its base chunk's full bounding box (so the base chunk is contained within the tile on the -// left, right, and bottom, but may extend past the top of the tile) -// -// TileIdx delta ChunkIdx delta -// [tx,ty] [cx,cz] -// ----------------------------------------------- -// [1,0] [T,T] -// [0,1] [-2T,2T] -// -// TileIdx: -// [tx,ty] in tile coords -// ChunkIdx of base chunk: -// [T*tx - 2*T*ty, T*tx + 2*T*ty] in chunk coords -// center of base chunk's origin block: -// [64*B*T*tx, 64*B*T*ty] in absolute pixels -// base chunk's endpoint-exclusive bounding box: -// [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 -// tile's endpoint-exclusive bounding box: -// [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 -// -// to compute the TileIdx [tx,ty] that includes a pixel [x,y]: -// 1. let x' = x + 2B, y' = y + 64BT - 17B -// 2. tx = floor(x' / 64BT) -// 3. ty = floor(y' / 64BT) -// ...where floor(a / b) represents floored division, i.e. the result you'd get by performing the real-number division a / b -// and then taking the floor - -// the tiles required to draw a chunk can be determined by finding the tile that the origin block's center -// is in, then checking the tiles below, right, and above that one, looking for bounding box intersections - -// the set of chunks required to draw a tile can be constructed thusly: -// set #1: start with the base chunk and the chunk directly SE of it -// set #2: if T > 1, add all the chunks that can be reached by moving up to T-1 steps SW -// and/or up to T-1 double steps SE from the chunks in set #1 (this is the set of -// chunks that would be in set #1 for *some* tile, if T was = 1) -// set #3: add all chunks that are immediate N, E, S, or W neighbors of chunks in set #2 -// (these are the chunks whose bottom layer of blocks is partially within the tile) -// set #4: add all chunks that are up to 8 steps NW of a chunk in set #3 (these are the chunks -// *some* layer of which is partially within the tile) - - -struct ChunkIdx; -struct RegionIdx; -struct TileIdx; -struct ZoomTileIdx; - -struct MapParams -{ - int B; // block size; must be >= 2 - int T; // tile multiplier; must be >= 1 - // Google Maps zoom level of the base tiles; maximum map size is 2^baseZoom by 2^baseZoom tiles - int baseZoom; - - // MINY and MAXY values used during rendering; these are either specified by the user, or filled - // in with Minecraft's limits (0-255) - int minY, maxY; - - // whether or not the MINY/MAXY values are user-provided or defaults; default values are not stored - // in pigmap.params - bool userMinY, userMaxY; - - MapParams(int b, int t, int bz) : B(b), T(t), baseZoom(bz), minY(0), maxY(255), userMinY(false), userMaxY(false) {} - MapParams() : B(0), T(0), baseZoom(0), minY(0), maxY(255), userMinY(false), userMaxY(false) {} - - int tileSize() const {return 64*B*T;} - - bool valid() const; // see if B and T are okay - bool validZoom() const; // see if baseZoom is okay - bool validYRange() const; // see if MINY/MAXY are okay - - // read/write the file "pigmap.params" in the output path (i.e. the top-level map directory) - bool readFile(const std::string& outputpath); // also validates stored values - void writeFile(const std::string& outputpath) const; -}; - - -struct Pixel -{ - int64_t x, y; - - Pixel(int64_t xx, int64_t yy) : x(xx), y(yy) {} - - Pixel& operator+=(const Pixel& p) {x += p.x; y += p.y; return *this;} - Pixel& operator-=(const Pixel& p) {x -= p.x; y -= p.y; return *this;} - bool operator==(const Pixel& p) const {return x == p.x && y == p.y;} - bool operator!=(const Pixel& p) const {return !operator==(p);} - - TileIdx getTile(const MapParams& mp) const; -}; - -Pixel operator+(const Pixel& p1, const Pixel& p2); -Pixel operator-(const Pixel& p1, const Pixel& p2); - -// endpoint-exclusive bounding box (right and bottom edges not included) -struct BBox -{ - Pixel topLeft, bottomRight; - - BBox(const Pixel& tl, const Pixel& br) : topLeft(tl), bottomRight(br) {} - - Pixel bottomLeft() const {return Pixel(topLeft.x, bottomRight.y);} - Pixel topRight() const {return Pixel(bottomRight.x, topLeft.y);} - - bool includes(const Pixel& p) const; - bool overlaps(const BBox& bb) const; -}; - -struct BlockIdx -{ - int64_t x, z, y; - - BlockIdx(int64_t xx, int64_t zz, int64_t yy) : x(xx), z(zz), y(yy) {} - - bool occludes(const BlockIdx& bi) const; - bool isOccludedBy(const BlockIdx& bi) const {return bi.occludes(*this);} - - Pixel getCenter(const MapParams& mp) const {return Pixel(2*mp.B*(x+z), mp.B*(z-x-2*y));} - 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));} - ChunkIdx getChunkIdx() const; - - // there are many blocks that project to each pixel on the map (one for each Y-value); - // this returns the topmost, assuming that the pixel is properly aligned on the block-center grid - static BlockIdx topBlock(const Pixel& p, const MapParams& mp); - - BlockIdx& operator+=(const BlockIdx& bi) {x += bi.x; z += bi.z; y += bi.y; return *this;} - BlockIdx& operator-=(const BlockIdx& bi) {x -= bi.x; z -= bi.z; y -= bi.y; return *this;} - bool operator==(const BlockIdx& bi) const {return x == bi.x && z == bi.z && y == bi.y;} - bool operator!=(const BlockIdx& bi) const {return !operator==(bi);} -}; - -BlockIdx operator+(const BlockIdx& bi1, const BlockIdx& bi2); -BlockIdx operator-(const BlockIdx& bi1, const BlockIdx& bi2); - -struct ChunkIdx -{ - int64_t x, z; - - ChunkIdx(int64_t xx, int64_t zz) : x(xx), z(zz) {} - - // just the filename (e.g. "c.0.0.dat") - std::string toFileName() const; - // the relative path from the top level of world data (e.g. "0/0/c.0.0.dat") - std::string toFilePath() const; - - // see if a path is a valid chunk file and return its ChunkIdx if so - // ...can be plain filename, relative path, or absolute path; the chunk coords - // depend only on the filename - static bool fromFilePath(const std::string& filename, ChunkIdx& result); - - BlockIdx originBlock() const {return BlockIdx(x*16, z*16, 0);} - BlockIdx nedCorner(const MapParams& mp) const {return BlockIdx(x*16, z*16, mp.minY);} - 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));} - RegionIdx getRegionIdx() const; - - std::vector getTiles(const MapParams& mp) const; - - ChunkIdx& operator+=(const ChunkIdx& ci) {x += ci.x; z += ci.z; return *this;} - ChunkIdx& operator-=(const ChunkIdx& ci) {x -= ci.x; z -= ci.z; return *this;} - bool operator==(const ChunkIdx& ci) const {return x == ci.x && z == ci.z;} - bool operator!=(const ChunkIdx& ci) const {return !operator==(ci);} -}; - -ChunkIdx operator+(const ChunkIdx& ci1, const ChunkIdx& ci2); -ChunkIdx operator-(const ChunkIdx& ci1, const ChunkIdx& ci2); - -struct RegionIdx -{ - int64_t x, z; - - RegionIdx(int64_t xx, int64_t zz) : x(xx), z(zz) {} - - // just the filename (e.g. "r.-1.2.mcr" for old-style, "r.-1.2.mca" for Anvil) - std::string toOldFileName() const; - std::string toAnvilFileName() const; - - // see if a path is a valid region file and return its RegionIdx if so - // ...can be plain filename, relative path, or absolute path; region coords - // depend only on the filename - // ...can be either an old (".mcr") or Anvil (".mca") filename - static bool fromFilePath(const std::string& filename, RegionIdx& result); - - ChunkIdx baseChunk() const {return ChunkIdx(x*32, z*32);} // NE corner - - bool operator==(const RegionIdx& ri) const {return x == ri.x && z == ri.z;} - bool operator!=(const RegionIdx& ri) const {return !operator==(ri);} -}; - -// these guys represent tiles at the base zoom level -struct TileIdx -{ - // these are not the same coords used by Google Maps, since their coords are all positive; - // our tile [0,0] maps to their tile [2^(baseZoom-1),2^(baseZoom-1)], etc. - int64_t x, y; - - TileIdx(int64_t xx, int64_t yy) : x(xx), y(yy) {} - - // Google Maps limit is 2^Z by 2^Z tiles per zoom level Z; check whether this TileIdx is - // within the allowed range for baseZoom - bool valid(const MapParams& mp) const; - // get Google Maps filepath (e.g. "0/3/2/0/0/1/2.png"), or empty string for invalid tile - std::string toFilePath(const MapParams& mp) const; - - ChunkIdx baseChunk(const MapParams& mp) const {return ChunkIdx(mp.T*(x-2*y), mp.T*(x+2*y));} - BBox getBBox(const MapParams& mp) const; - ZoomTileIdx toZoomTileIdx(const MapParams& mp) const; - - TileIdx& operator+=(const TileIdx& t) {x += t.x; y += t.y; return *this;} - TileIdx& operator-=(const TileIdx& t) {x -= t.x; y -= t.y; return *this;} - bool operator==(const TileIdx& t) const {return t.x == x && t.y == y;} - bool operator!=(const TileIdx& t) const {return !operator==(t);} -}; - -TileIdx operator+(const TileIdx& t1, const TileIdx& t2); -TileIdx operator-(const TileIdx& t1, const TileIdx& t2); - -// these guys represent tiles at the other zoom levels -struct ZoomTileIdx -{ - int64_t x, y; // Google Maps coords--each coord goes from 0 to 2^zoom - int zoom; // Google Maps zoom level--0 for top level (base.png), etc. - - ZoomTileIdx(int64_t xx, int64_t yy, int z) : x(xx), y(yy), zoom(z) {} - - bool valid() const; - std::string toFilePath() const; - - // get the top-left base tile contained in this tile - TileIdx toTileIdx(const MapParams& mp) const; - - // if z > zoom, gets the top-left tile of those at level z that this tile includes; - // if z < zoom, gets the tile at level z that includes this tile - ZoomTileIdx toZoom(int z) const; - - // no operator+; addition shouldn't be defined for tiles with different zoom levels - ZoomTileIdx add(int dx, int dy) const {return ZoomTileIdx(x + dx, y + dy, zoom);} -}; - +// Copyright 2010, 2011 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#ifndef MAP_H +#define MAP_H + +#include +#include +#include + +#include "utils.h" + +// IMPORTANT NOTE: +// This program was written before the location of the sun moved in Minecraft Beta 1.9 or so, +// therefore all of the N/S/E/W directions here are now wrong--rotated 90 degrees from what they +// should be. For example, the positive X direction used to be South, and is called South here, +// but is now East in the game (as of Minecraft 1.0, anyway). +// I decided to leave the old direction names here, because it would be pretty easy to mess +// something up trying to go through and change everything. Apologies for the confusion! + +// Minecraft coord system: +// +// +x = S +z = W +y = U +// -x = N -z = E -y = D + +// the block size is a parameter B >= 2 +// +// BlockIdx delta image coord delta +// [bx,bz,by] [x,y] +//--------------------------------------------- +// [-1,0,0] (N) [-2B,B] +// [1,0,0] (S) [2B,-B] +// [0,-1,0] (E) [-2B,-B] +// [0,1,0] (W) [2B,B] +// [0,0,-1] (D) [0,2B] +// [0,0,1] (U) [0,-2B] +// +// block size endpoint-exclusive bounding box (from block center) +// B [-2B,-2B] to [2B,2B] (size 4Bx4B) +// +// [2B 2B 0 ] [bx] [x] +// [-B B -2B] * [bz] = [y] +// [by] +// +// 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] +// +// the pixels that correspond to block centers form a hexagonal grid: +// x % 2B = 0 +// y % 2B = 0 (if x % 4B = 0) +// y % 2B = B (if x % 4B = 2B) +// +// example, with B = 1: +// +// X...X...X... +// ..X...X...X. +// X...X...X... +// ..X...X...X. +// X...X...X... +// ..X...X...X. + +// a chunk is 16x16x128 blocks (chunk/region format) or 16x16x256 blocks (Anvil format), but we can +// also specify the min and max Y values we want, and consider a chunk to be 16x16x(MAXY-MINY+1), +// where MINY and MAXY are inclusive--e.g. for an entire Anvil chunk, MINY=0 and MAXY=255 +// +// each chunk covers a hexagonal area of the image; the corners of the hexagon (in clockwise order) +// are the farthest blocks NED, NEU, SEU, SWU, SWD, NWD +// +// SEU +// / . \` +// / . \` +// NEU . SWU +// |\ . /| +// | \ . / | +// | NWU | +// | | | +// | (SED) | +// | . | . | +// |. | .| +// NED | SWD +// \ | / +// \ | / +// NWD +// +// in block coordinates, let the origin of the chunk be the block at [0,0,0] relative to the chunk--i.e. +// the NED corner of the full chunk (as opposed to the slice of it defined by MINY and MAXY) +// +// relative to the origin block of the chunk, the corner blocks are: +// NED NEU SEU SWU SWD NWD +// [0,0,MINY] [0,0,MAXY] [15,0,MAXY] [15,15,MAXY] [15,15,MINY] [0,15,MINY] +// +// ...and the image-coord centers of these blocks, relative to the image-coord center of the origin block, are: +// NED NEU SEU SWU SWD NWD +// [0,-2B*MINY] [0,-2B*MAXY] [30B,-15B-2B*MAXY] [60B,-2B*MAXY] [60B,-2B*MINY] [30B,15B-2B*MINY] +// +// ...and the distances to the origin blocks of the neighboring chunks are: +// N E S W +// [-32B,16B] [-32B,-16B] [32B,-16B] [32B,16B] +// NE SE SW NW +// [-64B,0] [0,-32B] [64B,0] [0,32B] +// +// ...and the endpoint-exclusive bounding box of a chunk, from the center of the origin block, is: +// [-2B,-17B-2B*MAXY] to [62B,17B-2B*MINY] (size 64B x 34B-2B*(MAXY-MINY)) +// +// the center of the origin block of chunk [0,0] is the origin for the absolute pixel +// 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] + +// tile size must be = T * 64B for some T (so it covers the width of at least one chunk) +// ...each tile has a base chunk; the tile's bounding box shares its bottom-left corner with +// its base chunk's full bounding box (so the base chunk is contained within the tile on the +// left, right, and bottom, but may extend past the top of the tile) +// +// TileIdx delta ChunkIdx delta +// [tx,ty] [cx,cz] +// ----------------------------------------------- +// [1,0] [T,T] +// [0,1] [-2T,2T] +// +// TileIdx: +// [tx,ty] in tile coords +// ChunkIdx of base chunk: +// [T*tx - 2*T*ty, T*tx + 2*T*ty] in chunk coords +// center of base chunk's origin block: +// [64*B*T*tx, 64*B*T*ty] in absolute pixels +// base chunk's endpoint-exclusive bounding box: +// [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 +// tile's endpoint-exclusive bounding box: +// [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 +// +// to compute the TileIdx [tx,ty] that includes a pixel [x,y]: +// 1. let x' = x + 2B, y' = y + 64BT - 17B +// 2. tx = floor(x' / 64BT) +// 3. ty = floor(y' / 64BT) +// ...where floor(a / b) represents floored division, i.e. the result you'd get by performing the real-number division a / b +// and then taking the floor + +// the tiles required to draw a chunk can be determined by finding the tile that the origin block's center +// is in, then checking the tiles below, right, and above that one, looking for bounding box intersections + +// the set of chunks required to draw a tile can be constructed thusly: +// set #1: start with the base chunk and the chunk directly SE of it +// set #2: if T > 1, add all the chunks that can be reached by moving up to T-1 steps SW +// and/or up to T-1 double steps SE from the chunks in set #1 (this is the set of +// chunks that would be in set #1 for *some* tile, if T was = 1) +// set #3: add all chunks that are immediate N, E, S, or W neighbors of chunks in set #2 +// (these are the chunks whose bottom layer of blocks is partially within the tile) +// set #4: add all chunks that are up to 8 steps NW of a chunk in set #3 (these are the chunks +// *some* layer of which is partially within the tile) + + +struct ChunkIdx; +struct RegionIdx; +struct TileIdx; +struct ZoomTileIdx; + +enum RenderMode { + RenderMode_Classic, + RenderMode_Daylight, + RenderMode_Night +}; + +struct MapParams +{ + int B; // block size; must be >= 2 + int T; // tile multiplier; must be >= 1 + // Google Maps zoom level of the base tiles; maximum map size is 2^baseZoom by 2^baseZoom tiles + int baseZoom; + + // MINY and MAXY values used during rendering; these are either specified by the user, or filled + // in with Minecraft's limits (0-255) + int minY, maxY; + + // whether or not the MINY/MAXY values are user-provided or defaults; default values are not stored + // in pigmap.params + bool userMinY, userMaxY; + + // Render in this mode - classic (all full-light), daylight or night + RenderMode mode; + + MapParams(int b, int t, int bz) : B(b), T(t), baseZoom(bz), minY(0), maxY(255), userMinY(false), userMaxY(false), mode(RenderMode_Classic) {} + MapParams() : B(0), T(0), baseZoom(0), minY(0), maxY(255), userMinY(false), userMaxY(false), mode(RenderMode_Classic) {} + + int tileSize() const {return 64*B*T;} + + bool valid() const; // see if B and T are okay + bool validZoom() const; // see if baseZoom is okay + bool validYRange() const; // see if MINY/MAXY are okay + + // read/write the file "pigmap.params" in the output path (i.e. the top-level map directory) + bool readFile(const std::string& outputpath); // also validates stored values + void writeFile(const std::string& outputpath) const; +}; + + +struct Pixel +{ + int64_t x, y; + + Pixel(int64_t xx, int64_t yy) : x(xx), y(yy) {} + + Pixel& operator+=(const Pixel& p) {x += p.x; y += p.y; return *this;} + Pixel& operator-=(const Pixel& p) {x -= p.x; y -= p.y; return *this;} + bool operator==(const Pixel& p) const {return x == p.x && y == p.y;} + bool operator!=(const Pixel& p) const {return !operator==(p);} + + TileIdx getTile(const MapParams& mp) const; +}; + +Pixel operator+(const Pixel& p1, const Pixel& p2); +Pixel operator-(const Pixel& p1, const Pixel& p2); + +// endpoint-exclusive bounding box (right and bottom edges not included) +struct BBox +{ + Pixel topLeft, bottomRight; + + BBox(const Pixel& tl, const Pixel& br) : topLeft(tl), bottomRight(br) {} + + Pixel bottomLeft() const {return Pixel(topLeft.x, bottomRight.y);} + Pixel topRight() const {return Pixel(bottomRight.x, topLeft.y);} + + bool includes(const Pixel& p) const; + bool overlaps(const BBox& bb) const; +}; + +struct BlockIdx +{ + int64_t x, z, y; + + BlockIdx(int64_t xx, int64_t zz, int64_t yy) : x(xx), z(zz), y(yy) {} + + bool occludes(const BlockIdx& bi) const; + bool isOccludedBy(const BlockIdx& bi) const {return bi.occludes(*this);} + + Pixel getCenter(const MapParams& mp) const {return Pixel(2*mp.B*(x+z), mp.B*(z-x-2*y));} + 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));} + ChunkIdx getChunkIdx() const; + + // there are many blocks that project to each pixel on the map (one for each Y-value); + // this returns the topmost, assuming that the pixel is properly aligned on the block-center grid + static BlockIdx topBlock(const Pixel& p, const MapParams& mp); + + BlockIdx& operator+=(const BlockIdx& bi) {x += bi.x; z += bi.z; y += bi.y; return *this;} + BlockIdx& operator-=(const BlockIdx& bi) {x -= bi.x; z -= bi.z; y -= bi.y; return *this;} + bool operator==(const BlockIdx& bi) const {return x == bi.x && z == bi.z && y == bi.y;} + bool operator!=(const BlockIdx& bi) const {return !operator==(bi);} +}; + +BlockIdx operator+(const BlockIdx& bi1, const BlockIdx& bi2); +BlockIdx operator-(const BlockIdx& bi1, const BlockIdx& bi2); + +struct ChunkIdx +{ + int64_t x, z; + + ChunkIdx(int64_t xx, int64_t zz) : x(xx), z(zz) {} + + // just the filename (e.g. "c.0.0.dat") + std::string toFileName() const; + // the relative path from the top level of world data (e.g. "0/0/c.0.0.dat") + std::string toFilePath() const; + + // see if a path is a valid chunk file and return its ChunkIdx if so + // ...can be plain filename, relative path, or absolute path; the chunk coords + // depend only on the filename + static bool fromFilePath(const std::string& filename, ChunkIdx& result); + + BlockIdx originBlock() const {return BlockIdx(x*16, z*16, 0);} + BlockIdx nedCorner(const MapParams& mp) const {return BlockIdx(x*16, z*16, mp.minY);} + 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));} + RegionIdx getRegionIdx() const; + + std::vector getTiles(const MapParams& mp) const; + + ChunkIdx& operator+=(const ChunkIdx& ci) {x += ci.x; z += ci.z; return *this;} + ChunkIdx& operator-=(const ChunkIdx& ci) {x -= ci.x; z -= ci.z; return *this;} + bool operator==(const ChunkIdx& ci) const {return x == ci.x && z == ci.z;} + bool operator!=(const ChunkIdx& ci) const {return !operator==(ci);} +}; + +ChunkIdx operator+(const ChunkIdx& ci1, const ChunkIdx& ci2); +ChunkIdx operator-(const ChunkIdx& ci1, const ChunkIdx& ci2); + +struct RegionIdx +{ + int64_t x, z; + + RegionIdx(int64_t xx, int64_t zz) : x(xx), z(zz) {} + + // just the filename (e.g. "r.-1.2.mcr" for old-style, "r.-1.2.mca" for Anvil) + std::string toOldFileName() const; + std::string toAnvilFileName() const; + + // see if a path is a valid region file and return its RegionIdx if so + // ...can be plain filename, relative path, or absolute path; region coords + // depend only on the filename + // ...can be either an old (".mcr") or Anvil (".mca") filename + static bool fromFilePath(const std::string& filename, RegionIdx& result); + + ChunkIdx baseChunk() const {return ChunkIdx(x*32, z*32);} // NE corner + + bool operator==(const RegionIdx& ri) const {return x == ri.x && z == ri.z;} + bool operator!=(const RegionIdx& ri) const {return !operator==(ri);} +}; + +// these guys represent tiles at the base zoom level +struct TileIdx +{ + // these are not the same coords used by Google Maps, since their coords are all positive; + // our tile [0,0] maps to their tile [2^(baseZoom-1),2^(baseZoom-1)], etc. + int64_t x, y; + + TileIdx(int64_t xx, int64_t yy) : x(xx), y(yy) {} + + // Google Maps limit is 2^Z by 2^Z tiles per zoom level Z; check whether this TileIdx is + // within the allowed range for baseZoom + bool valid(const MapParams& mp) const; + // get Google Maps filepath (e.g. "0/3/2/0/0/1/2.png"), or empty string for invalid tile + std::string toFilePath(const MapParams& mp) const; + + ChunkIdx baseChunk(const MapParams& mp) const {return ChunkIdx(mp.T*(x-2*y), mp.T*(x+2*y));} + BBox getBBox(const MapParams& mp) const; + ZoomTileIdx toZoomTileIdx(const MapParams& mp) const; + + TileIdx& operator+=(const TileIdx& t) {x += t.x; y += t.y; return *this;} + TileIdx& operator-=(const TileIdx& t) {x -= t.x; y -= t.y; return *this;} + bool operator==(const TileIdx& t) const {return t.x == x && t.y == y;} + bool operator!=(const TileIdx& t) const {return !operator==(t);} +}; + +TileIdx operator+(const TileIdx& t1, const TileIdx& t2); +TileIdx operator-(const TileIdx& t1, const TileIdx& t2); + +// these guys represent tiles at the other zoom levels +struct ZoomTileIdx +{ + int64_t x, y; // Google Maps coords--each coord goes from 0 to 2^zoom + int zoom; // Google Maps zoom level--0 for top level (base.png), etc. + + ZoomTileIdx(int64_t xx, int64_t yy, int z) : x(xx), y(yy), zoom(z) {} + + bool valid() const; + std::string toFilePath() const; + + // get the top-left base tile contained in this tile + TileIdx toTileIdx(const MapParams& mp) const; + + // if z > zoom, gets the top-left tile of those at level z that this tile includes; + // if z < zoom, gets the tile at level z that includes this tile + ZoomTileIdx toZoom(int z) const; + + // no operator+; addition shouldn't be defined for tiles with different zoom levels + ZoomTileIdx add(int dx, int dy) const {return ZoomTileIdx(x + dx, y + dy, zoom);} +}; + +inline ChunkIdx BlockIdx::getChunkIdx() const +{ + return ChunkIdx(floordiv16(x), floordiv16(z)); +} + #endif // MAP_H \ No newline at end of file diff --git a/pigmap.cpp b/pigmap.cpp old mode 100755 new mode 100644 index ae37015..4fb3e61 --- a/pigmap.cpp +++ b/pigmap.cpp @@ -1,1072 +1,1134 @@ -// Copyright 2010, 2011 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -// -// -// TODO: -// -cosmetic things: -// -have signs actually face correct directions -// -edge shadows on vertical edges, too? -// -proper redstone wire directions -// -better dragon egg -// -extended pistons -// -premultiply block image alphas? -// -dump list of corrupted chunks at end, so they can be retried later -// -keep some space around for PNG row pointers instead of allocating every time -// -see if it's better to have threads work together and share a cache? -// -for the love of god, clean up blockimages.cpp! -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "blockimages.h" -#include "rgba.h" -#include "map.h" -#include "utils.h" -#include "tables.h" -#include "chunk.h" -#include "render.h" -#include "world.h" - -using namespace std; - - - -//------------------------------------------------------------------------------------------------------------------- - -void printStats(int seconds, const RenderStats& stats) -{ - cout << stats.reqchunkcount << " chunks " << stats.reqregioncount << " regions " - << stats.reqtilecount << " base tiles " << seconds << " seconds" << endl; - cout << "chunk cache: " << stats.chunkcache.hits << " hits " << stats.chunkcache.misses << " misses" << endl; - cout << " " << stats.chunkcache.read << " read " << stats.chunkcache.skipped << " skipped " << stats.chunkcache.missing << " missing " - << stats.chunkcache.reqmissing << " reqmissing " << stats.chunkcache.corrupt << " corrupt" << endl; - cout << "region cache: " << stats.regioncache.hits << " hits " << stats.regioncache.misses << " misses" << endl; - cout << " " << stats.regioncache.read << " read " << stats.regioncache.skipped << " skipped " << stats.regioncache.missing << " missing " - << stats.regioncache.reqmissing << " reqmissing " << stats.regioncache.corrupt << " corrupt" << endl; -#if USE_MALLINFO - cout << "heap usage: " << stats.heapusage << " bytes" << endl; -#endif -} - -void runSingleThread(RenderJob& rj) -{ - cout << "single thread will render " << rj.stats.reqtilecount << " base tiles" << endl; - // allocate storage/caches - rj.regioncache.reset(new RegionCache(*rj.chunktable, *rj.regiontable, rj.inputpath, rj.fullrender, rj.stats.regioncache)); - rj.chunkcache.reset(new ChunkCache(*rj.chunktable, *rj.regiontable, *rj.regioncache, rj.inputpath, rj.fullrender, rj.regionformat, rj.stats.chunkcache)); - rj.tilecache.reset(new TileCache(rj.mp)); - rj.scenegraph.reset(new SceneGraph); - RGBAImage topimg; - // render the tiles recursively (starting at the very top) - renderZoomTile(ZoomTileIdx(0,0,0), rj, topimg); - // get memory stats - rj.stats.heapusage = getHeapUsage(); -} - -struct WorkerThreadParams -{ - RenderJob *rj; - ThreadOutputCache *tocache; - vector zoomtiles; // tiles that this thread is responsible for -}; - -void *runWorkerThread(void *arg) -{ - WorkerThreadParams *wtp = (WorkerThreadParams*)arg; - for (vector::const_iterator it = wtp->zoomtiles.begin(); it != wtp->zoomtiles.end(); it++) - { - int idx = wtp->tocache->getIndex(*it); - wtp->tocache->used[idx] = renderZoomTile(*it, *wtp->rj, wtp->tocache->images[idx]); - } - return 0; -} - -// see if there's enough available memory for some number of tile images -// (...by just attempting to allocate it!) -//!!!!!!! better way to do this? maybe allow user to specify max memory for -// ThreadOutputCache instead? -bool memoryAvailable(int tiles, const MapParams& mp) -{ - int64_t imgsize = mp.tileSize() * mp.tileSize(); // in pixels - try - { - RGBAPixel *tempbuf = new RGBAPixel[imgsize * tiles]; - delete[] tempbuf; - } - catch (bad_alloc& ba) - { - return false; - } - return true; -} - -// returns zoom level chosen for partitioning -int assignThreadTasks(vector& wtps, const TileTable& ttable, const MapParams& mp, int threads) -{ - vector best_reqzoomtiles; - vector best_costs; - vector best_assignments; - double best_error = 1.1; - // start with zoom level 1 and go up from there - for (int zoom = 1; zoom <= mp.baseZoom; zoom++) - { - // find all zoom tiles at this level that need to be drawn (i.e. contain > 0 required base tiles), - // and their costs (number of required base tiles) - vector reqzoomtiles; - vector costs; - vector assignments; - int64_t size = (1 << zoom); - ZoomTileIdx zti(-1, -1, zoom); - for (zti.x = 0; zti.x < size; zti.x++) - for (zti.y = 0; zti.y < size; zti.y++) - { - int numreq = ttable.getNumRequired(zti, mp); - if (numreq > 0) - { - reqzoomtiles.push_back(zti); - costs.push_back(numreq); - } - } - // if there are too many tiles at this zoom level (that is, if the ThreadOutputCache wouldn't - // fit in memory), then forget it (and those above it, too) - if (!memoryAvailable(reqzoomtiles.size(), mp)) - break; - // compute a good schedule for this level and get its "error" (difference between max thread - // cost and min thread cost, as a fraction of max thread cost) - pair error = schedule(costs, assignments, threads); - // if the error is less than 5%, or under 50 tiles (for small worlds), that's good enough - bool stop = error.second < 0.05 || error.first < 50; - // if this error is the best so far, remember these tiles/assignments - if (error.second < best_error || stop) - { - best_reqzoomtiles = reqzoomtiles; - best_costs = costs; - best_assignments = assignments; - best_error = error.second; - } - if (stop) - break; - } - - // perform actual assignments - for (int i = 0; i < best_assignments.size(); i++) - { - wtps[best_assignments[i]].zoomtiles.push_back(best_reqzoomtiles[i]); - wtps[best_assignments[i]].rj->stats.reqtilecount += best_costs[i]; - } - - return best_reqzoomtiles.front().zoom; -} - -void runMultithreaded(RenderJob& rj, int threads) -{ - // create a separate RenderJob for each thread; each one gets its own copy of the parameters, - // plus its own storage (caches, scenegraph, etc.) - RenderJob *rjs = new RenderJob[threads]; - arrayDeleter adrj(rjs); - for (int i = 0; i < threads; i++) - { - rjs[i].testmode = rj.testmode; - rjs[i].fullrender = rj.fullrender; - rjs[i].regionformat = rj.regionformat; - rjs[i].mp = rj.mp; - rjs[i].inputpath = rj.inputpath; - rjs[i].outputpath = rj.outputpath; - rjs[i].blockimages = rj.blockimages; - rjs[i].chunktable.reset(new ChunkTable); - rjs[i].chunktable->copyFrom(*rj.chunktable); - rjs[i].tiletable.reset(new TileTable); - rjs[i].tiletable->copyFrom(*rj.tiletable); - rjs[i].regiontable.reset(new RegionTable); - rjs[i].regiontable->copyFrom(*rj.regiontable); - if (!rjs[i].testmode) - { - rjs[i].regioncache.reset(new RegionCache(*rjs[i].chunktable, *rjs[i].regiontable, rjs[i].inputpath, rjs[i].fullrender, rjs[i].stats.regioncache)); - rjs[i].chunkcache.reset(new ChunkCache(*rjs[i].chunktable, *rjs[i].regiontable, *rjs[i].regioncache, rjs[i].inputpath, rjs[i].fullrender, rjs[i].regionformat, rjs[i].stats.chunkcache)); - rjs[i].scenegraph.reset(new SceneGraph); - } - rjs[i].tilecache.reset(new TileCache(rjs[i].mp)); - } - - // divide the required tiles evenly among the threads: find a zoom level that has enough tiles for us - // to make a balanced assignment, then give each thread some tiles from that level - vector wtps(threads); - for (int i = 0; i < threads; i++) - wtps[i].rj = &rjs[i]; - int threadzoom = assignThreadTasks(wtps, *rj.tiletable, rj.mp, threads); - for (int i = 0; i < threads; i++) - cout << "thread " << i << " will render " << rjs[i].stats.reqtilecount << " base tiles" << endl; - - // allocate storage for the threads to store their rendered zoom tiles into - // (doesn't need to be synchronized, because threads only touch the images for their own - // zoom tiles) - auto_ptr tocache(new ThreadOutputCache(threadzoom)); - for (int i = 0; i < threads; i++) - { - wtps[i].tocache = tocache.get(); - for (vector::const_iterator it = wtps[i].zoomtiles.begin(); it != wtps[i].zoomtiles.end(); it++) - { - int idx = tocache->getIndex(*it); - tocache->images[idx].create(rj.mp.tileSize(), rj.mp.tileSize()); // reserve the memory - } - } - - // run the threads; each one renders all the zoom tiles assigned to it - cout << "running threads..." << endl; - vector pthrs(threads); - for (int i = 0; i < threads; i++) - { - if (0 != pthread_create(&pthrs[i], NULL, runWorkerThread, (void*)&wtps[i])) - cerr << "failed to create thread!" << endl; - } - for (int i = 0; i < threads; i++) - { - pthread_join(pthrs[i], NULL); - } - - // now that the threads are done, render the final zoom levels (the ones above the ThreadOutputCache level) - cout << "finishing top zoom levels..." << endl; - rj.tilecache.reset(new TileCache(rj.mp)); - RGBAImage topimg; - renderZoomTile(ZoomTileIdx(0,0,0), rj, topimg, *tocache); - - // combine the thread stats - for (int i = 0; i < threads; i++) - { - rj.stats.chunkcache += rjs[i].stats.chunkcache; - rj.stats.regioncache += rjs[i].stats.regioncache; - } - rj.stats.heapusage = getHeapUsage(); - - // copy the drawn flags over from the thread TileTables (for the double-check) - for (RequiredTileIterator it(*rj.tiletable); !it.end; it.advance()) - { - for (int i = 0; i < threads; i++) - if (rjs[i].tiletable->isDrawn(it.current)) - { - rj.tiletable->setDrawn(it.current); - break; - } - } -} - -bool expandMap(const string& outputpath) -{ - // read old params - MapParams mp; - if (!mp.readFile(outputpath)) - { - cerr << "pigmap.params missing or corrupt" << endl; - return false; - } - int32_t tileSize = mp.tileSize(); - - // to expand a map, the following must be done: - // 1. the top-left quadrant of the current zoom level 1 needs to be moved to zoom level 2, where - // it will become the bottom-right quadrant of the top-left quadrant of the new zoom level 1, - // so the top-level file "0.png" and subdirectory "0" must become "0/3.png" and "0/3", - // respectively; and similarly for the other three quadrants - // 2. new zoom level 1 tiles must be created: "0.png" is 3/4 empty, but has a shrunk version of - // the old "0.png" (which is the new "0/3.png") in its bottom-right, etc. - // 3. a new "base.png" must be created from the new zoom level 1 tiles - - // move everything at zoom 1 or higher one level deeper - // ...first the subdirectories - renameFile(outputpath + "/0", outputpath + "/old0"); - renameFile(outputpath + "/1", outputpath + "/old1"); - renameFile(outputpath + "/2", outputpath + "/old2"); - renameFile(outputpath + "/3", outputpath + "/old3"); - makePath(outputpath + "/0"); - makePath(outputpath + "/1"); - makePath(outputpath + "/2"); - makePath(outputpath + "/3"); - renameFile(outputpath + "/old0", outputpath + "/0/3"); - renameFile(outputpath + "/old1", outputpath + "/1/2"); - renameFile(outputpath + "/old2", outputpath + "/2/1"); - renameFile(outputpath + "/old3", outputpath + "/3/0"); - // ...now the zoom 1 files - renameFile(outputpath + "/0.png", outputpath + "/0/3.png"); - renameFile(outputpath + "/1.png", outputpath + "/1/2.png"); - renameFile(outputpath + "/2.png", outputpath + "/2/1.png"); - renameFile(outputpath + "/3.png", outputpath + "/3/0.png"); - - // build the new zoom 1 tiles - RGBAImage old0img; - bool used0 = old0img.readPNG(outputpath + "/0/3.png"); - RGBAImage new0img; - new0img.create(tileSize, tileSize); - if (used0) - { - reduceHalf(new0img, ImageRect(tileSize/2, tileSize/2, tileSize/2, tileSize/2), old0img); - new0img.writePNG(outputpath + "/0.png"); - } - RGBAImage old1img; - bool used1 = old1img.readPNG(outputpath + "/1/2.png"); - RGBAImage new1img; - new1img.create(tileSize, tileSize); - if (used1) - { - reduceHalf(new1img, ImageRect(0, tileSize/2, tileSize/2, tileSize/2), old1img); - new1img.writePNG(outputpath + "/1.png"); - } - RGBAImage old2img; - bool used2 = old2img.readPNG(outputpath + "/2/1.png"); - RGBAImage new2img; - new2img.create(tileSize, tileSize); - if (used2) - { - reduceHalf(new2img, ImageRect(tileSize/2, 0, tileSize/2, tileSize/2), old2img); - new2img.writePNG(outputpath + "/2.png"); - } - RGBAImage old3img; - bool used3 = old3img.readPNG(outputpath + "/3/0.png"); - RGBAImage new3img; - new3img.create(tileSize, tileSize); - if (used3) - { - reduceHalf(new3img, ImageRect(0, 0, tileSize/2, tileSize/2), old3img); - new3img.writePNG(outputpath + "/3.png"); - } - - // build the new base tile - RGBAImage newbase; - newbase.create(tileSize, tileSize); - if (used0) - reduceHalf(newbase, ImageRect(0, 0, tileSize/2, tileSize/2), new0img); - if (used1) - reduceHalf(newbase, ImageRect(tileSize/2, 0, tileSize/2, tileSize/2), new1img); - if (used2) - reduceHalf(newbase, ImageRect(0, tileSize/2, tileSize/2, tileSize/2), new2img); - if (used3) - reduceHalf(newbase, ImageRect(tileSize/2, tileSize/2, tileSize/2, tileSize/2), new3img); - newbase.writePNG(outputpath + "/base.png"); - - // write new params (with incremented baseZoom) - mp.baseZoom++; - mp.writeFile(outputpath); - - // touch all tiles, to prevent browser cache mishaps (since many new tiles will have the same - // filename as some old tile, but possibly with an earlier timestamp) - system((string("find ") + outputpath + " -exec touch {} +").c_str()); - - return true; -} - -void writeHTML(const RenderJob& rj, const string& htmlpath) -{ - string templatePath = htmlpath + "/template.html"; - stringbuf strbuf; - ifstream infile(templatePath.c_str()); - infile.get(strbuf, 0); // get entire file (unless it happens to have a '\0' in it) - if (infile.fail()) - { - cerr << "couldn't find template.html" << endl; - return; - } - string templateText = strbuf.str(); - if (!replace(templateText, "{tileSize}", tostring(rj.mp.tileSize())) || - !replace(templateText, "{B}", tostring(rj.mp.B)) || - !replace(templateText, "{T}", tostring(rj.mp.T)) || - !replace(templateText, "{baseZoom}", tostring(rj.mp.baseZoom))) - { - cerr << "template.html is corrupt" << endl; - return; - } - string htmlOutPath = rj.outputpath + "/pigmap-default.html"; - ofstream outfile(htmlOutPath.c_str()); - outfile << templateText; - - copyFile(htmlpath + "/style.css", rj.outputpath + "/style.css"); -} - -bool performRender(const string& inputpath, const string& outputpath, const string& imgpath, const MapParams& mp, const string& chunklist, const string& regionlist, int threads, int testworldsize, bool expand, const string& htmlpath) -{ - time_t tstart = time(NULL); - - // prepare the rendering params and the chunk/tile tables - // ...note that mp.baseZoom might not be set yet if this is a full render; makeAllChunksRequired - // will handle it - RenderJob rj; - rj.testmode = testworldsize != -1; - rj.mp = mp; - rj.inputpath = inputpath; - rj.outputpath = outputpath; - if (!rj.blockimages.create(rj.mp.B, imgpath)) - { - cerr << "no block images available; aborting render" << endl; - return false; - } - rj.chunktable.reset(new ChunkTable); - rj.tiletable.reset(new TileTable); - rj.regiontable.reset(new RegionTable); - rj.regionformat = !rj.testmode && detectRegionFormat(rj.inputpath); - if (rj.regionformat) - cout << "region-format world detected" << endl; - else - cout << "no regions detected; assuming chunk-format world" << endl; - - // test world - if (testworldsize != -1) - { - rj.fullrender = true; - cout << "building test world..." << endl; - makeTestWorld(testworldsize, *rj.chunktable, *rj.tiletable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount); - } - // full render - else if (chunklist.empty() && regionlist.empty()) - { - rj.fullrender = true; - cout << "scanning world data..." << endl; - if (rj.regionformat) - { - if (!makeAllRegionsRequired(rj.inputpath, *rj.chunktable, *rj.tiletable, *rj.regiontable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount, rj.stats.reqregioncount)) - return false; - } - else - { - if (!makeAllChunksRequired(rj.inputpath, *rj.chunktable, *rj.tiletable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount)) - return false; - } - } - // incremental update - else - { - rj.fullrender = false; - int rv; - if (rj.regionformat) - { - cout << "processing regionlist..." << endl; - rv = readRegionlist(regionlist, rj.inputpath, *rj.chunktable, *rj.tiletable, *rj.regiontable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount, rj.stats.reqregioncount); - } - else - { - cout << "processing chunklist..." << endl; - rv = readChunklist(chunklist, *rj.chunktable, *rj.tiletable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount); - } - if (rv == -2) - return false; - // if we failed because baseZoom is too small, and -x was specified, expand the world and try once more - if (rv == -1 && expand) - { - if (!expandMap(rj.outputpath)) - return false; - rj.mp.baseZoom++; - cout << "baseZoom of output map has been increased to " << rj.mp.baseZoom << endl; - rj.chunktable.reset(new ChunkTable); - rj.tiletable.reset(new TileTable); - rj.regiontable.reset(new RegionTable); - if (rj.regionformat) - { - if (0 != readRegionlist(regionlist, rj.inputpath, *rj.chunktable, *rj.tiletable, *rj.regiontable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount, rj.stats.reqregioncount)) - return false; - } - else - { - if (0 != readChunklist(chunklist, *rj.chunktable, *rj.tiletable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount)) - return false; - } - } - } - - if (rj.stats.reqtilecount == 0) - { - cout << "nothing to do! (no required tiles)" << endl; - return true; - } - - // render stuff - cout << "rendering tiles..." << endl; - if (threads >= 2) - runMultithreaded(rj, threads); - else - runSingleThread(rj); - - // double-check that all the required tiles were drawn - cout << "performing double-check..." << endl; - for (RequiredTileIterator it(*rj.tiletable); !it.end; it.advance()) - { - if (!rj.tiletable->isDrawn(it.current)) - cerr << "required tile " << it.current.toTileIdx().toFilePath(rj.mp) << " was somehow not drawn!" << endl; - } - - // write map params, HTML - if (!rj.testmode) - { - rj.mp.writeFile(rj.outputpath); - writeHTML(rj, htmlpath); - } - - // done; print stats - time_t tfinish = time(NULL); - printStats(tfinish - tstart, rj.stats); - return true; -} - -//------------------------------------------------------------------------------------------------------------------- - -// warning: slow -void testTileBBoxes(const MapParams& mp) -{ - // check tile bounding boxes for a few tiles - for (int64_t tx = -5; tx <= 5; tx++) - for (int64_t ty = -5; ty <= 5; ty++) - { - // get computed BBox - TileIdx ti(tx,ty); - BBox bbox = ti.getBBox(mp); - - // this is what the box is supposed to be - int64_t xmin = 64*mp.B*mp.T*tx - 2*mp.B; - int64_t ymax = 64*mp.B*mp.T*ty + 17*mp.B; - int64_t xmax = xmin + mp.tileSize(); - int64_t ymin = ymax - mp.tileSize(); - - // test pixels - for (int64_t x = xmin - 15; x <= xmax + 15; x++) - for (int64_t y = ymin - 15; y <= ymax + 15; y++) - { - bool result = bbox.includes(Pixel(x,y)); - bool expected = x >= xmin && x < xmax && y >= ymin && y < ymax; - if (result != expected) - { - cout << "failed tile bounding box test! " << tx << " " << ty << endl; - cout << "[" << bbox.topLeft.x << "," << bbox.topLeft.y << "] to [" << bbox.bottomRight.x << "," << bbox.bottomRight.y << "]" << endl; - cout << "[" << xmin << "," << ymin << "] to [" << xmax << "," << ymax << "]" << endl; - cout << x << "," << y << endl; - return; - } - } - } -} - -// warning: slow -void testMath() -{ - // vary map params: block size B from 2 to 6, tile multiplier T from 1 to 4 - MapParams mp(0,0,0); - for (mp.B = 2; mp.B <= 6; mp.B++) - for (mp.T = 1; mp.T <= 4; mp.T++) - { - cout << "B = " << mp.B << " T = " << mp.T << endl; - - testTileBBoxes(mp); - } -} - -void testBase36() -{ - for (int64_t i = -2473; i <= 1472; i += 93) - cout << i << " " << toBase36(i) << " " << fromBase36(toBase36(i)) << endl; - for (int64_t x = -123; x <= 201; x += 45) - for (int64_t z = -239; z <= 196; z += 57) - { - ChunkIdx ci(x,z); - string filepath = ci.toFilePath(); - ChunkIdx ci2(-999999,-999999); - if (ChunkIdx::fromFilePath(filepath, ci2)) - cout << "[" << x << "," << z << "] " << filepath << " [" << ci2.x << "," << ci2.z << "]" << endl; - else - { - cout << "failed to get ChunkIdx from filename: " << filepath << endl; - return; - } - } -} - -void testMod64() -{ - for (int64_t i = -135; i < 135; i++) - cout << i << " mod64: " << mod64pos(i) << " base36: " << toBase36(mod64pos(i)) << endl; -} - -struct compareChunks -{ - bool operator()(const ChunkIdx& ci1, const ChunkIdx& ci2) const {if (ci1.x == ci2.x) return ci1.z < ci2.z; return ci1.x < ci2.x;} -}; - -// warning: slow; only use with small worlds -void testChunkTable(const string& inputpath) -{ - vector chunkpaths; - findAllChunks(inputpath, chunkpaths); - - auto_ptr chunktable(new ChunkTable); - auto_ptr tiletable(new TileTable); - int64_t reqchunkcount, reqtilecount; - MapParams mp(6,1,-1); - makeAllChunksRequired(inputpath, *chunktable, *tiletable, mp, reqchunkcount, reqtilecount); - - // make sure all chunks in the file list are present and marked required in the ChunkTable - // ...also build list of ChunkIdxs for comparison with the RequiredChunkIterator later - set chunklist; - for (vector::const_iterator it = chunkpaths.begin(); it != chunkpaths.end(); it++) - { - ChunkIdx ci(0,0); - if (ChunkIdx::fromFilePath(*it, ci)) - { - chunklist.insert(ci); - if (!chunktable->isRequired(ci)) - { - cout << "chunk file " << *it << " is not marked as required!" << endl; - return; - } - } - } - - // iterate over the required chunks in the ChunkTable and make sure each one is present in the file list - // ...also compute the total size of the ChunkTable - int lastcgi = -1, lastcsi = -1; - int64_t level3size = sizeof(ChunkTable); - int64_t level2size = 0; - int64_t level1size = 0; - int64_t chunkcount = 0; - for (RequiredChunkIterator it(*chunktable); !it.end; it.advance()) - { - ChunkIdx ci = it.current.toChunkIdx(); - if (chunklist.find(ci) == chunklist.end()) - { - cout << "chunk [" << ci.x << "," << ci.z << "] was iterated over, but is not in file list!" << endl; - return; - } - - chunkcount++; - if (it.cgi != lastcgi || it.csi != lastcsi) - { - level1size += sizeof(ChunkSet); - if (it.cgi != lastcgi) - level2size += sizeof(ChunkGroup); - lastcgi = it.cgi; - lastcsi = it.csi; - } - } - cout << "world size: " << chunkcount << " chunks" << endl; - cout << "ChunkTable size: " << level3size << " + " << level2size << " + " << level1size << " bytes" << endl; -} - -void testPNG() -{ - RGBAImage img; - img.create(100, 100); - for (vector::iterator it = img.data.begin(); it != img.data.end(); it++) - { - *it = ((rand() % 256) << 24) | ((rand() % 256) << 16) | ((rand() % 256) << 8) | (rand() % 256); - } - img.writePNG("test.png"); - - RGBAImage img2; - img2.readPNG("test.png"); - if (img2.data != img.data) - cout << "images don't match after trip through PNG!" << endl; - else - cout << "PNG test successful" << endl; -} - -struct compareTiles -{ - bool operator()(const TileIdx& ti1, const TileIdx& ti2) const {if (ti1.x == ti2.x) return ti1.y < ti2.y; return ti1.x < ti2.x;} -}; - -void testIterators(const string& inputpath) -{ - MapParams mp(3,2,10); - auto_ptr chunktable(new ChunkTable); - auto_ptr tiletable(new TileTable); - int64_t reqchunkcount, reqtilecount; - makeAllChunksRequired(inputpath, *chunktable, *tiletable, mp, reqchunkcount, reqtilecount); - - // make sure the RequiredChunkIterator and RequiredTileIterator compute the same results - set tiles1, tiles2; - - for (RequiredChunkIterator it(*chunktable); !it.end; it.advance()) - { - ChunkIdx ci = it.current.toChunkIdx(); - vector tiles = ci.getTiles(mp); - for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) - tiles1.insert(*tile); - } - - for (RequiredTileIterator it(*tiletable); !it.end; it.advance()) - { - tiles2.insert(it.current.toTileIdx()); - } - - if (tiles1 != tiles2) - cout << "iterators don't match!" << endl; - else - cout << "iterators match" << endl; -} - -void testZOrder() -{ - int SIZE = 64; - vector hits1(SIZE*SIZE, 0), hits2(SIZE*SIZE, 0); - for (int i = 0; i < SIZE*SIZE; i++) - { - hits1[toZOrder(i, SIZE)]++; - hits2[fromZOrder(i, SIZE)]++; - } - for (int i = 0; i < SIZE*SIZE; i++) - if (hits1[i] != 1 || hits2[i] != 1) - cout << "position " << i << " was hit " << hits1[i] << ", " << hits2[i] << " times!" << endl; -} - -void testTileIdxs() -{ - for (int baseZoom = 3; baseZoom < 11; baseZoom++) - { - MapParams mp(6,1,baseZoom); - for (int z = 0; z < 4; z++) - for (int x = 0; x < (1 << z); x++) - for (int y = 0; y < (1 << z); y++) - { - ZoomTileIdx zti(x, y, z); - TileIdx ti = zti.toTileIdx(mp); - ZoomTileIdx zti2 = zti.toZoom(baseZoom); - TileIdx ti2 = zti2.toTileIdx(mp); - if (ti != ti2) - cout << "mismatch! baseZoom " << baseZoom << " zoom tile [" << zti.x << "," - << zti.y << "] @ " << zti.zoom << endl; - } - } -} - -void testReqTileCount(const string& inputpath) -{ - MapParams mp(6,1,10); - auto_ptr chunktable(new ChunkTable); - auto_ptr tiletable(new TileTable); - int64_t reqchunkcount, reqtilecount; - makeAllChunksRequired(inputpath, *chunktable, *tiletable, mp, reqchunkcount, reqtilecount); - - cout << "required base tiles: " << reqtilecount << endl; - for (int z = 0; z <= mp.baseZoom; z++) - { - int64_t count = 0; - for (int x = 0; x < (1 << z); x++) - for (int y = 0; y < (1 << z); y++) - count += tiletable->getNumRequired(ZoomTileIdx(x, y, z), mp); - if (count != reqtilecount) - cout << "tile counts don't match for zoom " << z << "!" << endl; - else - cout << "tile counts okay for zoom " << z << endl; - } -} - -void testResize() -{ - int sourceSize = 16; - for (int B = 2; B <= 16; B++) - { - int destSize = 2*B; - cout << "====== B: " << B << " destSize: " << destSize << " sourceSize: " << sourceSize << " ======" << endl; - for (int i = 0; i < destSize; i++) - { - int j = interpolate(i, destSize, sourceSize); - cout << i << " --> " << j << endl; - } - } -} - -//------------------------------------------------------------------------------------------------------------------- - -bool validateParamsFull(const string& inputpath, const string& outputpath, const string& imgpath, const MapParams& mp, int threads, const string& chunklist, const string& regionlist, bool expand, const string& htmlpath) -{ - // -c and -x are not allowed for full renders - if (!chunklist.empty() || !regionlist.empty() || expand) - { - cerr << "-c, -r, -x not allowed for full renders" << endl; - return false; - } - - // B and T must be within range (upper limits aren't really necessary and can be adjusted if - // someone really wants gigantic tile images for some reason) - if (!mp.valid()) - { - cerr << "-B must be in range 2-16; -T must be in range 1-16" << endl; - return false; - } - - // baseZoom must be within range, or -1 (omitted) - if (!mp.validZoom() && mp.baseZoom != -1) - { - cerr << "-Z must be in range 0-30, or may be omitted to set automatically" << endl; - return false; - } - - // MINY/MAXY must describe a valid range - if (!mp.validYRange()) - { - cerr << "-y and -Y, if used, must be in range 0-255, and -y must be <= -Y" << endl; - return false; - } - - // must have a sensible number of threads (upper limit is arbitrary, but you'd need a truly - // insanely large map to see any benefit to having that many...) - if (threads < 1 || threads > 64) - { - cerr << "-h must be in range 1-64" << endl; - return false; - } - - // the various paths must be non-empty - if (inputpath.empty() || outputpath.empty()) - { - cerr << "must provide both input (-i) and output (-o) paths" << endl; - return false; - } - if (imgpath.empty()) - { - cerr << "must provide non-empty image path, or omit -g to use \".\"" << endl; - return false; - } - if (htmlpath.empty()) - { - cerr << "must provide non-empty HTML path, or omit -m to use \".\"" << endl; - return false; - } - - return true; -} - -// also sets MapParams to values from existing map -bool validateParamsIncremental(const string& inputpath, const string& outputpath, const string& imgpath, MapParams& mp, int threads, const string& chunklist, const string& regionlist, bool expand, const string& htmlpath) -{ - // -B, -T, -Z, -y, -Y are not allowed - if (mp.B != -1 || mp.T != -1 || mp.baseZoom != -1 || mp.userMinY || mp.userMaxY) - { - cerr << "-B, -T, -Z, -y, -Y not allowed for incremental updates" << endl; - return false; - } - - // the various paths must be non-empty - if (inputpath.empty() || outputpath.empty()) - { - cerr << "must provide both input (-i) and output (-o) paths" << endl; - return false; - } - if (imgpath.empty()) - { - cerr << "must provide non-empty image path, or omit -g to use \".\"" << endl; - return false; - } - if (htmlpath.empty()) - { - cerr << "must provide non-empty HTML path, or omit -m to use \".\"" << endl; - return false; - } - - // can't have both chunklist and regionlist - if (!chunklist.empty() && !regionlist.empty()) - { - cerr << "only one of -c, -r may be used" << endl; - return false; - } - - // if world is in region format, must use regionlist - if (detectRegionFormat(inputpath) && regionlist.empty()) - { - cerr << "world is in region format; must use -r, not -c" << endl; - return false; - } - - // pigmap.params must be present in output path; read it now - if (!mp.readFile(outputpath)) - { - cerr << "can't find pigmap.params in output path" << endl; - return false; - } - - // must have a sensible number of threads (upper limit is arbitrary, but you'd need a truly - // insanely large map to see any benefit to having that many...) - if (threads < 1 || threads > 64) - { - cerr << "-h must be in range 1-64" << endl; - return false; - } - - return true; -} - -bool validateParamsTest(const string& inputpath, const string& outputpath, const string& imgpath, const MapParams& mp, int threads, const string& chunklist, const string& regionlist, bool expand, const string& htmlpath, int testworldsize) -{ - // -i, -o, -c, -r, -x, -m are not allowed - if (!inputpath.empty() || !outputpath.empty() || !chunklist.empty() || !regionlist.empty() || expand || htmlpath != ".") - { - cerr << "-i, -o, -c, -r, -x, -m not allowed for test worlds" << endl; - return false; - } - - // B and T must be within range (upper limits aren't really necessary and can be adjusted if - // someone really wants gigantic tile images for some reason) - if (!mp.valid()) - { - cerr << "-B must be in range 2-16; -T must be in range 1-16" << endl; - return false; - } - - // baseZoom must be within range, or -1 (omitted) - if (!mp.validZoom() && mp.baseZoom != -1) - { - cerr << "-Z must be in range 0-30, or may be omitted to set automatically" << endl; - return false; - } - - // MINY/MAXY must describe a valid range - if (!mp.validYRange()) - { - cerr << "-y and -Y, if used, must be in range 0-255, and -y must be <= -Y" << endl; - return false; - } - - // must have a sensible number of threads (upper limit is arbitrary, but you'd need a truly - // insanely large map to see any benefit to having that many...) - if (threads < 1 || threads > 64) - { - cerr << "-h must be in range 1-64" << endl; - return false; - } - - // image path must be non-empty - if (imgpath.empty()) - { - cerr << "must provide non-empty image path, or omit -g to use \".\"" << endl; - return false; - } - - // test world size must be positive - if (testworldsize < 0) - { - cerr << "testworld size must be positive" << endl; - return false; - } - - return true; -} - -int main(int argc, char **argv) -{ - //testMath(); - //testBase36(); - //testMod64(); - //testChunkTable(inputpath); - //testTileIterator(); - //testPColIterator(); - //testChunkCache(); - //testPNG(); - //testIterators(inputpath); - //testZOrder(); - //testTileIdxs(); - //testReqTileCount(inputpath); - //testResize(); - - string inputpath, outputpath, imgpath = ".", chunklist, regionlist, htmlpath = "."; - MapParams mp(-1,-1,-1); - int threads = 1; - int testworldsize = -1; - bool expand = false; - - int c; - while ((c = getopt(argc, argv, "i:o:g:c:B:T:Z:h:w:xm:r:y:Y:")) != -1) - { - switch (c) - { - case 'i': - inputpath = optarg; - break; - case 'o': - outputpath = optarg; - break; - case 'g': - imgpath = optarg; - break; - case 'c': - chunklist = optarg; - break; - case 'r': - regionlist = optarg; - break; - case 'B': - mp.B = atoi(optarg); - break; - case 'T': - mp.T = atoi(optarg); - break; - case 'Z': - mp.baseZoom = atoi(optarg); - break; - case 'y': - mp.minY = atoi(optarg); - mp.userMinY = true; - break; - case 'Y': - mp.maxY = atoi(optarg); - mp.userMaxY = true; - break; - case 'h': - threads = atoi(optarg); - break; - case 'x': - expand = true; - break; - case 'm': - htmlpath = optarg; - break; - case 'w': - testworldsize = atoi(optarg); - break; - case '?': - cerr << "-" << (char)optopt << ": unrecognized option or missing argument" << endl; - return 1; - default: // should never happen (?) - cerr << "getopt not working?" << endl; - return 1; - } - } - - if (testworldsize != -1) - { - if (!validateParamsTest(inputpath, outputpath, imgpath, mp, threads, chunklist, regionlist, expand, htmlpath, testworldsize)) - return 1; - } - else if (chunklist.empty() && regionlist.empty()) - { - if (!validateParamsFull(inputpath, outputpath, imgpath, mp, threads, chunklist, regionlist, expand, htmlpath)) - return 1; - } - else - { - if (!validateParamsIncremental(inputpath, outputpath, imgpath, mp, threads, chunklist, regionlist, expand, htmlpath)) - return 1; - } - - if (!performRender(inputpath, outputpath, imgpath, mp, chunklist, regionlist, threads, testworldsize, expand, htmlpath)) - return 1; - - return 0; -} +// Copyright 2010, 2011 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +// +// +// TODO: +// -cosmetic things: +// -have signs actually face correct directions +// -edge shadows on vertical edges, too? +// -proper redstone wire directions +// -better dragon egg +// -extended pistons +// -premultiply block image alphas? +// -dump list of corrupted chunks at end, so they can be retried later +// -keep some space around for PNG row pointers instead of allocating every time +// -see if it's better to have threads work together and share a cache? +// -for the love of god, clean up blockimages.cpp! +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "blockimages.h" +#include "rgba.h" +#include "map.h" +#include "utils.h" +#include "tables.h" +#include "chunk.h" +#include "render.h" +#include "world.h" + +using namespace std; + + + +//------------------------------------------------------------------------------------------------------------------- + +void printStats(int seconds, const RenderStats& stats) +{ + cout << stats.reqchunkcount << " chunks " << stats.reqregioncount << " regions " + << stats.reqtilecount << " base tiles " << seconds << " seconds" << endl; + cout << "chunk cache: " << stats.chunkcache.hits << " hits " << stats.chunkcache.misses << " misses" << endl; + cout << " " << stats.chunkcache.read << " read " << stats.chunkcache.skipped << " skipped " << stats.chunkcache.missing << " missing " + << stats.chunkcache.reqmissing << " reqmissing " << stats.chunkcache.corrupt << " corrupt" << endl; + cout << "region cache: " << stats.regioncache.hits << " hits " << stats.regioncache.misses << " misses" << endl; + cout << " " << stats.regioncache.read << " read " << stats.regioncache.skipped << " skipped " << stats.regioncache.missing << " missing " + << stats.regioncache.reqmissing << " reqmissing " << stats.regioncache.corrupt << " corrupt" << endl; +#if USE_MALLINFO + cout << "heap usage: " << stats.heapusage << " bytes" << endl; +#endif +} + +void runSingleThread(RenderJob& rj) +{ + cout << "single thread will render " << rj.stats.reqtilecount << " base tiles" << endl; + // allocate storage/caches + rj.regioncache.reset(new RegionCache(*rj.chunktable, *rj.regiontable, rj.inputpath, rj.fullrender, rj.stats.regioncache)); + rj.chunkcache.reset(new ChunkCache(*rj.chunktable, *rj.regiontable, *rj.regioncache, rj.inputpath, rj.fullrender, rj.regionformat, rj.stats.chunkcache)); + rj.tilecache.reset(new TileCache(rj.mp)); + rj.scenegraph.reset(new SceneGraph); + RGBAImage topimg; + // render the tiles recursively (starting at the very top) + renderZoomTile(ZoomTileIdx(0,0,0), rj, topimg); + // get memory stats + rj.stats.heapusage = getHeapUsage(); +} + +struct WorkerThreadParams +{ + RenderJob *rj; + ThreadOutputCache *tocache; + vector zoomtiles; // tiles that this thread is responsible for +}; + +void *runWorkerThread(void *arg) +{ + WorkerThreadParams *wtp = (WorkerThreadParams*)arg; + for (vector::const_iterator it = wtp->zoomtiles.begin(); it != wtp->zoomtiles.end(); it++) + { + int idx = wtp->tocache->getIndex(*it); + wtp->tocache->used[idx] = renderZoomTile(*it, *wtp->rj, wtp->tocache->images[idx]); + } + return 0; +} + +// see if there's enough available memory for some number of tile images +// (...by just attempting to allocate it!) +//!!!!!!! better way to do this? maybe allow user to specify max memory for +// ThreadOutputCache instead? +bool memoryAvailable(int tiles, const MapParams& mp) +{ + int64_t imgsize = mp.tileSize() * mp.tileSize(); // in pixels + try + { + RGBAPixel *tempbuf = new RGBAPixel[imgsize * tiles]; + delete[] tempbuf; + } + catch (bad_alloc& ba) + { + return false; + } + return true; +} + +// returns zoom level chosen for partitioning +int assignThreadTasks(vector& wtps, const TileTable& ttable, const MapParams& mp, int threads) +{ + vector best_reqzoomtiles; + vector best_costs; + vector best_assignments; + double best_error = 1.1; + // start with zoom level 1 and go up from there + for (int zoom = 1; zoom <= mp.baseZoom; zoom++) + { + // find all zoom tiles at this level that need to be drawn (i.e. contain > 0 required base tiles), + // and their costs (number of required base tiles) + vector reqzoomtiles; + vector costs; + vector assignments; + int64_t size = (1 << zoom); + ZoomTileIdx zti(-1, -1, zoom); + for (zti.x = 0; zti.x < size; zti.x++) + for (zti.y = 0; zti.y < size; zti.y++) + { + int numreq = ttable.getNumRequired(zti, mp); + if (numreq > 0) + { + reqzoomtiles.push_back(zti); + costs.push_back(numreq); + } + } + // if there are too many tiles at this zoom level (that is, if the ThreadOutputCache wouldn't + // fit in memory), then forget it (and those above it, too) + if (!memoryAvailable(reqzoomtiles.size(), mp)) + break; + // compute a good schedule for this level and get its "error" (difference between max thread + // cost and min thread cost, as a fraction of max thread cost) + pair error = schedule(costs, assignments, threads); + // if the error is less than 5%, or under 50 tiles (for small worlds), that's good enough + bool stop = error.second < 0.05 || error.first < 50; + // if this error is the best so far, remember these tiles/assignments + if (error.second < best_error || stop) + { + best_reqzoomtiles = reqzoomtiles; + best_costs = costs; + best_assignments = assignments; + best_error = error.second; + } + if (stop) + break; + } + + // perform actual assignments + for (uint i = 0; i < best_assignments.size(); i++) + { + wtps[best_assignments[i]].zoomtiles.push_back(best_reqzoomtiles[i]); + wtps[best_assignments[i]].rj->stats.reqtilecount += best_costs[i]; + } + + return best_reqzoomtiles.front().zoom; +} + +void runMultithreaded(RenderJob& rj, int threads) +{ + // create a separate RenderJob for each thread; each one gets its own copy of the parameters, + // plus its own storage (caches, scenegraph, etc.) + RenderJob *rjs = new RenderJob[threads]; + arrayDeleter adrj(rjs); + for (int i = 0; i < threads; i++) + { + rjs[i].testmode = rj.testmode; + rjs[i].fullrender = rj.fullrender; + rjs[i].regionformat = rj.regionformat; + rjs[i].mp = rj.mp; + rjs[i].inputpath = rj.inputpath; + rjs[i].outputpath = rj.outputpath; + rjs[i].blockimages = rj.blockimages; + rjs[i].chunktable.reset(new ChunkTable); + rjs[i].chunktable->copyFrom(*rj.chunktable); + rjs[i].tiletable.reset(new TileTable); + rjs[i].tiletable->copyFrom(*rj.tiletable); + rjs[i].regiontable.reset(new RegionTable); + rjs[i].regiontable->copyFrom(*rj.regiontable); + if (!rjs[i].testmode) + { + rjs[i].regioncache.reset(new RegionCache(*rjs[i].chunktable, *rjs[i].regiontable, rjs[i].inputpath, rjs[i].fullrender, rjs[i].stats.regioncache)); + rjs[i].chunkcache.reset(new ChunkCache(*rjs[i].chunktable, *rjs[i].regiontable, *rjs[i].regioncache, rjs[i].inputpath, rjs[i].fullrender, rjs[i].regionformat, rjs[i].stats.chunkcache)); + rjs[i].scenegraph.reset(new SceneGraph); + } + rjs[i].tilecache.reset(new TileCache(rjs[i].mp)); + } + + // divide the required tiles evenly among the threads: find a zoom level that has enough tiles for us + // to make a balanced assignment, then give each thread some tiles from that level + vector wtps(threads); + for (int i = 0; i < threads; i++) + wtps[i].rj = &rjs[i]; + int threadzoom = assignThreadTasks(wtps, *rj.tiletable, rj.mp, threads); + for (int i = 0; i < threads; i++) + cout << "thread " << i << " will render " << rjs[i].stats.reqtilecount << " base tiles" << endl; + + // allocate storage for the threads to store their rendered zoom tiles into + // (doesn't need to be synchronized, because threads only touch the images for their own + // zoom tiles) + auto_ptr tocache(new ThreadOutputCache(threadzoom)); + for (int i = 0; i < threads; i++) + { + wtps[i].tocache = tocache.get(); + for (vector::const_iterator it = wtps[i].zoomtiles.begin(); it != wtps[i].zoomtiles.end(); it++) + { + int idx = tocache->getIndex(*it); + tocache->images[idx].create(rj.mp.tileSize(), rj.mp.tileSize()); // reserve the memory + } + } + + // run the threads; each one renders all the zoom tiles assigned to it + cout << "running threads..." << endl; + vector pthrs(threads); + for (int i = 0; i < threads; i++) + { + if (0 != pthread_create(&pthrs[i], NULL, runWorkerThread, (void*)&wtps[i])) + cerr << "failed to create thread!" << endl; + } + for (int i = 0; i < threads; i++) + { + pthread_join(pthrs[i], NULL); + } + + // now that the threads are done, render the final zoom levels (the ones above the ThreadOutputCache level) + cout << "finishing top zoom levels..." << endl; + rj.tilecache.reset(new TileCache(rj.mp)); + RGBAImage topimg; + renderZoomTile(ZoomTileIdx(0,0,0), rj, topimg, *tocache); + + // combine the thread stats + for (int i = 0; i < threads; i++) + { + rj.stats.chunkcache += rjs[i].stats.chunkcache; + rj.stats.regioncache += rjs[i].stats.regioncache; + } + rj.stats.heapusage = getHeapUsage(); + + // copy the drawn flags over from the thread TileTables (for the double-check) + for (RequiredTileIterator it(*rj.tiletable); !it.end; it.advance()) + { + for (int i = 0; i < threads; i++) + if (rjs[i].tiletable->isDrawn(it.current)) + { + rj.tiletable->setDrawn(it.current); + break; + } + } +} + +bool expandMap(const string& outputpath) +{ + // read old params + MapParams mp; + if (!mp.readFile(outputpath)) + { + cerr << "pigmap.params missing or corrupt" << endl; + return false; + } + int32_t tileSize = mp.tileSize(); + + // to expand a map, the following must be done: + // 1. the top-left quadrant of the current zoom level 1 needs to be moved to zoom level 2, where + // it will become the bottom-right quadrant of the top-left quadrant of the new zoom level 1, + // so the top-level file "0.png" and subdirectory "0" must become "0/3.png" and "0/3", + // respectively; and similarly for the other three quadrants + // 2. new zoom level 1 tiles must be created: "0.png" is 3/4 empty, but has a shrunk version of + // the old "0.png" (which is the new "0/3.png") in its bottom-right, etc. + // 3. a new "base.png" must be created from the new zoom level 1 tiles + + // move everything at zoom 1 or higher one level deeper + // ...first the subdirectories + renameFile(outputpath + "/0", outputpath + "/old0"); + renameFile(outputpath + "/1", outputpath + "/old1"); + renameFile(outputpath + "/2", outputpath + "/old2"); + renameFile(outputpath + "/3", outputpath + "/old3"); + makePath(outputpath + "/0"); + makePath(outputpath + "/1"); + makePath(outputpath + "/2"); + makePath(outputpath + "/3"); + renameFile(outputpath + "/old0", outputpath + "/0/3"); + renameFile(outputpath + "/old1", outputpath + "/1/2"); + renameFile(outputpath + "/old2", outputpath + "/2/1"); + renameFile(outputpath + "/old3", outputpath + "/3/0"); + // ...now the zoom 1 files + const char* formatExtensions[] = {".png", ".jpeg"}; + ImageSettings::Format formats[] = {ImageSettings::Format_PNG, ImageSettings::Format_JPEG}; + for (int i = 0; i < 2; ++i) { + if (ImageSettings::format == formats[i] || ImageSettings::format == ImageSettings::Format_Both) + { + const char* format = formatExtensions[i]; + renameFile(outputpath + "/0" + format, outputpath + "/0/3" + format); + renameFile(outputpath + "/1" + format, outputpath + "/1/2" + format); + renameFile(outputpath + "/2" + format, outputpath + "/2/1" + format); + renameFile(outputpath + "/3" + format, outputpath + "/3/0" + format); + } + } + + // build the new zoom 1 tiles + RGBAImage old0img; + bool used0 = old0img.readPNG(outputpath + "/0/3.png"); + RGBAImage new0img; + new0img.create(tileSize, tileSize); + if (used0) + { + reduceHalf(new0img, ImageRect(tileSize/2, tileSize/2, tileSize/2, tileSize/2), old0img); + new0img.writeImage(outputpath + "/0"); + } + RGBAImage old1img; + bool used1 = old1img.readPNG(outputpath + "/1/2.png"); + RGBAImage new1img; + new1img.create(tileSize, tileSize); + if (used1) + { + reduceHalf(new1img, ImageRect(0, tileSize/2, tileSize/2, tileSize/2), old1img); + new1img.writeImage(outputpath + "/1"); + } + RGBAImage old2img; + bool used2 = old2img.readPNG(outputpath + "/2/1.png"); + RGBAImage new2img; + new2img.create(tileSize, tileSize); + if (used2) + { + reduceHalf(new2img, ImageRect(tileSize/2, 0, tileSize/2, tileSize/2), old2img); + new2img.writeImage(outputpath + "/2"); + } + RGBAImage old3img; + bool used3 = old3img.readPNG(outputpath + "/3/0.png"); + RGBAImage new3img; + new3img.create(tileSize, tileSize); + if (used3) + { + reduceHalf(new3img, ImageRect(0, 0, tileSize/2, tileSize/2), old3img); + new3img.writeImage(outputpath + "/3"); + } + + // build the new base tile + RGBAImage newbase; + newbase.create(tileSize, tileSize); + if (used0) + reduceHalf(newbase, ImageRect(0, 0, tileSize/2, tileSize/2), new0img); + if (used1) + reduceHalf(newbase, ImageRect(tileSize/2, 0, tileSize/2, tileSize/2), new1img); + if (used2) + reduceHalf(newbase, ImageRect(0, tileSize/2, tileSize/2, tileSize/2), new2img); + if (used3) + reduceHalf(newbase, ImageRect(tileSize/2, tileSize/2, tileSize/2, tileSize/2), new3img); + newbase.writeImage(outputpath + "/base"); + + // write new params (with incremented baseZoom) + mp.baseZoom++; + mp.writeFile(outputpath); + + // touch all tiles, to prevent browser cache mishaps (since many new tiles will have the same + // filename as some old tile, but possibly with an earlier timestamp) + if (system((string("find ") + outputpath + " -exec touch {} +").c_str()) < 0) + cerr << "Error changing mtimes. Ignoring..." << endl; + + return true; +} + +void writeHTML(const RenderJob& rj, const string& htmlpath) +{ + string templatePath = htmlpath + "/template.html"; + stringbuf strbuf; + ifstream infile(templatePath.c_str()); + infile.get(strbuf, 0); // get entire file (unless it happens to have a '\0' in it) + if (infile.fail()) + { + cerr << "couldn't find template.html" << endl; + return; + } + string templateText = strbuf.str(); + if (!replace(templateText, "{tileSize}", tostring(rj.mp.tileSize())) || + !replace(templateText, "{B}", tostring(rj.mp.B)) || + !replace(templateText, "{T}", tostring(rj.mp.T)) || + !replace(templateText, "{baseZoom}", tostring(rj.mp.baseZoom)) || + !replace(templateText, "{format}", ImageSettings::format == ImageSettings::Format_PNG ? "png" : "jpeg")) + { + cerr << "template.html is corrupt" << endl; + return; + } + string htmlOutPath = rj.outputpath + "/pigmap-default.html"; + ofstream outfile(htmlOutPath.c_str()); + outfile << templateText; + + copyFile(htmlpath + "/style.css", rj.outputpath + "/style.css"); +} + +bool performRender(const string& inputpath, const string& outputpath, const string& imgpath, const MapParams& mp, const string& chunklist, const string& regionlist, int threads, int testworldsize, bool expand, const string& htmlpath) +{ + time_t tstart = time(NULL); + + // prepare the rendering params and the chunk/tile tables + // ...note that mp.baseZoom might not be set yet if this is a full render; makeAllChunksRequired + // will handle it + RenderJob rj; + rj.testmode = testworldsize != -1; + rj.mp = mp; + rj.inputpath = inputpath; + rj.outputpath = outputpath; + if (!rj.blockimages.create(rj.mp.B, imgpath)) + { + cerr << "no block images available; aborting render" << endl; + return false; + } + rj.chunktable.reset(new ChunkTable); + rj.tiletable.reset(new TileTable); + rj.regiontable.reset(new RegionTable); + rj.regionformat = !rj.testmode && detectRegionFormat(rj.inputpath); + if (rj.regionformat) + cout << "region-format world detected" << endl; + else + cout << "no regions detected; assuming chunk-format world" << endl; + + // test world + if (testworldsize != -1) + { + rj.fullrender = true; + cout << "building test world..." << endl; + makeTestWorld(testworldsize, *rj.chunktable, *rj.tiletable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount); + } + // full render + else if (chunklist.empty() && regionlist.empty()) + { + rj.fullrender = true; + cout << "scanning world data..." << endl; + if (rj.regionformat) + { + if (!makeAllRegionsRequired(rj.inputpath, *rj.chunktable, *rj.tiletable, *rj.regiontable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount, rj.stats.reqregioncount)) + return false; + } + else + { + if (!makeAllChunksRequired(rj.inputpath, *rj.chunktable, *rj.tiletable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount)) + return false; + } + } + // incremental update + else + { + rj.fullrender = false; + int rv; + if (rj.regionformat) + { + cout << "processing regionlist..." << endl; + rv = readRegionlist(regionlist, rj.inputpath, *rj.chunktable, *rj.tiletable, *rj.regiontable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount, rj.stats.reqregioncount); + } + else + { + cout << "processing chunklist..." << endl; + rv = readChunklist(chunklist, *rj.chunktable, *rj.tiletable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount); + } + if (rv == -2) + return false; + // if we failed because baseZoom is too small, and -x was specified, expand the world and try once more + if (rv == -1 && expand) + { + if (!expandMap(rj.outputpath)) + return false; + rj.mp.baseZoom++; + cout << "baseZoom of output map has been increased to " << rj.mp.baseZoom << endl; + rj.chunktable.reset(new ChunkTable); + rj.tiletable.reset(new TileTable); + rj.regiontable.reset(new RegionTable); + if (rj.regionformat) + { + if (0 != readRegionlist(regionlist, rj.inputpath, *rj.chunktable, *rj.tiletable, *rj.regiontable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount, rj.stats.reqregioncount)) + return false; + } + else + { + if (0 != readChunklist(chunklist, *rj.chunktable, *rj.tiletable, rj.mp, rj.stats.reqchunkcount, rj.stats.reqtilecount)) + return false; + } + } + } + + if (rj.stats.reqtilecount == 0) + { + cout << "nothing to do! (no required tiles)" << endl; + return true; + } + + // render stuff + cout << "rendering tiles..." << endl; + if (threads >= 2) + runMultithreaded(rj, threads); + else + runSingleThread(rj); + + // double-check that all the required tiles were drawn + cout << "performing double-check..." << endl; + for (RequiredTileIterator it(*rj.tiletable); !it.end; it.advance()) + { + if (!rj.tiletable->isDrawn(it.current)) + cerr << "required tile " << it.current.toTileIdx().toFilePath(rj.mp) << " was somehow not drawn!" << endl; + } + + // write map params, HTML + if (!rj.testmode) + { + rj.mp.writeFile(rj.outputpath); + writeHTML(rj, htmlpath); + } + + // done; print stats + time_t tfinish = time(NULL); + printStats(tfinish - tstart, rj.stats); + return true; +} + +//------------------------------------------------------------------------------------------------------------------- + +// warning: slow +void testTileBBoxes(const MapParams& mp) +{ + // check tile bounding boxes for a few tiles + for (int64_t tx = -5; tx <= 5; tx++) + for (int64_t ty = -5; ty <= 5; ty++) + { + // get computed BBox + TileIdx ti(tx,ty); + BBox bbox = ti.getBBox(mp); + + // this is what the box is supposed to be + int64_t xmin = 64*mp.B*mp.T*tx - 2*mp.B; + int64_t ymax = 64*mp.B*mp.T*ty + 17*mp.B; + int64_t xmax = xmin + mp.tileSize(); + int64_t ymin = ymax - mp.tileSize(); + + // test pixels + for (int64_t x = xmin - 15; x <= xmax + 15; x++) + for (int64_t y = ymin - 15; y <= ymax + 15; y++) + { + bool result = bbox.includes(Pixel(x,y)); + bool expected = x >= xmin && x < xmax && y >= ymin && y < ymax; + if (result != expected) + { + cout << "failed tile bounding box test! " << tx << " " << ty << endl; + cout << "[" << bbox.topLeft.x << "," << bbox.topLeft.y << "] to [" << bbox.bottomRight.x << "," << bbox.bottomRight.y << "]" << endl; + cout << "[" << xmin << "," << ymin << "] to [" << xmax << "," << ymax << "]" << endl; + cout << x << "," << y << endl; + return; + } + } + } +} + +// warning: slow +void testMath() +{ + // vary map params: block size B from 2 to 6, tile multiplier T from 1 to 4 + MapParams mp(0,0,0); + for (mp.B = 2; mp.B <= 6; mp.B++) + for (mp.T = 1; mp.T <= 4; mp.T++) + { + cout << "B = " << mp.B << " T = " << mp.T << endl; + + testTileBBoxes(mp); + } +} + +void testBase36() +{ + for (int64_t i = -2473; i <= 1472; i += 93) + cout << i << " " << toBase36(i) << " " << fromBase36(toBase36(i)) << endl; + for (int64_t x = -123; x <= 201; x += 45) + for (int64_t z = -239; z <= 196; z += 57) + { + ChunkIdx ci(x,z); + string filepath = ci.toFilePath(); + ChunkIdx ci2(-999999,-999999); + if (ChunkIdx::fromFilePath(filepath, ci2)) + cout << "[" << x << "," << z << "] " << filepath << " [" << ci2.x << "," << ci2.z << "]" << endl; + else + { + cout << "failed to get ChunkIdx from filename: " << filepath << endl; + return; + } + } +} + +void testMod64() +{ + for (int64_t i = -135; i < 135; i++) + cout << i << " mod64: " << mod64pos(i) << " base36: " << toBase36(mod64pos(i)) << endl; +} + +struct compareChunks +{ + bool operator()(const ChunkIdx& ci1, const ChunkIdx& ci2) const {if (ci1.x == ci2.x) return ci1.z < ci2.z; return ci1.x < ci2.x;} +}; + +// warning: slow; only use with small worlds +void testChunkTable(const string& inputpath) +{ + vector chunkpaths; + findAllChunks(inputpath, chunkpaths); + + auto_ptr chunktable(new ChunkTable); + auto_ptr tiletable(new TileTable); + int64_t reqchunkcount, reqtilecount; + MapParams mp(6,1,-1); + makeAllChunksRequired(inputpath, *chunktable, *tiletable, mp, reqchunkcount, reqtilecount); + + // make sure all chunks in the file list are present and marked required in the ChunkTable + // ...also build list of ChunkIdxs for comparison with the RequiredChunkIterator later + set chunklist; + for (vector::const_iterator it = chunkpaths.begin(); it != chunkpaths.end(); it++) + { + ChunkIdx ci(0,0); + if (ChunkIdx::fromFilePath(*it, ci)) + { + chunklist.insert(ci); + if (!chunktable->isRequired(ci)) + { + cout << "chunk file " << *it << " is not marked as required!" << endl; + return; + } + } + } + + // iterate over the required chunks in the ChunkTable and make sure each one is present in the file list + // ...also compute the total size of the ChunkTable + int lastcgi = -1, lastcsi = -1; + int64_t level3size = sizeof(ChunkTable); + int64_t level2size = 0; + int64_t level1size = 0; + int64_t chunkcount = 0; + for (RequiredChunkIterator it(*chunktable); !it.end; it.advance()) + { + ChunkIdx ci = it.current.toChunkIdx(); + if (chunklist.find(ci) == chunklist.end()) + { + cout << "chunk [" << ci.x << "," << ci.z << "] was iterated over, but is not in file list!" << endl; + return; + } + + chunkcount++; + if (it.cgi != lastcgi || it.csi != lastcsi) + { + level1size += sizeof(ChunkSet); + if (it.cgi != lastcgi) + level2size += sizeof(ChunkGroup); + lastcgi = it.cgi; + lastcsi = it.csi; + } + } + cout << "world size: " << chunkcount << " chunks" << endl; + cout << "ChunkTable size: " << level3size << " + " << level2size << " + " << level1size << " bytes" << endl; +} + +void testPNG() +{ + RGBAImage img; + img.create(100, 100); + for (vector::iterator it = img.data.begin(); it != img.data.end(); it++) + { + *it = ((rand() % 256) << 24) | ((rand() % 256) << 16) | ((rand() % 256) << 8) | (rand() % 256); + } + img.writePNG("test.png"); + + RGBAImage img2; + img2.readPNG("test.png"); + if (img2.data != img.data) + cout << "images don't match after trip through PNG!" << endl; + else + cout << "PNG test successful" << endl; +} + +struct compareTiles +{ + bool operator()(const TileIdx& ti1, const TileIdx& ti2) const {if (ti1.x == ti2.x) return ti1.y < ti2.y; return ti1.x < ti2.x;} +}; + +void testIterators(const string& inputpath) +{ + MapParams mp(3,2,10); + auto_ptr chunktable(new ChunkTable); + auto_ptr tiletable(new TileTable); + int64_t reqchunkcount, reqtilecount; + makeAllChunksRequired(inputpath, *chunktable, *tiletable, mp, reqchunkcount, reqtilecount); + + // make sure the RequiredChunkIterator and RequiredTileIterator compute the same results + set tiles1, tiles2; + + for (RequiredChunkIterator it(*chunktable); !it.end; it.advance()) + { + ChunkIdx ci = it.current.toChunkIdx(); + vector tiles = ci.getTiles(mp); + for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) + tiles1.insert(*tile); + } + + for (RequiredTileIterator it(*tiletable); !it.end; it.advance()) + { + tiles2.insert(it.current.toTileIdx()); + } + + if (tiles1 != tiles2) + cout << "iterators don't match!" << endl; + else + cout << "iterators match" << endl; +} + +void testZOrder() +{ + int SIZE = 64; + vector hits1(SIZE*SIZE, 0), hits2(SIZE*SIZE, 0); + for (int i = 0; i < SIZE*SIZE; i++) + { + hits1[toZOrder(i, SIZE)]++; + hits2[fromZOrder(i, SIZE)]++; + } + for (int i = 0; i < SIZE*SIZE; i++) + if (hits1[i] != 1 || hits2[i] != 1) + cout << "position " << i << " was hit " << hits1[i] << ", " << hits2[i] << " times!" << endl; +} + +void testTileIdxs() +{ + for (int baseZoom = 3; baseZoom < 11; baseZoom++) + { + MapParams mp(6,1,baseZoom); + for (int z = 0; z < 4; z++) + for (int x = 0; x < (1 << z); x++) + for (int y = 0; y < (1 << z); y++) + { + ZoomTileIdx zti(x, y, z); + TileIdx ti = zti.toTileIdx(mp); + ZoomTileIdx zti2 = zti.toZoom(baseZoom); + TileIdx ti2 = zti2.toTileIdx(mp); + if (ti != ti2) + cout << "mismatch! baseZoom " << baseZoom << " zoom tile [" << zti.x << "," + << zti.y << "] @ " << zti.zoom << endl; + } + } +} + +void testReqTileCount(const string& inputpath) +{ + MapParams mp(6,1,10); + auto_ptr chunktable(new ChunkTable); + auto_ptr tiletable(new TileTable); + int64_t reqchunkcount, reqtilecount; + makeAllChunksRequired(inputpath, *chunktable, *tiletable, mp, reqchunkcount, reqtilecount); + + cout << "required base tiles: " << reqtilecount << endl; + for (int z = 0; z <= mp.baseZoom; z++) + { + int64_t count = 0; + for (int x = 0; x < (1 << z); x++) + for (int y = 0; y < (1 << z); y++) + count += tiletable->getNumRequired(ZoomTileIdx(x, y, z), mp); + if (count != reqtilecount) + cout << "tile counts don't match for zoom " << z << "!" << endl; + else + cout << "tile counts okay for zoom " << z << endl; + } +} + +void testResize() +{ + int sourceSize = 16; + for (int B = 2; B <= 16; B++) + { + int destSize = 2*B; + cout << "====== B: " << B << " destSize: " << destSize << " sourceSize: " << sourceSize << " ======" << endl; + for (int i = 0; i < destSize; i++) + { + int j = interpolate(i, destSize, sourceSize); + cout << i << " --> " << j << endl; + } + } +} + +//------------------------------------------------------------------------------------------------------------------- + +bool validateParamsFull(const string& inputpath, const string& outputpath, const string& imgpath, const MapParams& mp, int threads, const string& chunklist, const string& regionlist, bool expand, const string& htmlpath) +{ + // -c and -x are not allowed for full renders + if (!chunklist.empty() || !regionlist.empty() || expand) + { + cerr << "-c, -r, -x not allowed for full renders" << endl; + return false; + } + + // B and T must be within range (upper limits aren't really necessary and can be adjusted if + // someone really wants gigantic tile images for some reason) + if (!mp.valid()) + { + cerr << "-B must be in range 2-16; -T must be in range 1-16" << endl; + return false; + } + + // baseZoom must be within range, or -1 (omitted) + if (!mp.validZoom() && mp.baseZoom != -1) + { + cerr << "-Z must be in range 0-30, or may be omitted to set automatically" << endl; + return false; + } + + // MINY/MAXY must describe a valid range + if (!mp.validYRange()) + { + cerr << "-y and -Y, if used, must be in range 0-255, and -y must be <= -Y" << endl; + return false; + } + + // must have a sensible number of threads (upper limit is arbitrary, but you'd need a truly + // insanely large map to see any benefit to having that many...) + if (threads < 1 || threads > 64) + { + cerr << "-t must be in range 1-64" << endl; + return false; + } + + // the various paths must be non-empty + if (inputpath.empty() || outputpath.empty()) + { + cerr << "must provide both input (-i) and output (-o) paths" << endl; + return false; + } + if (imgpath.empty()) + { + cerr << "must provide non-empty image path, or omit -g to use \".\"" << endl; + return false; + } + if (htmlpath.empty()) + { + cerr << "must provide non-empty HTML path, or omit -m to use \".\"" << endl; + return false; + } + + return true; +} + +// also sets MapParams to values from existing map +bool validateParamsIncremental(const string& inputpath, const string& outputpath, const string& imgpath, MapParams& mp, int threads, const string& chunklist, const string& regionlist, bool expand, const string& htmlpath) +{ + // -B, -T, -Z, -y, -Y are not allowed + if (mp.B != -1 || mp.T != -1 || mp.baseZoom != -1 || mp.userMinY || mp.userMaxY) + { + cerr << "-B, -T, -Z, -y, -Y not allowed for incremental updates" << endl; + return false; + } + + // Format cannot be jpeg-only + if (ImageSettings::format == ImageSettings::Format_JPEG) + { + cerr << "PNG image output is required for incremental rendering" << endl + << "Please use format \"png\" or \"both\"" << endl; + return false; + } + + // the various paths must be non-empty + if (inputpath.empty() || outputpath.empty()) + { + cerr << "must provide both input (-i) and output (-o) paths" << endl; + return false; + } + if (imgpath.empty()) + { + cerr << "must provide non-empty image path, or omit -g to use \".\"" << endl; + return false; + } + if (htmlpath.empty()) + { + cerr << "must provide non-empty HTML path, or omit -m to use \".\"" << endl; + return false; + } + + // can't have both chunklist and regionlist + if (!chunklist.empty() && !regionlist.empty()) + { + cerr << "only one of -c, -r may be used" << endl; + return false; + } + + // if world is in region format, must use regionlist + if (detectRegionFormat(inputpath) && regionlist.empty()) + { + cerr << "world is in region format; must use -r, not -c" << endl; + return false; + } + + // pigmap.params must be present in output path; read it now + if (!mp.readFile(outputpath)) + { + cerr << "can't find pigmap.params in output path" << endl; + return false; + } + + // must have a sensible number of threads (upper limit is arbitrary, but you'd need a truly + // insanely large map to see any benefit to having that many...) + if (threads < 1 || threads > 64) + { + cerr << "-t must be in range 1-64" << endl; + return false; + } + + return true; +} + +bool validateParamsTest(const string& inputpath, const string& outputpath, const string& imgpath, const MapParams& mp, int threads, const string& chunklist, const string& regionlist, bool expand, const string& htmlpath, int testworldsize) +{ + // -i, -o, -c, -r, -x, -m are not allowed + if (!inputpath.empty() || !outputpath.empty() || !chunklist.empty() || !regionlist.empty() || expand || htmlpath != ".") + { + cerr << "-i, -o, -c, -r, -x, -m not allowed for test worlds" << endl; + return false; + } + + // B and T must be within range (upper limits aren't really necessary and can be adjusted if + // someone really wants gigantic tile images for some reason) + if (!mp.valid()) + { + cerr << "-B must be in range 2-16; -T must be in range 1-16" << endl; + return false; + } + + // baseZoom must be within range, or -1 (omitted) + if (!mp.validZoom() && mp.baseZoom != -1) + { + cerr << "-Z must be in range 0-30, or may be omitted to set automatically" << endl; + return false; + } + + // MINY/MAXY must describe a valid range + if (!mp.validYRange()) + { + cerr << "-y and -Y, if used, must be in range 0-255, and -y must be <= -Y" << endl; + return false; + } + + // must have a sensible number of threads (upper limit is arbitrary, but you'd need a truly + // insanely large map to see any benefit to having that many...) + if (threads < 1 || threads > 64) + { + cerr << "-t must be in range 1-64" << endl; + return false; + } + + // image path must be non-empty + if (imgpath.empty()) + { + cerr << "must provide non-empty image path, or omit -g to use \".\"" << endl; + return false; + } + + // test world size must be positive + if (testworldsize < 0) + { + cerr << "testworld size must be positive" << endl; + return false; + } + + return true; +} + +int main(int argc, char **argv) +{ + //testMath(); + //testBase36(); + //testMod64(); + //testChunkTable(inputpath); + //testTileIterator(); + //testPColIterator(); + //testChunkCache(); + //testPNG(); + //testIterators(inputpath); + //testZOrder(); + //testTileIdxs(); + //testReqTileCount(inputpath); + //testResize(); + + string inputpath, outputpath, imgpath = ".", chunklist, regionlist, htmlpath = "."; + MapParams mp(-1,-1,-1); + int threads = 1; + int testworldsize = -1; + bool expand = false; + + int c; + while ((c = getopt(argc, argv, "i:o:g:c:B:T:Z:t:w:xm:r:y:Y:j:f:h")) != -1) + { + switch (c) + { + case 'i': + inputpath = optarg; + break; + case 'o': + outputpath = optarg; + break; + case 'g': + imgpath = optarg; + break; + case 'c': + chunklist = optarg; + break; + case 'r': + regionlist = optarg; + break; + case 'f': + if (strcasecmp(optarg, "png") == 0) + ImageSettings::format = ImageSettings::Format_PNG; + else if (strcasecmp(optarg, "jpg") == 0 || strcasecmp(optarg, "jpeg") == 0) + ImageSettings::format = ImageSettings::Format_JPEG; + else if (strcasecmp(optarg, "both") == 0) + ImageSettings::format = ImageSettings::Format_Both; + else + { + cerr << "Unrecognized format: " << optarg << ", expected png/jpeg/both" << endl; + return 1; + } + break; + case 'j': + ImageSettings::jpegQuality = atoi(optarg); + if (ImageSettings::jpegQuality < 1 || ImageSettings::jpegQuality > 100) + { + cerr << "Invalid jpeg quality (" << ImageSettings::jpegQuality << ")" << endl; + return 1; + } + break; + case 'B': + mp.B = atoi(optarg); + break; + case 'T': + mp.T = atoi(optarg); + break; + case 'Z': + mp.baseZoom = atoi(optarg); + break; + case 'y': + mp.minY = atoi(optarg); + mp.userMinY = true; + break; + case 'Y': + mp.maxY = atoi(optarg); + mp.userMaxY = true; + break; + case 't': + threads = atoi(optarg); + break; + case 'x': + expand = true; + break; + case 'm': + htmlpath = optarg; + break; + case 'w': + testworldsize = atoi(optarg); + break; + case 'h': + cerr << "PigMap " << endl + << "-i minecraft world input path. This should be the base of the world" << endl + << "-o output path. This is the diretory to put the html file in" << endl + << "-g image path. This is where to find the *.list files, minecraft textures and also to output the cached blocks." << endl + << "-c [filename] file containing chunks to render" << endl + << "-r [filename] file containing regions to render" << endl + << "-f [format] rendering output format - png,jpg or both" << endl + << "-j jpeg quality (1-100)" << endl + << "-Y maximum Y value" << endl + << "-y minimum Y value" << endl + << "-Z (base zoom)?" << endl + << "-t threads to use for rendering" << endl + << "-B Block size - size in pixels of each minecraft block (2-16)!" << endl + << "-T Tile Size Division. (2-16)" << endl + << "-Z Map zoom levels (0-30)" << endl + << "-m location of html input files" << endl + << "-x turn on expanding of map, for when base zoom is too small for the tiling" << endl + << "-w turn on test mode, and create test world of size " << endl + << endl + << " Tile Size Determines how large the tiles on the map are." << endl + << " A larger size saves disk space, but makes tiles load slower." << endl; + return 0; + case '?': + cerr << "-" << (char)optopt << ": unrecognized option or missing argument, -h displays help." << endl; + return 1; + default: // should never happen (?) + cerr << "getopt not working?" << endl; + return 1; + } + } + + if (testworldsize != -1) + { + if (!validateParamsTest(inputpath, outputpath, imgpath, mp, threads, chunklist, regionlist, expand, htmlpath, testworldsize)) + return 1; + } + else if (chunklist.empty() && regionlist.empty()) + { + if (!validateParamsFull(inputpath, outputpath, imgpath, mp, threads, chunklist, regionlist, expand, htmlpath)) + return 1; + } + else + { + if (!validateParamsIncremental(inputpath, outputpath, imgpath, mp, threads, chunklist, regionlist, expand, htmlpath)) + return 1; + } + + if (!performRender(inputpath, outputpath, imgpath, mp, chunklist, regionlist, threads, testworldsize, expand, htmlpath)) + return 1; + + return 0; +} diff --git a/region.cpp b/region.cpp old mode 100755 new mode 100644 index 27f416a..57c22d8 --- a/region.cpp +++ b/region.cpp @@ -1,276 +1,276 @@ -// Copyright 2011 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#include -#include -#include - -#include "region.h" -#include "utils.h" - -using namespace std; - - - -struct fcloser -{ - FILE *f; - fcloser(FILE *ff) : f(ff) {} - ~fcloser() {fclose(f);} -}; - -FILE* openRegionFile(const RegionIdx& ri, const string& inputpath, bool& anvil) -{ - string filename = inputpath + "/region/" + ri.toAnvilFileName(); - FILE *anvilfile = fopen(filename.c_str(), "rb"); - if (anvilfile != NULL) - { - anvil = true; - return anvilfile; - } - filename = inputpath + "/region/" + ri.toOldFileName(); - anvil = false; - return fopen(filename.c_str(), "rb"); -} - -int RegionFileReader::loadFromFile(const RegionIdx& ri, const string& inputpath) -{ - // open file - FILE *f = openRegionFile(ri, inputpath, anvil); - if (f == NULL) - return -1; - fcloser fc(f); - - // get file length - fseek(f, 0, SEEK_END); - size_t length = (size_t)ftell(f); - fseek(f, 0, SEEK_SET); - if (length < 4096) - return -2; - - // read the header - size_t count = fread(&(offsets[0]), 4096, 1, f); - if (count < 1) - return -2; - - // read the rest of the file - chunkdata.resize(length - 4096); - if (length > 4096) - { - count = fread(&(chunkdata[0]), length - 4096, 1, f); - if (count < 1) - return -2; - } - return 0; -} - -int RegionFileReader::loadHeaderOnly(const RegionIdx& ri, const string& inputpath) -{ - // open file - FILE *f = openRegionFile(ri, inputpath, anvil); - if (f == NULL) - return -1; - fcloser fc(f); - - // read the header - size_t count = fread(&(offsets[0]), 4096, 1, f); - if (count < 1) - return -2; - - return 0; -} - -int RegionFileReader::decompressChunk(const ChunkOffset& co, vector& buf) -{ - // see if chunk is present - if (!containsChunk(co)) - return -1; - - // attempt to decompress chunk data into buffer - int idx = getIdx(co); - uint32_t sector = getSectorOffset(idx); - if ((sector - 1) * 4096 >= chunkdata.size()) - return -2; - uint8_t *chunkstart = &(chunkdata[(sector - 1) * 4096]); - uint32_t datasize = fromBigEndian(*((uint32_t*)chunkstart)); - bool okay = readGzOrZlib(chunkstart + 5, datasize - 1, buf); - if (!okay) - return -2; - return 0; -} - -int RegionFileReader::getContainedChunks(const RegionIdx& ri, const string84& inputpath, vector& chunks) -{ - chunks.clear(); - int result = loadHeaderOnly(ri, inputpath.s); - if (0 != result) - return result; - for (RegionChunkIterator it(ri); !it.end; it.advance()) - if (containsChunk(it.current)) - chunks.push_back(it.current); - return 0; -} - - - - - -RegionChunkIterator::RegionChunkIterator(const RegionIdx& ri) - : end(false), current(ri.baseChunk()), basechunk(ri.baseChunk()) -{ -} - -void RegionChunkIterator::advance() -{ - current.x++; - if (current.x >= basechunk.x + 32) - { - current.x = basechunk.x; - current.z++; - } - if (current.z >= basechunk.z + 32) - end = true; -} - - - -RegionCacheStats& RegionCacheStats::operator+=(const RegionCacheStats& rcs) -{ - hits += rcs.hits; - misses += rcs.misses; - read += rcs.read; - skipped += rcs.skipped; - missing += rcs.missing; - reqmissing += rcs.reqmissing; - corrupt += rcs.corrupt; - return *this; -} - - -int RegionCache::getDecompressedChunk(const PosChunkIdx& ci, vector& buf, bool& anvil) -{ - PosRegionIdx ri = ci.toChunkIdx().getRegionIdx(); - int e = getEntryNum(ri); - int state = regiontable.getDiskState(ri); - - if (state == RegionSet::REGION_UNKNOWN) - stats.misses++; - else - stats.hits++; - - // if we already tried and failed to read this region, don't try again - if (state == RegionSet::REGION_CORRUPTED || state == RegionSet::REGION_MISSING) - { - // actually, it shouldn't even be possible to get here, since the disk state - // flags for all chunks in the region should have been set the first time we failed - cerr << "cache invariant failure! tried to read already-failed region" << endl; - return -1; - } - - // if the region is in the cache, try to extract the chunk from it - if (state == RegionSet::REGION_CACHED) - { - // try the "real" cache entry, then the extra readbuf - if (entries[e].ri == ri) - { - anvil = entries[e].regionfile.anvil; - return entries[e].regionfile.decompressChunk(ci.toChunkIdx(), buf); - } - else if (readbuf.ri == ri) - { - anvil = readbuf.regionfile.anvil; - return readbuf.regionfile.decompressChunk(ci.toChunkIdx(), buf); - } - // if it wasn't in one of those two places, it shouldn't have been marked as cached - cerr << "grievous region cache failure!" << endl; - cerr << "[" << ri.x << "," << ri.z << "] [" << entries[e].ri.x << "," << entries[e].ri.z << "] [" << readbuf.ri.x << "," << readbuf.ri.z << "]" << endl; - exit(-1); - } - - // if this is a full render and the region is not required, we already know it doesn't exist - bool req = regiontable.isRequired(ri); - if (fullrender && !req) - { - stats.skipped++; - regiontable.setDiskState(ri, RegionSet::REGION_MISSING); - for (RegionChunkIterator it(ri.toRegionIdx()); !it.end; it.advance()) - chunktable.setDiskState(it.current, ChunkSet::CHUNK_MISSING); - return -1; - } - - // okay, we actually have to read the region from disk, if it's there - readRegionFile(ri); - - // check whether the read succeeded; try to extract the chunk if so - state = regiontable.getDiskState(ri); - if (state == RegionSet::REGION_CORRUPTED) - { - stats.corrupt++; - return -1; - } - if (state == RegionSet::REGION_MISSING) - { - if (req) - stats.reqmissing++; - else - stats.missing++; - return -1; - } - // since we've actually just done a read, the region should now be in a real cache entry, not the readbuf - if (state != RegionSet::REGION_CACHED || entries[e].ri != ri) - { - cerr << "grievous region cache failure!" << endl; - cerr << "[" << ri.x << "," << ri.z << "] [" << entries[e].ri.x << "," << entries[e].ri.z << "]" << endl; - exit(-1); - } - stats.read++; - anvil = entries[e].regionfile.anvil; - return entries[e].regionfile.decompressChunk(ci.toChunkIdx(), buf); -} - -void RegionCache::readRegionFile(const PosRegionIdx& ri) -{ - // forget the data in the readbuf - if (readbuf.ri.valid()) - regiontable.setDiskState(readbuf.ri, RegionSet::REGION_UNKNOWN); - readbuf.ri = PosRegionIdx(-1,-1); - - // read the region file from disk, if it's there - int result = readbuf.regionfile.loadFromFile(ri.toRegionIdx(), inputpath); - if (result == -1) - { - regiontable.setDiskState(ri, RegionSet::REGION_MISSING); - for (RegionChunkIterator it(ri.toRegionIdx()); !it.end; it.advance()) - chunktable.setDiskState(it.current, ChunkSet::CHUNK_MISSING); - return; - } - if (result == -2) - { - regiontable.setDiskState(ri, RegionSet::REGION_CORRUPTED); - for (RegionChunkIterator it(ri.toRegionIdx()); !it.end; it.advance()) - chunktable.setDiskState(it.current, ChunkSet::CHUNK_MISSING); - return; - } - - // read was successful; evict current tenant of chunk's cache slot (swap it into the readbuf) - int e = getEntryNum(ri); - entries[e].regionfile.swap(readbuf.regionfile); - swap(entries[e].ri, readbuf.ri); - // mark the entry as vaild and the region as cached - entries[e].ri = ri; - regiontable.setDiskState(ri, RegionSet::REGION_CACHED); -} +// Copyright 2011 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#include +#include +#include + +#include "region.h" +#include "utils.h" + +using namespace std; + + + +struct fcloser +{ + FILE *f; + fcloser(FILE *ff) : f(ff) {} + ~fcloser() {fclose(f);} +}; + +FILE* openRegionFile(const RegionIdx& ri, const string& inputpath, bool& anvil) +{ + string filename = inputpath + "/region/" + ri.toAnvilFileName(); + FILE *anvilfile = fopen(filename.c_str(), "rb"); + if (anvilfile != NULL) + { + anvil = true; + return anvilfile; + } + filename = inputpath + "/region/" + ri.toOldFileName(); + anvil = false; + return fopen(filename.c_str(), "rb"); +} + +int RegionFileReader::loadFromFile(const RegionIdx& ri, const string& inputpath) +{ + // open file + FILE *f = openRegionFile(ri, inputpath, anvil); + if (f == NULL) + return -1; + fcloser fc(f); + + // get file length + fseek(f, 0, SEEK_END); + size_t length = (size_t)ftell(f); + fseek(f, 0, SEEK_SET); + if (length < 4096) + return -2; + + // read the header + size_t count = fread(&(offsets[0]), 4096, 1, f); + if (count < 1) + return -2; + + // read the rest of the file + chunkdata.resize(length - 4096); + if (length > 4096) + { + count = fread(&(chunkdata[0]), length - 4096, 1, f); + if (count < 1) + return -2; + } + return 0; +} + +int RegionFileReader::loadHeaderOnly(const RegionIdx& ri, const string& inputpath) +{ + // open file + FILE *f = openRegionFile(ri, inputpath, anvil); + if (f == NULL) + return -1; + fcloser fc(f); + + // read the header + size_t count = fread(&(offsets[0]), 4096, 1, f); + if (count < 1) + return -2; + + return 0; +} + +int RegionFileReader::decompressChunk(const ChunkOffset& co, vector& buf) +{ + // see if chunk is present + if (!containsChunk(co)) + return -1; + + // attempt to decompress chunk data into buffer + int idx = getIdx(co); + uint32_t sector = getSectorOffset(idx); + if ((sector - 1) * 4096 >= chunkdata.size()) + return -2; + uint8_t *chunkstart = &(chunkdata[(sector - 1) * 4096]); + uint32_t datasize = fromBigEndian(*((uint32_t*)chunkstart)); + bool okay = readGzOrZlib(chunkstart + 5, datasize - 1, buf); + if (!okay) + return -2; + return 0; +} + +int RegionFileReader::getContainedChunks(const RegionIdx& ri, const string84& inputpath, vector& chunks) +{ + chunks.clear(); + int result = loadHeaderOnly(ri, inputpath.s); + if (0 != result) + return result; + for (RegionChunkIterator it(ri); !it.end; it.advance()) + if (containsChunk(it.current)) + chunks.push_back(it.current); + return 0; +} + + + + + +RegionChunkIterator::RegionChunkIterator(const RegionIdx& ri) + : end(false), current(ri.baseChunk()), basechunk(ri.baseChunk()) +{ +} + +void RegionChunkIterator::advance() +{ + current.x++; + if (current.x >= basechunk.x + 32) + { + current.x = basechunk.x; + current.z++; + } + if (current.z >= basechunk.z + 32) + end = true; +} + + + +RegionCacheStats& RegionCacheStats::operator+=(const RegionCacheStats& rcs) +{ + hits += rcs.hits; + misses += rcs.misses; + read += rcs.read; + skipped += rcs.skipped; + missing += rcs.missing; + reqmissing += rcs.reqmissing; + corrupt += rcs.corrupt; + return *this; +} + + +int RegionCache::getDecompressedChunk(const PosChunkIdx& ci, vector& buf, bool& anvil) +{ + PosRegionIdx ri = ci.toChunkIdx().getRegionIdx(); + int e = getEntryNum(ri); + int state = regiontable.getDiskState(ri); + + if (state == RegionSet::REGION_UNKNOWN) + stats.misses++; + else + stats.hits++; + + // if we already tried and failed to read this region, don't try again + if (state == RegionSet::REGION_CORRUPTED || state == RegionSet::REGION_MISSING) + { + // actually, it shouldn't even be possible to get here, since the disk state + // flags for all chunks in the region should have been set the first time we failed + cerr << "cache invariant failure! tried to read already-failed region" << endl; + return -1; + } + + // if the region is in the cache, try to extract the chunk from it + if (state == RegionSet::REGION_CACHED) + { + // try the "real" cache entry, then the extra readbuf + if (entries[e].ri == ri) + { + anvil = entries[e].regionfile.anvil; + return entries[e].regionfile.decompressChunk(ci.toChunkIdx(), buf); + } + else if (readbuf.ri == ri) + { + anvil = readbuf.regionfile.anvil; + return readbuf.regionfile.decompressChunk(ci.toChunkIdx(), buf); + } + // if it wasn't in one of those two places, it shouldn't have been marked as cached + cerr << "grievous region cache failure!" << endl; + cerr << "[" << ri.x << "," << ri.z << "] [" << entries[e].ri.x << "," << entries[e].ri.z << "] [" << readbuf.ri.x << "," << readbuf.ri.z << "]" << endl; + exit(-1); + } + + // if this is a full render and the region is not required, we already know it doesn't exist + bool req = regiontable.isRequired(ri); + if (fullrender && !req) + { + stats.skipped++; + regiontable.setDiskState(ri, RegionSet::REGION_MISSING); + for (RegionChunkIterator it(ri.toRegionIdx()); !it.end; it.advance()) + chunktable.setDiskState(it.current, ChunkSet::CHUNK_MISSING); + return -1; + } + + // okay, we actually have to read the region from disk, if it's there + readRegionFile(ri); + + // check whether the read succeeded; try to extract the chunk if so + state = regiontable.getDiskState(ri); + if (state == RegionSet::REGION_CORRUPTED) + { + stats.corrupt++; + return -1; + } + if (state == RegionSet::REGION_MISSING) + { + if (req) + stats.reqmissing++; + else + stats.missing++; + return -1; + } + // since we've actually just done a read, the region should now be in a real cache entry, not the readbuf + if (state != RegionSet::REGION_CACHED || entries[e].ri != ri) + { + cerr << "grievous region cache failure!" << endl; + cerr << "[" << ri.x << "," << ri.z << "] [" << entries[e].ri.x << "," << entries[e].ri.z << "]" << endl; + exit(-1); + } + stats.read++; + anvil = entries[e].regionfile.anvil; + return entries[e].regionfile.decompressChunk(ci.toChunkIdx(), buf); +} + +void RegionCache::readRegionFile(const PosRegionIdx& ri) +{ + // forget the data in the readbuf + if (readbuf.ri.valid()) + regiontable.setDiskState(readbuf.ri, RegionSet::REGION_UNKNOWN); + readbuf.ri = PosRegionIdx(-1,-1); + + // read the region file from disk, if it's there + int result = readbuf.regionfile.loadFromFile(ri.toRegionIdx(), inputpath); + if (result == -1) + { + regiontable.setDiskState(ri, RegionSet::REGION_MISSING); + for (RegionChunkIterator it(ri.toRegionIdx()); !it.end; it.advance()) + chunktable.setDiskState(it.current, ChunkSet::CHUNK_MISSING); + return; + } + if (result == -2) + { + regiontable.setDiskState(ri, RegionSet::REGION_CORRUPTED); + for (RegionChunkIterator it(ri.toRegionIdx()); !it.end; it.advance()) + chunktable.setDiskState(it.current, ChunkSet::CHUNK_MISSING); + return; + } + + // read was successful; evict current tenant of chunk's cache slot (swap it into the readbuf) + int e = getEntryNum(ri); + entries[e].regionfile.swap(readbuf.regionfile); + swap(entries[e].ri, readbuf.ri); + // mark the entry as vaild and the region as cached + entries[e].ri = ri; + regiontable.setDiskState(ri, RegionSet::REGION_CACHED); +} diff --git a/region.h b/region.h old mode 100755 new mode 100644 index ce6ada7..78150c1 --- a/region.h +++ b/region.h @@ -1,179 +1,179 @@ -// Copyright 2011 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#ifndef REGION_H -#define REGION_H - -#include - -#include "map.h" -#include "tables.h" - - -// offset into a region of a chunk -struct ChunkOffset -{ - int64_t x, z; - ChunkOffset(const ChunkIdx& ci) - { - RegionIdx ri = ci.getRegionIdx(); - x = ci.x - ri.x*32; - z = ci.z - ri.z*32; - } -}; - -struct string84 -{ - std::string s; - explicit string84(const std::string& sss) : s(sss) {} -}; - -struct RegionFileReader -{ - // region file is broken into 4096-byte sectors; first sector is the header that holds the - // chunk offsets, remaining sectors are chunk data - - // chunk offsets are big-endian; lower (that is, 4th) byte is size in sectors, upper 3 bytes are - // sector offset *in region file* (one more than the offset into chunkdata) - // offsets are indexed by Z*32 + X - std::vector offsets; - // each set of chunk data contains: - // -a 4-byte big-endian data length (not including the length field itself) - // -a single-byte version: 1 for gzip, 2 for zlib (this byte *is* included in the length) - // -length - 1 bytes of actual compressed data - std::vector chunkdata; - // whether this data was read from an Anvil region file or an old-style one - bool anvil; - - RegionFileReader() - { - offsets.resize(32 * 32); - chunkdata.reserve(8388608); - } - - void swap(RegionFileReader& rfr) - { - offsets.swap(rfr.offsets); - chunkdata.swap(rfr.chunkdata); - std::swap(anvil, rfr.anvil); - } - - // extract values from the offsets - static int getIdx(const ChunkOffset& co) {return co.z*32 + co.x;} - uint32_t getSizeSectors(int idx) const {return fromBigEndian(offsets[idx]) & 0xff;} - uint32_t getSectorOffset(int idx) const {return fromBigEndian(offsets[idx]) >> 8;} - bool containsChunk(const ChunkOffset& co) {return offsets[getIdx(co)] != 0;} - - - // attempt to read a region file; return 0 for success, -1 for file not found, -2 for - // other errors - // looks for an Anvil region file (.mca) first, then an old-style one (.mcr) - int loadFromFile(const RegionIdx& ri, const std::string& inputpath); - - // attempt to decompress a chunk into a buffer; return 0 for success, -1 for missing chunk, - // -2 for other errors - // (this is not const only because zlib won't take const pointers for input) - int decompressChunk(const ChunkOffset& co, std::vector& buf); - - // attempt to read only the header (i.e. the chunk offsets) from a region file; return 0 - // for success, -1 for file not found, -2 for other errors - // looks for an Anvil region file (.mca) first, then an old-style one (.mcr) - int loadHeaderOnly(const RegionIdx& ri, const std::string& inputpath); - - // open a region file, load only its header, and return a list of chunks it contains (i.e. the ones that - // actually currently exist) - // ...returns 0 for success, -1 for file not found, -2 for other errors - int getContainedChunks(const RegionIdx& ri, const string84& inputpath, std::vector& chunks); -}; - -// iterates over the chunks in a region -struct RegionChunkIterator -{ - bool end; // true once we've reached the end - ChunkIdx current; // if end == false, holds the current chunk - - ChunkIdx basechunk; - - // constructor initializes to to first chunk in provided region - RegionChunkIterator(const RegionIdx& ri); - - // move to the next chunk, or to the end - void advance(); -}; - - -struct RegionCacheStats -{ - int64_t hits, misses; - // types of misses: - int64_t read; // successfully read from disk - int64_t skipped; // assumed not to exist because not required in a full render - int64_t missing; // non-required region not present on disk - int64_t reqmissing; // required region not present on disk - int64_t corrupt; // found on disk, but failed to read - - RegionCacheStats() : hits(0), misses(0), read(0), skipped(0), missing(0), reqmissing(0), corrupt(0) {} - - RegionCacheStats& operator+=(const RegionCacheStats& rs); -}; - -struct RegionCacheEntry -{ - PosRegionIdx ri; // or [-1, -1] if this entry is empty - RegionFileReader regionfile; - - RegionCacheEntry() : ri(-1,-1) {} -}; - -#define RCACHEBITSX 1 -#define RCACHEBITSZ 1 -#define RCACHEXSIZE (1 << RCACHEBITSX) -#define RCACHEZSIZE (1 << RCACHEBITSZ) -#define RCACHESIZE (RCACHEXSIZE * RCACHEZSIZE) -#define RCACHEXMASK (RCACHEXSIZE - 1) -#define RCACHEZMASK (RCACHEZSIZE - 1) - -struct RegionCache : private nocopy -{ - RegionCacheEntry entries[RCACHESIZE]; - - ChunkTable& chunktable; - RegionTable& regiontable; - RegionCacheStats& stats; - std::string inputpath; - bool fullrender; - // readbuf is an extra less-important cache entry--when a new region is read, it's this entry which will be trashed - // and its storage used for the read (which might fail), but if the read succeeds, the new region is swapped - // into its proper place in the cache, and the previous tenant there moves here - RegionCacheEntry readbuf; - RegionCache(ChunkTable& ctable, RegionTable& rtable, const std::string& inpath, bool fullr, RegionCacheStats& st) - : chunktable(ctable), regiontable(rtable), inputpath(inpath), fullrender(fullr), stats(st) - { - } - - // attempt to decompress a chunk into a buffer; return 0 for success, -1 for missing chunk, - // -2 for other errors - // (this is not const only because zlib won't take const pointers for input) - int getDecompressedChunk(const PosChunkIdx& ci, std::vector& buf, bool& anvil); - - static int getEntryNum(const PosRegionIdx& ri) {return (ri.x & RCACHEXMASK) * RCACHEZSIZE + (ri.z & RCACHEZMASK);} - - void readRegionFile(const PosRegionIdx& ri); -}; - - +// Copyright 2011 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#ifndef REGION_H +#define REGION_H + +#include + +#include "map.h" +#include "tables.h" + + +// offset into a region of a chunk +struct ChunkOffset +{ + int64_t x, z; + ChunkOffset(const ChunkIdx& ci) + { + RegionIdx ri = ci.getRegionIdx(); + x = ci.x - ri.x*32; + z = ci.z - ri.z*32; + } +}; + +struct string84 +{ + std::string s; + explicit string84(const std::string& sss) : s(sss) {} +}; + +struct RegionFileReader +{ + // region file is broken into 4096-byte sectors; first sector is the header that holds the + // chunk offsets, remaining sectors are chunk data + + // chunk offsets are big-endian; lower (that is, 4th) byte is size in sectors, upper 3 bytes are + // sector offset *in region file* (one more than the offset into chunkdata) + // offsets are indexed by Z*32 + X + std::vector offsets; + // each set of chunk data contains: + // -a 4-byte big-endian data length (not including the length field itself) + // -a single-byte version: 1 for gzip, 2 for zlib (this byte *is* included in the length) + // -length - 1 bytes of actual compressed data + std::vector chunkdata; + // whether this data was read from an Anvil region file or an old-style one + bool anvil; + + RegionFileReader() + { + offsets.resize(32 * 32); + chunkdata.reserve(8388608); + } + + void swap(RegionFileReader& rfr) + { + offsets.swap(rfr.offsets); + chunkdata.swap(rfr.chunkdata); + std::swap(anvil, rfr.anvil); + } + + // extract values from the offsets + static int getIdx(const ChunkOffset& co) {return co.z*32 + co.x;} + uint32_t getSizeSectors(int idx) const {return fromBigEndian(offsets[idx]) & 0xff;} + uint32_t getSectorOffset(int idx) const {return fromBigEndian(offsets[idx]) >> 8;} + bool containsChunk(const ChunkOffset& co) {return offsets[getIdx(co)] != 0;} + + + // attempt to read a region file; return 0 for success, -1 for file not found, -2 for + // other errors + // looks for an Anvil region file (.mca) first, then an old-style one (.mcr) + int loadFromFile(const RegionIdx& ri, const std::string& inputpath); + + // attempt to decompress a chunk into a buffer; return 0 for success, -1 for missing chunk, + // -2 for other errors + // (this is not const only because zlib won't take const pointers for input) + int decompressChunk(const ChunkOffset& co, std::vector& buf); + + // attempt to read only the header (i.e. the chunk offsets) from a region file; return 0 + // for success, -1 for file not found, -2 for other errors + // looks for an Anvil region file (.mca) first, then an old-style one (.mcr) + int loadHeaderOnly(const RegionIdx& ri, const std::string& inputpath); + + // open a region file, load only its header, and return a list of chunks it contains (i.e. the ones that + // actually currently exist) + // ...returns 0 for success, -1 for file not found, -2 for other errors + int getContainedChunks(const RegionIdx& ri, const string84& inputpath, std::vector& chunks); +}; + +// iterates over the chunks in a region +struct RegionChunkIterator +{ + bool end; // true once we've reached the end + ChunkIdx current; // if end == false, holds the current chunk + + ChunkIdx basechunk; + + // constructor initializes to to first chunk in provided region + RegionChunkIterator(const RegionIdx& ri); + + // move to the next chunk, or to the end + void advance(); +}; + + +struct RegionCacheStats +{ + int64_t hits, misses; + // types of misses: + int64_t read; // successfully read from disk + int64_t skipped; // assumed not to exist because not required in a full render + int64_t missing; // non-required region not present on disk + int64_t reqmissing; // required region not present on disk + int64_t corrupt; // found on disk, but failed to read + + RegionCacheStats() : hits(0), misses(0), read(0), skipped(0), missing(0), reqmissing(0), corrupt(0) {} + + RegionCacheStats& operator+=(const RegionCacheStats& rs); +}; + +struct RegionCacheEntry +{ + PosRegionIdx ri; // or [-1, -1] if this entry is empty + RegionFileReader regionfile; + + RegionCacheEntry() : ri(-1,-1) {} +}; + +#define RCACHEBITSX 1 +#define RCACHEBITSZ 1 +#define RCACHEXSIZE (1 << RCACHEBITSX) +#define RCACHEZSIZE (1 << RCACHEBITSZ) +#define RCACHESIZE (RCACHEXSIZE * RCACHEZSIZE) +#define RCACHEXMASK (RCACHEXSIZE - 1) +#define RCACHEZMASK (RCACHEZSIZE - 1) + +struct RegionCache : private nocopy +{ + RegionCacheEntry entries[RCACHESIZE]; + + ChunkTable& chunktable; + RegionTable& regiontable; + RegionCacheStats& stats; + std::string inputpath; + bool fullrender; + // readbuf is an extra less-important cache entry--when a new region is read, it's this entry which will be trashed + // and its storage used for the read (which might fail), but if the read succeeds, the new region is swapped + // into its proper place in the cache, and the previous tenant there moves here + RegionCacheEntry readbuf; + RegionCache(ChunkTable& ctable, RegionTable& rtable, const std::string& inpath, bool fullr, RegionCacheStats& st) + : chunktable(ctable), regiontable(rtable), stats(st), inputpath(inpath), fullrender(fullr) + { + } + + // attempt to decompress a chunk into a buffer; return 0 for success, -1 for missing chunk, + // -2 for other errors + // (this is not const only because zlib won't take const pointers for input) + int getDecompressedChunk(const PosChunkIdx& ci, std::vector& buf, bool& anvil); + + static int getEntryNum(const PosRegionIdx& ri) {return (ri.x & RCACHEXMASK) * RCACHEZSIZE + (ri.z & RCACHEZMASK);} + + void readRegionFile(const PosRegionIdx& ri); +}; + + #endif // REGION_H \ No newline at end of file diff --git a/render.cpp b/render.cpp old mode 100755 new mode 100644 index 952d31e..f65874d --- a/render.cpp +++ b/render.cpp @@ -1,888 +1,929 @@ -// Copyright 2010-2012 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#include -#include - -#include "render.h" -#include "utils.h" - -using namespace std; - - - -int ThreadOutputCache::getIndex(const ZoomTileIdx& zti) const -{ - if (zti.zoom != zoom) - return -1; - return zti.y * (1 << zoom) + zti.x; -} - - - - -// get topmost y-coord in a column (even if column is out-of-bounds--only looks at top edge of bbox) -int64_t topPixelY(int64_t x, int64_t bboxTop, int B) -{ - if ((x % (4*B)) == 0) - return ceildiv(bboxTop, 2*B) * 2*B; - return ceildiv(bboxTop - B, 2*B) * 2*B + B; -} - -TileBlockIterator::TileBlockIterator(const TileIdx& ti, const MapParams& mp) - : tile(ti), mparams(mp), current(0,0), expandedBBox(Pixel(0,0), Pixel(0,0)) -{ - expandedBBox = ti.getBBox(mparams); - expandedBBox.topLeft -= Pixel(2*mparams.B - 1, 2*mparams.B - 1); - expandedBBox.bottomRight += Pixel(2*mparams.B - 1, 2*mparams.B - 1); - - current.x = ceildiv(expandedBBox.topLeft.x, 2*mparams.B) * 2*mparams.B; - current.y = topPixelY(current.x, expandedBBox.topLeft.y, mparams.B); - end = false; - pos = 0; - lastTop = 0; - lastBottom = -1; - nextN = nextE = nextSE = -1; -} - -void TileBlockIterator::advance() -{ - // move down the column - current.y += 2*mparams.B; - // our current pos is SE of our next pos - nextSE = pos; - // when we reset at the top of a column, we may not get an E neighbor, but we always - // gete a N one; so if we have no N neighbor at the moment, we're on the left edge - if (nextN != -1) - { - // if we're not on the left edge, then our N neighbor is our next position's E - // neighbor, etc. - nextE = nextN; // can't just do nextE++; nextE might have been -1 - nextN++; - // gotta watch for the bottom, though, where we might have no N neighbor - if (nextE == lastBottom) - nextN = -1; - } - // advance to next pos - pos++; - - // if we went off the bottom, we need to reset some stuff - if (current.y >= expandedBBox.bottomRight.y) - { - // move over to the next column - current.x += 2*mparams.B; - // ...and we can abort now if we've gone off the right edge; that means we're done - if (current.x >= expandedBBox.bottomRight.x) - { - end = true; - return; - } - // find the top of our new column - current.y = topPixelY(current.x, expandedBBox.topLeft.y, mparams.B); - // since we're up at the top, we have no SE neighbor - nextSE = -1; - // however, we do have a N neighbor, and if the top of the column to the left - // is above us, we have an E as well - if (topPixelY(current.x - 2*mparams.B, expandedBBox.topLeft.y, mparams.B) < current.y) - { - nextE = lastTop; - nextN = nextE + 1; - } - else - { - nextE = -1; - nextN = lastTop; - } - // finally, remember this new column top's position, and remember that our previous - // position was the old column's bottom - lastTop = pos; - lastBottom = pos - 1; - } -} - - - - -PseudocolumnIterator::PseudocolumnIterator(const Pixel& center, const MapParams& mp) : current(0,0,0), mparams(mp) -{ - current = BlockIdx::topBlock(center, mp); - end = false; -} - -void PseudocolumnIterator::advance() -{ - current += BlockIdx(1,-1,-1); - if (current.y < mparams.minY) - end = true; -} - - - -// travel down two neighboring pseudocolumns, setting occlusion edges between their nodes -// ...the first pcol must be N, E, or SE of the second one, and the "which" parameter tells -// which pointer from the first goes to the second--e.g. if which == 4, then the first is -// N of the second, so its S pointer (#4) should be used, and the second's N pointer -// (which - 3 == #1) should be used -void buildDependencies(SceneGraph& sg, int pcol1, int pcol2, int which) -{ - int node1 = sg.getTopNode(pcol1), node2 = sg.getTopNode(pcol2); - if (node1 == -1 || node2 == -1) - return; - - while (true) - { - // if node1 occludes node2, then scan down pcol1 and see if there are any lower - // nodes that also occlude it; use the lowest one, then set node1 to the one after it - if (sg.nodes[node1].bi.occludes(sg.nodes[node2].bi)) - { - int next1 = sg.nodes[node1].children[0]; - while (next1 != -1 && sg.nodes[next1].bi.occludes(sg.nodes[node2].bi)) - { - node1 = next1; - next1 = sg.nodes[node1].children[0]; - } - sg.nodes[node1].children[which] = node2; - node1 = next1; - } - - if (node1 == -1) - return; - - // ...same thing for the other direction - if (sg.nodes[node2].bi.occludes(sg.nodes[node1].bi)) - { - int next2 = sg.nodes[node2].children[0]; - while (next2 != -1 && sg.nodes[next2].bi.occludes(sg.nodes[node1].bi)) - { - node2 = next2; - next2 = sg.nodes[node2].children[0]; - } - sg.nodes[node2].children[which - 3] = node1; - node2 = next2; - } - - if (node2 == -1) - return; - } -} - -#define GETNEIGHBOR(gnid, gndata, gnoff) \ -{ \ - BlockIdx bin = bi + gnoff; \ - PosChunkIdx cin = bin.getChunkIdx(); \ - if (cin == ci) \ - { \ - gnid = chunkdata->id(bin); \ - gndata = chunkdata->data(bin); \ - } \ - else \ - { \ - ChunkData *cdn = rj.chunkcache->getData(cin); \ - gnid = cdn->id(bin); \ - gndata = cdn->data(bin); \ - } \ -} - -#define GETNEIGHBORUD(gnid, gndata, gnoff) \ -{ \ - BlockIdx bin = bi + gnoff; \ - if (bin.y >= 0 && bin.y <= 255) \ - { \ - gnid = chunkdata->id(bin); \ - gndata = chunkdata->data(bin); \ - } \ - else \ - { \ - gnid = 0; \ - gndata = 0; \ - } \ -} - -#define CONNECTFENCE(cfid, cfdata) (rj.blockimages.isOpaque(cfid, cfdata) || cfid == 85 || cfid == 107) -#define CONNECTNETHERFENCE(cfid, cfdata) (rj.blockimages.isOpaque(cfid, cfdata) || cfid == 113 || cfid == 107) - -// given a node that must be drawn, see if we need to do anything special to it--that is, anything that -// doesn't depend purely on its blockID/blockData -// examples: for nodes with no E/S neighbors, we add a little darkness on the EU/SU edge to indicate drop-off; -// for chests, we may need to draw half of a double chest instead if there's another chest next door; etc. -void checkSpecial(SceneGraphNode& node, uint16_t blockID, uint8_t blockData, const PosChunkIdx& ci, ChunkData *chunkdata, RenderJob& rj) -{ - const BlockIdx& bi = node.bi; - - uint16_t blockIDN, blockIDS, blockIDE, blockIDW, blockIDU, blockIDD; - uint8_t blockDataN, blockDataS, blockDataE, blockDataW, blockDataU, blockDataD; - - if (node.bimgoffset == 8) // solid water - { - // if there's water to the W or N, we don't draw those faces - GETNEIGHBOR(blockIDN, blockDataN, BlockIdx(-1,0,0)) - GETNEIGHBOR(blockIDW, blockDataW, BlockIdx(0,1,0)) - bool waterN = blockIDN == 8 || blockIDN == 9; - bool waterW = blockIDW == 8 || blockIDW == 9; - if (waterW && waterN) - node.bimgoffset = 157; - else if (waterW) - node.bimgoffset = 178; - else if (waterN) - node.bimgoffset = 179; - } - else if (blockID == 79) // ice - { - // if there's ice to the W or N, we don't draw those faces - GETNEIGHBOR(blockIDN, blockDataN, BlockIdx(-1,0,0)) - GETNEIGHBOR(blockIDW, blockDataW, BlockIdx(0,1,0)) - bool iceN = blockIDN == 79; - bool iceW = blockIDW == 79; - if (iceW && iceN) - node.bimgoffset = 180; - else if (iceW) - node.bimgoffset = 181; - else if (iceN) - node.bimgoffset = 182; - } - else if (blockID == 85) // fence - { - GETNEIGHBOR(blockIDN, blockDataN, BlockIdx(-1,0,0)) - GETNEIGHBOR(blockIDS, blockDataS, BlockIdx(1,0,0)) - GETNEIGHBOR(blockIDE, blockDataE, BlockIdx(0,-1,0)) - GETNEIGHBOR(blockIDW, blockDataW, BlockIdx(0,1,0)) - int bits = (CONNECTFENCE(blockIDN, blockDataN) ? 0x1 : 0) | - (CONNECTFENCE(blockIDS, blockDataS) ? 0x2 : 0) | - (CONNECTFENCE(blockIDE, blockDataE) ? 0x4 : 0) | - (CONNECTFENCE(blockIDW, blockDataW) ? 0x8 : 0); - if (bits != 0) - node.bimgoffset = 157 + bits; - } - else if (blockID == 113) // nether fence - { - GETNEIGHBOR(blockIDN, blockDataN, BlockIdx(-1,0,0)) - GETNEIGHBOR(blockIDS, blockDataS, BlockIdx(1,0,0)) - GETNEIGHBOR(blockIDE, blockDataE, BlockIdx(0,-1,0)) - GETNEIGHBOR(blockIDW, blockDataW, BlockIdx(0,1,0)) - int bits = (CONNECTNETHERFENCE(blockIDN, blockDataN) ? 0x1 : 0) | - (CONNECTNETHERFENCE(blockIDS, blockDataS) ? 0x2 : 0) | - (CONNECTNETHERFENCE(blockIDE, blockDataE) ? 0x4 : 0) | - (CONNECTNETHERFENCE(blockIDW, blockDataW) ? 0x8 : 0); - if (bits != 0) - node.bimgoffset = 316 + bits; - } - else if (blockID == 54) // chest - { - GETNEIGHBOR(blockIDN, blockDataN, BlockIdx(-1,0,0)) - GETNEIGHBOR(blockIDS, blockDataS, BlockIdx(1,0,0)) - GETNEIGHBOR(blockIDE, blockDataE, BlockIdx(0,-1,0)) - GETNEIGHBOR(blockIDW, blockDataW, BlockIdx(0,1,0)) - // if there's another chest to the N, make this a southern half - if (blockIDN == 54) - node.bimgoffset = (blockDataN == 3) ? 488 : 492; - // ...or if there's one to the S, make this a northern half - else if (blockIDS == 54) - node.bimgoffset = (blockDataS == 3) ? 487 : 491; - // ...same deal with E/W - else if (blockIDW == 54) - node.bimgoffset = (blockDataW == 4) ? 489 : 493; - else if (blockIDE == 54) - node.bimgoffset = (blockDataE == 4) ? 490 : 494; - } - else if (blockID == 95) // locked chest - { - GETNEIGHBOR(blockIDW, blockDataW, BlockIdx(0,1,0)) - // if there's an opaque block to the W, we should face N instead - if (rj.blockimages.isOpaque(blockIDW, blockDataW)) - node.bimgoffset = 271; - } - else if (blockID == 101) // iron bars - { - GETNEIGHBOR(blockIDN, blockDataN, BlockIdx(-1,0,0)) - GETNEIGHBOR(blockIDS, blockDataS, BlockIdx(1,0,0)) - GETNEIGHBOR(blockIDE, blockDataE, BlockIdx(0,-1,0)) - GETNEIGHBOR(blockIDW, blockDataW, BlockIdx(0,1,0)) - // decide which edges to draw based on which neighbors are not air (zero neighbors gets the full cross) - int bits = (blockIDN != 0 ? 0x1 : 0) | (blockIDS != 0 ? 0x2 : 0) | (blockIDE != 0 ? 0x4 : 0) | (blockIDW != 0 ? 0x8 : 0); - static const int ironBarOffsets[16] = {355, 419, 420, 356, 421, 357, 359, 365, 422, 358, 360, 364, 361, 363, 362, 355}; - node.bimgoffset = ironBarOffsets[bits]; - } - else if (blockID == 102) // glass pane - { - GETNEIGHBOR(blockIDN, blockDataN, BlockIdx(-1,0,0)) - GETNEIGHBOR(blockIDS, blockDataS, BlockIdx(1,0,0)) - GETNEIGHBOR(blockIDE, blockDataE, BlockIdx(0,-1,0)) - GETNEIGHBOR(blockIDW, blockDataW, BlockIdx(0,1,0)) - // decide which edges to draw based on which neighbors are not air (zero neighbors gets the full cross) - int bits = (blockIDN != 0 ? 0x1 : 0) | (blockIDS != 0 ? 0x2 : 0) | (blockIDE != 0 ? 0x4 : 0) | (blockIDW != 0 ? 0x8 : 0); - static const int glassPaneOffsets[16] = {366, 423, 424, 367, 425, 368, 370, 376, 426, 369, 371, 375, 372, 374, 373, 366}; - node.bimgoffset = glassPaneOffsets[bits]; - } - else if (blockID == 132) // tripwire - { - GETNEIGHBOR(blockIDN, blockDataN, BlockIdx(-1,0,0)) - GETNEIGHBOR(blockIDS, blockDataS, BlockIdx(1,0,0)) - GETNEIGHBOR(blockIDE, blockDataE, BlockIdx(0,-1,0)) - GETNEIGHBOR(blockIDW, blockDataW, BlockIdx(0,1,0)) - // decide which edges to draw based on which neighbors are not air (zero neighbors gets EW) - int bits = (blockIDN != 0 ? 0x1 : 0) | (blockIDS != 0 ? 0x2 : 0) | (blockIDE != 0 ? 0x4 : 0) | (blockIDW != 0 ? 0x8 : 0); - static const int tripwireOffsets[16] = {549, 544, 544, 544, 549, 545, 547, 553, 549, 546, 548, 552, 549, 551, 550, 543}; - node.bimgoffset = tripwireOffsets[bits]; - } - else if ((blockID == 104 || blockID == 105) && blockData == 7) // full stem - { - GETNEIGHBOR(blockIDN, blockDataN, BlockIdx(-1,0,0)) - GETNEIGHBOR(blockIDS, blockDataS, BlockIdx(1,0,0)) - GETNEIGHBOR(blockIDE, blockDataE, BlockIdx(0,-1,0)) - GETNEIGHBOR(blockIDW, blockDataW, BlockIdx(0,1,0)) - int target = (blockID == 104) ? 86 : 103; - if (blockIDN == target) - node.bimgoffset = 403; - else if (blockIDS == target) - node.bimgoffset = 404; - else if (blockIDE == target) - node.bimgoffset = 405; - else if (blockIDW == target) - node.bimgoffset = 406; - } - else if (blockID == 64) // wooden door - { - GETNEIGHBORUD(blockIDU, blockDataU, BlockIdx(0,0,1)) - GETNEIGHBORUD(blockIDD, blockDataD, BlockIdx(0,0,-1)) - bool isTop = blockIDD == 64; - uint8_t blockDataTop = isTop ? blockData : blockDataU; - uint8_t blockDataBottom = isTop ? blockDataD : blockData; - int dir = blockDataBottom % 4; - if (blockDataBottom & 0x4) - dir = (dir + ((blockDataTop & 0x1) ? 3 : 1)) % 4; - static const int topImages[4] = {81, 78, 80, 79}; - static const int bottomImages[4] = {77, 74, 76, 75}; - node.bimgoffset = isTop ? topImages[dir] : bottomImages[dir]; - } - else if (blockID == 71) // iron door - { - GETNEIGHBORUD(blockIDU, blockDataU, BlockIdx(0,0,1)) - GETNEIGHBORUD(blockIDD, blockDataD, BlockIdx(0,0,-1)) - bool isTop = blockIDD == 71; - uint8_t blockDataTop = isTop ? blockData : blockDataU; - uint8_t blockDataBottom = isTop ? blockDataD : blockData; - int dir = blockDataBottom % 4; - if (blockDataBottom & 0x4) - dir = (dir + ((blockDataTop & 0x1) ? 3 : 1)) % 4; - static const int topImages[4] = {118, 115, 117, 116}; - static const int bottomImages[4] = {114, 111, 113, 112}; - node.bimgoffset = isTop ? topImages[dir] : bottomImages[dir]; - } - - //!!!!!!!! for now, only fully opaque blocks can have drop-off shadows, but some others like snow could - // probably use them, too - if (rj.blockimages.isOpaque(node.bimgoffset)) - { - GETNEIGHBOR(blockIDS, blockDataS, BlockIdx(1,0,0)) - GETNEIGHBOR(blockIDE, blockDataE, BlockIdx(0,-1,0)) - GETNEIGHBOR(blockIDD, blockDataD, BlockIdx(0,0,-1)) - - //!!!!!! neighboring blocks that aren't full height like snow and half-steps should probably produce - // the drop-off effect, too - //!!!!!!! not to mention fully-transparent block images - if (blockIDS == 0) // air - node.darkenSU = true; - if (blockIDE == 0) // air - node.darkenEU = true; - if (blockIDD == 0) // air - { - node.darkenND = true; - node.darkenWD = true; - } - } -} - -//!!!!!! speed these up--lots of conditionals at the moment -void darkenEUEdge(RGBAImage& img, int32_t xstart, int32_t ystart, int B) -{ - // EU edge starts at [2B-1,0] and goes one step DL, then one step L, etc., for a total of 2B-1 steps - int32_t x = xstart + 2*B-1, y = ystart; - bool which = true; - for (int i = 0; i < 2*B-1; i++) - { - if (x >= 0 && x < img.w && y >= 0 && y < img.h) - blend(img(x, y), 0x60000000); - x--; - if (which) - y++; - which = !which; - } -} -void darkenSUEdge(RGBAImage& img, int32_t xstart, int32_t ystart, int B) -{ - // SU edge starts at [2B,0] and goes one step DR, then one step R, etc., for a total of 2B-1 steps - int32_t x = xstart + 2*B, y = ystart; - bool which = true; - for (int i = 0; i < 2*B-1; i++) - { - if (x >= 0 && x < img.w && y >= 0 && y < img.h) - blend(img(x, y), 0x60000000); - x++; - if (which) - y++; - which = !which; - } -} -void darkenNDEdge(RGBAImage& img, int32_t xstart, int32_t ystart, int B) -{ - // ND edge starts at [2B-1,4B-1] and goes one step UL, then one step L, etc., for a total of 2B-1 steps - int32_t x = xstart + 2*B-1, y = ystart + 4*B-1; - bool which = true; - for (int i = 0; i < 2*B-1; i++) - { - if (x >= 0 && x < img.w && y >= 0 && y < img.h) - blend(img(x, y), 0x60000000); - x--; - if (which) - y--; - which = !which; - } -} -void darkenWDEdge(RGBAImage& img, int32_t xstart, int32_t ystart, int B) -{ - // WD edge starts at [2B,4B-1] and goes one step UR, then one step R, etc., for a total of 2B-1 steps - int32_t x = xstart + 2*B, y = ystart + 4*B-1; - bool which = true; - for (int i = 0; i < 2*B-1; i++) - { - if (x >= 0 && x < img.w && y >= 0 && y < img.h) - blend(img(x, y), 0x60000000); - x++; - if (which) - y--; - which = !which; - } -} - -void drawNode(SceneGraphNode& node, RGBAImage& img, const BlockImages& blockimages) -{ - alphablit(blockimages.img, blockimages.getRect(node.bimgoffset), img, node.xstart, node.ystart); - if (node.darkenEU) - darkenEUEdge(img, node.xstart, node.ystart, blockimages.rectsize / 4); - if (node.darkenSU) - darkenSUEdge(img, node.xstart, node.ystart, blockimages.rectsize / 4); - if (node.darkenND) - darkenNDEdge(img, node.xstart, node.ystart, blockimages.rectsize / 4); - if (node.darkenWD) - darkenWDEdge(img, node.xstart, node.ystart, blockimages.rectsize / 4); - node.drawn = true; -} - -void drawSubgraph(SceneGraph& sg, int rootnode, RGBAImage& img, const BlockImages& blockimages) -{ - if (sg.nodes[rootnode].drawn) - return; - vector& stack = sg.nodestack; - stack.clear(); - stack.push_back(rootnode); - while (!stack.empty()) - { - SceneGraphNode& node = sg.nodes[stack.back()]; - bool pushed = false; - for (int i = 0; i < 7; i++) - if (node.children[i] != -1 && !sg.nodes[node.children[i]].drawn) - { - stack.push_back(node.children[i]); - pushed = true; - break; - } - if (pushed) - continue; - drawNode(node, img, blockimages); - stack.pop_back(); - } -} - -//!!!!!!!!!!!!! many opportunities for optimization in here -bool renderTile(const TileIdx& ti, RenderJob& rj, RGBAImage& tile) -{ - // if this tile isn't required, abort - if (!rj.tiletable->isRequired(ti)) - return false; - - // if this tile doesn't fit in the Google map, skip it - string tilefile = rj.outputpath + "/" + ti.toFilePath(rj.mp); - if (tilefile.empty()) - { - cerr << "tile [" << ti.x << "," << ti.y << "] exceeds the possible map size! skipping..." << endl; - return false; - } - // if we've somehow already drawn this tile (which should not be possible!), skip it - if (rj.tiletable->isDrawn(ti)) - { - cerr << "attempted to draw tile [" << ti.x << "," << ti.y << "] more than once!" << endl; - return false; - } - - // mark this tile drawn - rj.tiletable->setDrawn(ti); - - // if we're in test mode, don't actually draw anything - if (rj.testmode) - return true; - - SceneGraph& sg = *rj.scenegraph; - sg.clear(); - tile.create(rj.mp.tileSize(), rj.mp.tileSize()); - const BlockImages& blockimages = rj.blockimages; - - // we'll be given block center pixels in absolute coords, but for blitting, we need the block bounding box - // in tile image coords; compute the translation that gives us that - // (subtract the tile bounding box corner, then subtract another [2B,2B] to convert from block center to box) - BBox tilebb = ti.getBBox(rj.mp); - int64_t xoff = -tilebb.topLeft.x - 2*rj.mp.B; - int64_t yoff = -tilebb.topLeft.y - 2*rj.mp.B; - - // step 1: build the scene graph - // ...we'll iterate through the pseudocolumn center pixels, starting in the top left of the image, moving down then - // right; this means that by the time we reach a pseudocolumn, its N, E, and SE neighbors have already been done, - // so we can add any necessary edges to or from those neighbors - for (TileBlockIterator tbit(ti, rj.mp); !tbit.end; tbit.advance()) - { - // we'll start at the top of the pseudocolumn and go down, adding any non-air blocks to the graph, stopping - // at the first totally opaque block - sg.pcols.push_back(-1); - PosChunkIdx lastci(-1,-1); - ChunkData *chunkdata = NULL; - int prevnode = -1; - for (PseudocolumnIterator pcit(tbit.current, rj.mp); !pcit.end; pcit.advance()) - { - // look up chunk data (we might have it already) - PosChunkIdx ci = pcit.current.getChunkIdx(); - if (ci != lastci) - chunkdata = rj.chunkcache->getData(ci); - - // get block type and data - uint16_t blockID = chunkdata->id(pcit.current); - uint8_t blockData = chunkdata->data(pcit.current); - int initialoffset = blockimages.getOffset(blockID, blockData); // we might use a different one after checkSpecial - - // if this is air, move on (we *always* consider air to be transparent; it has no block image) - if (blockID == 0) - continue; - - // create a node for this block - SceneGraphNode node(tbit.current.x + xoff, tbit.current.y + yoff, pcit.current, initialoffset); - - // check out neighboring blocks to see if we need to do anything special: set the darken-edge flags, - // or change the offset to a special one (one not corresponding to a plain blockID/blockData combo) - checkSpecial(node, blockID, blockData, ci, chunkdata, rj); - - // if this is not air, but is nonetheless transparent, move on - if (blockimages.isTransparent(node.bimgoffset)) - continue; - - // commit the node - int thisnode = sg.nodes.size(); - sg.nodes.push_back(node); - - // link our parent (the node above us in our own pseudocolumn) to us - if (prevnode != -1) - sg.nodes[prevnode].children[0] = thisnode; - // ...if we have no parent, then we're the top of this pcol - else - sg.pcols.back() = thisnode; - prevnode = thisnode; - - // if this block is opaque, we're done with this pcol - if (blockimages.isOpaque(node.bimgoffset)) - break; - } - - // check dependencies with our N, E, and SE neighbors - if (tbit.nextN != -1) - buildDependencies(sg, tbit.nextN, tbit.pos, 4); - if (tbit.nextE != -1) - buildDependencies(sg, tbit.nextE, tbit.pos, 5); - if (tbit.nextSE != -1) - buildDependencies(sg, tbit.nextSE, tbit.pos, 6); - } - - // if we didn't find anything to draw--i.e. our final image will be fully transparent--then there's - // no sense saving it to disk - if (sg.nodes.empty()) - return false; - - // step 2: traverse the graph and draw the image - for (int i = 0; i < (int)sg.nodes.size(); i++) - drawSubgraph(sg, i, tile, blockimages); - - // save the image to disk - if (!tile.writePNG(tilefile)) - cerr << "failed to write " << tilefile << endl; - return true; -} - - - -bool renderZoomTile(const ZoomTileIdx& zti, RenderJob& rj, RGBAImage& tile) -{ - // if this is a base tile, render it - if (zti.zoom == rj.mp.baseZoom) - return renderTile(zti.toTileIdx(rj.mp), rj, tile); - - // see whether this entire tile can be rejected early - if (rj.tiletable->reject(zti, rj.mp)) - return false; - - // render the four subtiles (if they're needed) - TileCache::ZoomLevel& zlevel = rj.tilecache->levels[rj.mp.baseZoom - zti.zoom - 1]; - ZoomTileIdx topleft = zti.toZoom(zti.zoom + 1); - zlevel.used[0] = renderZoomTile(topleft, rj, zlevel.tiles[0]); - zlevel.used[1] = renderZoomTile(topleft.add(0,1), rj, zlevel.tiles[1]); - zlevel.used[2] = renderZoomTile(topleft.add(1,0), rj, zlevel.tiles[2]); - zlevel.used[3] = renderZoomTile(topleft.add(1,1), rj, zlevel.tiles[3]); - - // if none of the subtiles are used, we have nothing to do - int usedcount = 0; - for (int i = 0; i < 4; i++) - if (zlevel.used[i]) - usedcount++; - if (usedcount == 0) - return false; - - // if we're in test mode, pretend we've successfully drawn - if (rj.testmode) - return true; - - // if some of the subtiles are unused and this is an incremental update, we need to - // load the existing version of this tile (if there is one) to get the unchanged portions - string tilefile = rj.outputpath + "/" + zti.toFilePath(); - if (usedcount < 4 && !rj.fullrender) - { - // if it doesn't read, no big deal (it may not exist anyway) - if (!tile.readPNG(tilefile) || tile.w != rj.mp.tileSize() || tile.h != rj.mp.tileSize()) - tile.create(rj.mp.tileSize(), rj.mp.tileSize()); - } - else - tile.create(rj.mp.tileSize(), rj.mp.tileSize()); - - // combine the four subtile images into this tile's image - int halfsize = rj.mp.tileSize() / 2; - if (zlevel.used[0]) - reduceHalf(tile, ImageRect(0, 0, halfsize, halfsize), zlevel.tiles[0]); - if (zlevel.used[1]) - reduceHalf(tile, ImageRect(0, halfsize, halfsize, halfsize), zlevel.tiles[1]); - if (zlevel.used[2]) - reduceHalf(tile, ImageRect(halfsize, 0, halfsize, halfsize), zlevel.tiles[2]); - if (zlevel.used[3]) - reduceHalf(tile, ImageRect(halfsize, halfsize, halfsize, halfsize), zlevel.tiles[3]); - - // save to disk - if (!tile.writePNG(tilefile)) - cerr << "failed to write " << tilefile << endl; - return true; -} - - - -bool renderZoomTile(const ZoomTileIdx& zti, RenderJob& rj, RGBAImage& tile, const ThreadOutputCache& tocache) -{ - // if this is at or below the ThreadOutputCache level, abort - if (zti.zoom >= tocache.zoom) - return false; - - // get the four subtiles: if we're one level above the ThreadOutputCache level, they're in the - // cache; otherwise, we recurse - // ...we use the ZoomLevel::used bits either way - TileCache::ZoomLevel& zlevel = rj.tilecache->levels[rj.mp.baseZoom - zti.zoom - 1]; - ZoomTileIdx topleft = zti.toZoom(zti.zoom + 1); - const RGBAImage *tile0, *tile1, *tile2, *tile3; - if (zti.zoom == tocache.zoom - 1) - { - int idx = tocache.getIndex(topleft); - zlevel.used[0] = tocache.used[idx]; - tile0 = &tocache.images[idx]; - idx = tocache.getIndex(topleft.add(0,1)); - zlevel.used[1] = tocache.used[idx]; - tile1 = &tocache.images[idx]; - idx = tocache.getIndex(topleft.add(1,0)); - zlevel.used[2] = tocache.used[idx]; - tile2 = &tocache.images[idx]; - idx = tocache.getIndex(topleft.add(1,1)); - zlevel.used[3] = tocache.used[idx]; - tile3 = &tocache.images[idx]; - } - else - { - zlevel.used[0] = renderZoomTile(topleft, rj, zlevel.tiles[0], tocache); - tile0 = &zlevel.tiles[0]; - zlevel.used[1] = renderZoomTile(topleft.add(0,1), rj, zlevel.tiles[1], tocache); - tile1 = &zlevel.tiles[1]; - zlevel.used[2] = renderZoomTile(topleft.add(1,0), rj, zlevel.tiles[2], tocache); - tile2 = &zlevel.tiles[2]; - zlevel.used[3] = renderZoomTile(topleft.add(1,1), rj, zlevel.tiles[3], tocache); - tile3 = &zlevel.tiles[3]; - } - - // if none of the subtiles are used, we have nothing to do - int usedcount = 0; - for (int i = 0; i < 4; i++) - if (zlevel.used[i]) - usedcount++; - if (usedcount == 0) - return false; - - // if we're in test mode, pretend we've successfully drawn - if (rj.testmode) - return true; - - // if some of the subtiles are unused and this is an incremental update, we need to - // load the existing version of this tile (if there is one) to get the unchanged portions - string tilefile = rj.outputpath + "/" + zti.toFilePath(); - if (usedcount < 4 && !rj.fullrender) - { - // if it doesn't read, no big deal (it may not exist anyway) - if (!tile.readPNG(tilefile) || tile.w != rj.mp.tileSize() || tile.h != rj.mp.tileSize()) - tile.create(rj.mp.tileSize(), rj.mp.tileSize()); - } - else - tile.create(rj.mp.tileSize(), rj.mp.tileSize()); - - // combine the four subtile images into this tile's image - int halfsize = rj.mp.tileSize() / 2; - if (zlevel.used[0]) - reduceHalf(tile, ImageRect(0, 0, halfsize, halfsize), *tile0); - if (zlevel.used[1]) - reduceHalf(tile, ImageRect(0, halfsize, halfsize, halfsize), *tile1); - if (zlevel.used[2]) - reduceHalf(tile, ImageRect(halfsize, 0, halfsize, halfsize), *tile2); - if (zlevel.used[3]) - reduceHalf(tile, ImageRect(halfsize, halfsize, halfsize, halfsize), *tile3); - - // save to disk - if (!tile.writePNG(tilefile)) - cerr << "failed to write " << tilefile << endl; - return true; -} - - - - - -void testTileIterator() -{ - MapParams mp(0,0,0); - for (mp.B = 2; mp.B <= 6; mp.B++) - for (mp.T = 1; mp.T <= 4; mp.T++) - { - cout << "B = " << mp.B << " T = " << mp.T << endl; - for (int64_t tx = -5; tx <= 5; tx++) - for (int64_t ty = -5; ty <= 5; ty++) - { - // get computed BBox - TileIdx ti(tx,ty); - BBox bbox = ti.getBBox(mp); - - // use TileBlockIterator to go through the block centers in the tile; verify that - // each is actually in the tile by getting the topmost block with that center and checking - // its bounding box against the tile's box - // ...and also make sure that the N, E, SE neighbors are where they're supposed to be - vector blocks; - for (TileBlockIterator it(ti, mp); !it.end; it.advance()) - { - BlockIdx bi = BlockIdx::topBlock(it.current, mp); - //cout << "[" << bi.x << "," << bi.z << "," << bi.y << "] pos " << it.pos << " nextE " << it.nextE << " nextN " << it.nextN << " nextSE " << it.nextSE << endl; - if (bi.getCenter(mp) != it.current) - { - cout << "topBlock mismatch: [" << it.current.x << "," << it.current.y << "] -> [" << bi.x << "," << bi.z << "," << bi.y << "] -> [" << bi.getCenter(mp).x << "," << bi.getCenter(mp).y << "]" << endl; - return; - } - if (!bi.getBBox(mp).overlaps(bbox)) - { - cout << "block centered at [" << it.current.x << "," << it.current.y << "] is not in tile!" << endl; - cout << "[" << bbox.topLeft.x << "," << bbox.topLeft.y << "] to [" << bbox.bottomRight.x << "," << bbox.bottomRight.y << "]" << endl; - return; - } - if (it.pos != blocks.size()) - { - cout << "block position seems to have advanced too fast!" << endl; - return; - } - blocks.push_back(bi); - if (it.nextE >= it.pos || it.nextN >= it.pos || it.nextSE >= it.pos) - { - cout << "neighbor position is *after* us!" << endl; - return; - } - if (it.nextE != -1 && blocks[it.nextE].z != bi.z - 1) - { - cout << "E neighbor pos is wrong" << endl; - return; - } - if (it.nextN != -1 && blocks[it.nextN].x != bi.x - 1) - { - cout << "N neighbor pos is wrong" << endl; - return; - } - if (it.nextSE != -1 && (blocks[it.nextSE].z != bi.z - 1 || blocks[it.nextSE].x != bi.x + 1)) - { - cout << "SE neighbor pos is wrong" << endl; - return; - } - } - } - } -} - -void testPColIterator() -{ - MapParams mp(6,1,0); - for (int64_t tx = -5; tx <= 5; tx++) - for (int64_t ty = -5; ty <= 5; ty++) - { - TileIdx ti(tx,ty); - vector centers; - for (TileBlockIterator tbit(ti, mp); !tbit.end; tbit.advance()) - { - centers.push_back(tbit.current); - - // check this pseudocolumn against its N, E, SE neighbors; make sure the blocks - // chosen by the iterator actually have the proper relationships - auto_ptr nit((tbit.nextN != -1) ? new PseudocolumnIterator(centers[tbit.nextN], mp) : NULL); - auto_ptr eit((tbit.nextE != -1) ? new PseudocolumnIterator(centers[tbit.nextE], mp) : NULL); - auto_ptr seit((tbit.nextSE != -1) ? new PseudocolumnIterator(centers[tbit.nextSE], mp) : NULL); - for (PseudocolumnIterator pcit(tbit.current, mp); !pcit.end; pcit.advance()) - { - if (nit.get() != NULL) - { - if (nit->current != pcit.current + BlockIdx(-1,0,0)) - cout << "N pcol iterator block is not actually N neighbor!" << endl; - if (nit->current.getCenter(mp) != pcit.current.getCenter(mp) + Pixel(-2*mp.B, mp.B)) - cout << "N neighbor pixel is wrong!" << endl; - nit->advance(); - } - if (eit.get() != NULL) - { - if (eit->current != pcit.current + BlockIdx(0,-1,0)) - cout << "E pcol iterator block is not actually E neighbor!" << endl; - if (eit->current.getCenter(mp) != pcit.current.getCenter(mp) + Pixel(-2*mp.B, -mp.B)) - cout << "E neighbor pixel is wrong!" << endl; - eit->advance(); - } - if (seit.get() != NULL) - { - if (seit->current != pcit.current + BlockIdx(1,-1,0)) - cout << "SE pcol iterator block is not actually SE neighbor!" << endl; - if (seit->current.getCenter(mp) != pcit.current.getCenter(mp) + Pixel(0, -2*mp.B)) - cout << "SE neighbor pixel is wrong!" << endl; - seit->advance(); - } - } - } - } -} +// Copyright 2010-2012 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#include +#include +#include + +#include "render.h" +#include "utils.h" + +using namespace std; + + + +int ThreadOutputCache::getIndex(const ZoomTileIdx& zti) const +{ + if (zti.zoom != zoom) + return -1; + return zti.y * (1 << zoom) + zti.x; +} + + + + +// get topmost y-coord in a column (even if column is out-of-bounds--only looks at top edge of bbox) +int64_t topPixelY(int64_t x, int64_t bboxTop, int B) +{ + if ((x % (4*B)) == 0) + return ceildiv(bboxTop, 2*B) * 2*B; + return ceildiv(bboxTop - B, 2*B) * 2*B + B; +} + +TileBlockIterator::TileBlockIterator(const TileIdx& ti, const MapParams& mp) + : current(0,0), mparams(mp), tile(ti), expandedBBox(Pixel(0,0), Pixel(0,0)) +{ + expandedBBox = ti.getBBox(mparams); + expandedBBox.topLeft -= Pixel(2*mparams.B - 1, 2*mparams.B - 1); + expandedBBox.bottomRight += Pixel(2*mparams.B - 1, 2*mparams.B - 1); + + current.x = ceildiv(expandedBBox.topLeft.x, 2*mparams.B) * 2*mparams.B; + current.y = topPixelY(current.x, expandedBBox.topLeft.y, mparams.B); + end = false; + pos = 0; + lastTop = 0; + lastBottom = -1; + nextN = nextE = nextSE = -1; +} + +void TileBlockIterator::advance() +{ + // move down the column + current.y += 2*mparams.B; + // our current pos is SE of our next pos + nextSE = pos; + // when we reset at the top of a column, we may not get an E neighbor, but we always + // gete a N one; so if we have no N neighbor at the moment, we're on the left edge + if (nextN != -1) + { + // if we're not on the left edge, then our N neighbor is our next position's E + // neighbor, etc. + nextE = nextN; // can't just do nextE++; nextE might have been -1 + nextN++; + // gotta watch for the bottom, though, where we might have no N neighbor + if (nextE == lastBottom) + nextN = -1; + } + // advance to next pos + pos++; + + // if we went off the bottom, we need to reset some stuff + if (current.y >= expandedBBox.bottomRight.y) + { + // move over to the next column + current.x += 2*mparams.B; + // ...and we can abort now if we've gone off the right edge; that means we're done + if (current.x >= expandedBBox.bottomRight.x) + { + end = true; + return; + } + // find the top of our new column + current.y = topPixelY(current.x, expandedBBox.topLeft.y, mparams.B); + // since we're up at the top, we have no SE neighbor + nextSE = -1; + // however, we do have a N neighbor, and if the top of the column to the left + // is above us, we have an E as well + if (topPixelY(current.x - 2*mparams.B, expandedBBox.topLeft.y, mparams.B) < current.y) + { + nextE = lastTop; + nextN = nextE + 1; + } + else + { + nextE = -1; + nextN = lastTop; + } + // finally, remember this new column top's position, and remember that our previous + // position was the old column's bottom + lastTop = pos; + lastBottom = pos - 1; + } +} + + + + +PseudocolumnIterator::PseudocolumnIterator(const Pixel& center, const MapParams& mp) : current(0,0,0), mparams(mp) +{ + current = BlockIdx::topBlock(center, mp); + end = false; +} + +void PseudocolumnIterator::advance() +{ + current += BlockIdx(1,-1,-1); + if (current.y < mparams.minY) + end = true; +} + + + +// travel down two neighboring pseudocolumns, setting occlusion edges between their nodes +// ...the first pcol must be N, E, or SE of the second one, and the "which" parameter tells +// which pointer from the first goes to the second--e.g. if which == 4, then the first is +// N of the second, so its S pointer (#4) should be used, and the second's N pointer +// (which - 3 == #1) should be used +void buildDependencies(SceneGraph& sg, int pcol1, int pcol2, int which) +{ + int node1 = sg.getTopNode(pcol1), node2 = sg.getTopNode(pcol2); + if (node1 == -1 || node2 == -1) + return; + + while (true) + { + // if node1 occludes node2, then scan down pcol1 and see if there are any lower + // nodes that also occlude it; use the lowest one, then set node1 to the one after it + if (sg.nodes[node1].bi.occludes(sg.nodes[node2].bi)) + { + int next1 = sg.nodes[node1].children[0]; + while (next1 != -1 && sg.nodes[next1].bi.occludes(sg.nodes[node2].bi)) + { + node1 = next1; + next1 = sg.nodes[node1].children[0]; + } + sg.nodes[node1].children[which] = node2; + node1 = next1; + } + + if (node1 == -1) + return; + + // ...same thing for the other direction + if (sg.nodes[node2].bi.occludes(sg.nodes[node1].bi)) + { + int next2 = sg.nodes[node2].children[0]; + while (next2 != -1 && sg.nodes[next2].bi.occludes(sg.nodes[node1].bi)) + { + node2 = next2; + next2 = sg.nodes[node2].children[0]; + } + sg.nodes[node2].children[which - 3] = node1; + node2 = next2; + } + + if (node2 == -1) + return; + } +} + +struct Block +{ + uint16_t id; + uint8_t data; + + inline Block(uint16_t id_, uint8_t data_): id(id_), data(data_) {} +}; + +inline Block getNeighbor(ChunkData *chunkdata, RenderJob& rj, const PosChunkIdx& ci, const BlockIdx& bin) +{ + PosChunkIdx cin = bin.getChunkIdx(); + if (cin != ci) + chunkdata = rj.chunkcache->getData(cin); + + return Block(chunkdata->id(bin), chunkdata->data(bin)); +} + +inline Block getNeighborUD(ChunkData* chunkdata, const BlockIdx& bin) +{ + if (bin.y >= 0 && bin.y <= 255) + return Block(chunkdata->id(bin), chunkdata->data(bin)); + return Block(0, 0); +} + +inline bool connectFence(RenderJob& rj, const Block& block) +{ + return block.id == 85 || block.id == 107 || rj.blockimages.isOpaque(block.id, block.data); +} + +inline bool connectNetherFence(RenderJob& rj, const Block& block) +{ + return block.id == 113 || block.id == 107 || rj.blockimages.isOpaque(block.id, block.data); +} + +inline bool connectCobblestoneWall(RenderJob& rj, const Block& block) +{ + return block.id == 139 || block.id == 107 || block.id == 120 || (rj.blockimages.isOpaque(block.id, block.data) && block.id != 46 && block.id != 89 && block.id != 54 && block.id != 130 && block.id != 29 && block.id != 33); +} + +inline bool connectCobblestoneWallUp(RenderJob& rj, const Block& block) +{ + return block.id != 0 || rj.blockimages.isOpaque(block.id, block.data); +} + +inline bool connectPane(RenderJob& rj, const Block& block) +{ + return rj.blockimages.isOpaque(block.id, block.data) && block.id != 130; +} + +// fromside: 0 - NS, 1 - WE +inline bool connectRedstone(RenderJob& rj, const Block& block, uint8_t fromside) +{ + return block.id == 28 || block.id == 55 || block.id == 75 || block.id == 75 || block.id ==146 || block.id ==149 || block.id == 150 || block.id == 152 || ((block.id == 93 || block.id == 94) && block.data % 2 == fromside); +} + +inline bool connectTripWire(RenderJob& rj, const Block& block) +{ + return block.id == 131 || block.id == 132; +} + +inline int getNeighborAirMask(ChunkData* chunkdata, RenderJob& rj, const PosChunkIdx& ci, const BlockIdx& bi) +{ + Block blockN = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(-1,0,0)); + Block blockS = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(1,0,0)); + Block blockE = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,-1,0)); + Block blockW = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,1,0)); + return (blockN.id != 0 ? 0x1 : 0) | + (blockS.id != 0 ? 0x2 : 0) | + (blockE.id != 0 ? 0x4 : 0) | + (blockW.id != 0 ? 0x8 : 0); +} + +// given a node that must be drawn, see if we need to do anything special to it--that is, anything that +// doesn't depend purely on its blockID/blockData +// examples: for nodes with no E/S neighbors, we add a little darkness on the EU/SU edge to indicate drop-off; +// for chests, we may need to draw half of a double chest instead if there's another chest next door; etc. +void checkSpecial(SceneGraphNode& node, uint16_t blockID, uint8_t blockData, const PosChunkIdx& ci, ChunkData *chunkdata, RenderJob& rj) +{ + const BlockIdx& bi = node.bi; + + //if (node.bimgoffset == 8) // solid water + if ((blockID == 8 || blockID == 9) && (blockData == 0 || blockData > 7)) // solid water + { + // if there's water to the W or S, we don't draw those faces + Block blockW = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(-1,0,0)); + Block blockS = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,1,0)); + bool waterW = blockW.id == 8 || blockW.id == 9; + bool waterS = blockS.id == 8 || blockS.id == 9; + if (waterW && waterS) + node.bimgoffset += 1; + else if (waterW) + node.bimgoffset += 2; + else if (waterS) + node.bimgoffset += 3; + } + else if (blockID == 79) // ice + { + // if there's ice to the W or S, we don't draw those faces + Block blockW = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(-1,0,0)); + Block blockS = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,1,0)); + + bool iceW = blockW.id == 79; + bool iceS = blockS.id == 79; + if (iceW && iceS) + node.bimgoffset += 1; + else if (iceW) + node.bimgoffset += 2; + else if (iceS) + node.bimgoffset += 3; + } + else if (blockID == 85 || blockID == 113) // fence, nether fence + { + Block blockW = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(-1,0,0)); + Block blockS = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,1,0)); + Block blockN = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,-1,0)); + Block blockE = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(1,0,0)); + int bits; + if(blockID == 85) // fence + bits = (connectFence(rj, blockN) ? 0x1 : 0) | + (connectFence(rj, blockS) ? 0x2 : 0) | + (connectFence(rj, blockE) ? 0x4 : 0) | + (connectFence(rj, blockW) ? 0x8 : 0); + else // nether fence + bits = (connectNetherFence(rj, blockN) ? 0x1 : 0) | + (connectNetherFence(rj, blockS) ? 0x2 : 0) | + (connectNetherFence(rj, blockE) ? 0x4 : 0) | + (connectNetherFence(rj, blockW) ? 0x8 : 0); + if (bits != 0) + node.bimgoffset += bits; + } + else if (blockID == 139) // cobblestone wall + { + Block blockW = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(-1,0,0)); + Block blockS = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,1,0)); + Block blockN = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,-1,0)); + Block blockE = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(1,0,0)); + Block blockU = getNeighborUD(chunkdata, bi + BlockIdx(0,0,1)); + int bits = (connectCobblestoneWall(rj, blockN) ? 0x1 : 0) | + (connectCobblestoneWall(rj, blockS) ? 0x2 : 0) | + (connectCobblestoneWall(rj, blockE) ? 0x4 : 0) | + (connectCobblestoneWall(rj, blockW) ? 0x8 : 0); + + if (bits != 0) + { + if(connectCobblestoneWallUp(rj, blockU)) + node.bimgoffset += bits; + else if (bits == 3) + node.bimgoffset += 16; + else if(bits == 12) + node.bimgoffset += 17; + else + node.bimgoffset += bits; + } + } + else if (blockID == 54 || blockID == 146) // chest + { + Block blockW = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(-1,0,0)); + Block blockS = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,1,0)); + Block blockN = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,-1,0)); + Block blockE = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(1,0,0)); + // if there's another chest to the N, make this a southern half + if (blockN.id == blockID) + node.bimgoffset += (blockN.data == 4) ? 6 : 10; + // ...or if there's one to the S, make this a northern half + else if (blockS.id == blockID) + node.bimgoffset += (blockS.data == 4) ? 5 : 9; + // ...same deal with E/W + else if (blockW.id == blockID) + node.bimgoffset += (blockW.data == 2) ? 4 : 5; + else if (blockE.id == blockID) + node.bimgoffset += (blockE.data == 2) ? 3 : 4; + } + else if (blockID == 101 || blockID == 102) // iron bars, glass pane + { + Block blockW = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(-1,0,0)); + Block blockS = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,1,0)); + Block blockN = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,-1,0)); + Block blockE = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(1,0,0)); + // decide which edges to draw based on which neighbors are not air (zero neighbors gets the full cross) + int bits = ((blockN.id == blockID || connectPane(rj, blockN)) ? 0x1 : 0) | + ((blockS.id == blockID || connectPane(rj, blockS)) ? 0x2 : 0) | + ((blockE.id == blockID || connectPane(rj, blockE)) ? 0x4 : 0) | + ((blockW.id == blockID || connectPane(rj, blockW)) ? 0x8 : 0); + if (bits != 0 && bits != 15) + node.bimgoffset += bits; + } + else if (blockID == 55 || blockID == 132) // wire + { + Block blockW = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(-1,0,0)); + Block blockS = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,1,0)); + Block blockN = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,-1,0)); + Block blockE = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(1,0,0)); + // decide which edges to draw based on which neighbors are not air (zero neighbors gets EW) + int bits = 0; + if(blockID == 55) + bits = (connectRedstone(rj, blockN, 0) ? 0x1 : 0) | + (connectRedstone(rj, blockS, 0) ? 0x2 : 0) | + (connectRedstone(rj, blockW, 1) ? 0x4 : 0) | + (connectRedstone(rj, blockE, 1) ? 0x8 : 0); + else if(blockID == 132) + bits = (connectTripWire(rj, blockN) ? 0x1 : 0) | + (connectTripWire(rj, blockS) ? 0x2 : 0) | + (connectTripWire(rj, blockW) ? 0x4 : 0) | + (connectTripWire(rj, blockE) ? 0x8 : 0); + else + bits = (blockN.id == blockID ? 0x1 : 0) | + (blockS.id == blockID ? 0x2 : 0) | + (blockW.id == blockID ? 0x4 : 0) | + (blockE.id == blockID ? 0x8 : 0); + static const int wireOffsets[16] = {0, 1, 1, 1, 2, 6, 7, 8, 2, 3, 4, 5, 2, 9, 10, 11}; + node.bimgoffset += wireOffsets[bits]; + } + else if ((blockID == 104 || blockID == 105) && blockData == 7) // full stem + { + Block blockW = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(-1,0,0)); + Block blockS = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,1,0)); + Block blockN = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,-1,0)); + Block blockE = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(1,0,0)); + int target = (blockID == 104) ? 86 : 103; + if (blockN.id == target) + node.bimgoffset += 1; + else if (blockS.id == target) + node.bimgoffset += 2; + else if (blockW.id == target) + node.bimgoffset += 3; + else if (blockE.id == target) + node.bimgoffset += 4; + } + else if (blockID == 64 || blockID == 71) // wooden door, iron door + { + Block blockU = getNeighborUD(chunkdata, bi + BlockIdx(0,0,1)); + Block blockD = getNeighborUD(chunkdata, bi + BlockIdx(0,0,-1)); + bool isTop = blockD.id == blockID; + uint8_t blockDataTop = isTop ? blockData : blockU.data; + uint8_t blockDataBottom = isTop ? blockD.data : blockData; + int dir = blockDataBottom % 4; + if (blockDataBottom & 0x4) + dir = (dir + ((blockDataTop & 0x1) ? 3 : 1)) % 4; + node.bimgoffset += isTop ? (dir + 4) : dir; + } + + //!!!!!!!! for now, only fully opaque blocks can have drop-off shadows, but some others like snow could + // probably use them, too + if (rj.blockimages.isOpaque(node.bimgoffset)) + { + Block blockS = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(1,0,0)); + Block blockE = getNeighbor(chunkdata, rj, ci, bi + BlockIdx(0,-1,0)); + Block blockD = getNeighborUD(chunkdata, bi + BlockIdx(0,0,-1)); + + //!!!!!! neighboring blocks that aren't full height like snow and half-steps should probably produce + // the drop-off effect, too + //!!!!!!! not to mention fully-transparent block images + if (blockS.id == 0) // air + node.darkenSU = true; + if (blockE.id == 0) // air + node.darkenEU = true; + if (blockD.id == 0) // air + { + node.darkenND = true; + node.darkenWD = true; + } + } +} + +//!!!!!! speed these up--lots of conditionals at the moment +void darkenEUEdge(RGBAImage& img, int32_t xstart, int32_t ystart, int B) +{ + // EU edge starts at [2B-1,0] and goes one step DL, then one step L, etc., for a total of 2B-1 steps + int32_t x = xstart + 2*B-1, y = ystart; + bool which = true; + for (int i = 0; i < 2*B-1; i++) + { + if (x >= 0 && x < img.w && y >= 0 && y < img.h) + blend(img(x, y), 0x60000000); + x--; + if (which) + y++; + which = !which; + } +} +void darkenSUEdge(RGBAImage& img, int32_t xstart, int32_t ystart, int B) +{ + // SU edge starts at [2B,0] and goes one step DR, then one step R, etc., for a total of 2B-1 steps + int32_t x = xstart + 2*B, y = ystart; + bool which = true; + for (int i = 0; i < 2*B-1; i++) + { + if (x >= 0 && x < img.w && y >= 0 && y < img.h) + blend(img(x, y), 0x60000000); + x++; + if (which) + y++; + which = !which; + } +} +void darkenNDEdge(RGBAImage& img, int32_t xstart, int32_t ystart, int B) +{ + // ND edge starts at [2B-1,4B-1] and goes one step UL, then one step L, etc., for a total of 2B-1 steps + int32_t x = xstart + 2*B-1, y = ystart + 4*B-1; + bool which = true; + for (int i = 0; i < 2*B-1; i++) + { + if (x >= 0 && x < img.w && y >= 0 && y < img.h) + blend(img(x, y), 0x60000000); + x--; + if (which) + y--; + which = !which; + } +} +void darkenWDEdge(RGBAImage& img, int32_t xstart, int32_t ystart, int B) +{ + // WD edge starts at [2B,4B-1] and goes one step UR, then one step R, etc., for a total of 2B-1 steps + int32_t x = xstart + 2*B, y = ystart + 4*B-1; + bool which = true; + for (int i = 0; i < 2*B-1; i++) + { + if (x >= 0 && x < img.w && y >= 0 && y < img.h) + blend(img(x, y), 0x60000000); + x++; + if (which) + y--; + which = !which; + } +} + +void drawNode(SceneGraphNode& node, RGBAImage& img, const BlockImages& blockimages) +{ + alphablit(blockimages.img, blockimages.getRect(node.bimgoffset), img, node.xstart, node.ystart); + if (node.darkenEU) + darkenEUEdge(img, node.xstart, node.ystart, blockimages.rectsize / 4); + if (node.darkenSU) + darkenSUEdge(img, node.xstart, node.ystart, blockimages.rectsize / 4); + if (node.darkenND) + darkenNDEdge(img, node.xstart, node.ystart, blockimages.rectsize / 4); + if (node.darkenWD) + darkenWDEdge(img, node.xstart, node.ystart, blockimages.rectsize / 4); + node.drawn = true; +} + +void drawSubgraph(SceneGraph& sg, int rootnode, RGBAImage& img, const BlockImages& blockimages) +{ + if (sg.nodes[rootnode].drawn) + return; + vector& stack = sg.nodestack; + stack.clear(); + stack.push_back(rootnode); + while (!stack.empty()) + { + SceneGraphNode& node = sg.nodes[stack.back()]; + bool pushed = false; + for (int i = 0; i < 7; i++) + if (node.children[i] != -1 && !sg.nodes[node.children[i]].drawn) + { + stack.push_back(node.children[i]); + pushed = true; + break; + } + if (pushed) + continue; + drawNode(node, img, blockimages); + stack.pop_back(); + } +} + +//!!!!!!!!!!!!! many opportunities for optimization in here +bool renderTile(const TileIdx& ti, RenderJob& rj, RGBAImage& tile) +{ + // if this tile isn't required, abort + if (!rj.tiletable->isRequired(ti)) + return false; + + // if this tile doesn't fit in the Google map, skip it + string tilefile = rj.outputpath + "/" + ti.toFilePath(rj.mp); + if (tilefile.empty()) + { + cerr << "tile [" << ti.x << "," << ti.y << "] exceeds the possible map size! skipping..." << endl; + return false; + } + // if we've somehow already drawn this tile (which should not be possible!), skip it + if (rj.tiletable->isDrawn(ti)) + { + cerr << "attempted to draw tile [" << ti.x << "," << ti.y << "] more than once!" << endl; + return false; + } + + // mark this tile drawn + rj.tiletable->setDrawn(ti); + + // if we're in test mode, don't actually draw anything + if (rj.testmode) + return true; + + SceneGraph& sg = *rj.scenegraph; + sg.clear(); + tile.create(rj.mp.tileSize(), rj.mp.tileSize()); + const BlockImages& blockimages = rj.blockimages; + + // we'll be given block center pixels in absolute coords, but for blitting, we need the block bounding box + // in tile image coords; compute the translation that gives us that + // (subtract the tile bounding box corner, then subtract another [2B,2B] to convert from block center to box) + BBox tilebb = ti.getBBox(rj.mp); + int64_t xoff = -tilebb.topLeft.x - 2*rj.mp.B; + int64_t yoff = -tilebb.topLeft.y - 2*rj.mp.B; + + // step 1: build the scene graph + // ...we'll iterate through the pseudocolumn center pixels, starting in the top left of the image, moving down then + // right; this means that by the time we reach a pseudocolumn, its N, E, and SE neighbors have already been done, + // so we can add any necessary edges to or from those neighbors + for (TileBlockIterator tbit(ti, rj.mp); !tbit.end; tbit.advance()) + { + // we'll start at the top of the pseudocolumn and go down, adding any non-air blocks to the graph, stopping + // at the first totally opaque block + sg.pcols.push_back(-1); + PosChunkIdx lastci(-1,-1); + ChunkData *chunkdata = NULL; + int prevnode = -1; + for (PseudocolumnIterator pcit(tbit.current, rj.mp); !pcit.end; pcit.advance()) + { + // look up chunk data (we might have it already) + PosChunkIdx ci = pcit.current.getChunkIdx(); + if (ci != lastci) + chunkdata = rj.chunkcache->getData(ci); + + // get block type + uint16_t blockID = chunkdata->id(pcit.current); + + // if this is air, move on (we *always* consider air to be transparent; it has no block image) + if (blockID == 0) + continue; + + // get the block data + uint8_t blockData = chunkdata->data(pcit.current); + int initialoffset = blockimages.getOffset(blockID, blockData); // we might use a different one after checkSpecial + + // create a node for this block + SceneGraphNode node(tbit.current.x + xoff, tbit.current.y + yoff, pcit.current, initialoffset); + + // check out neighboring blocks to see if we need to do anything special: set the darken-edge flags, + // or change the offset to a special one (one not corresponding to a plain blockID/blockData combo) + checkSpecial(node, blockID, blockData, ci, chunkdata, rj); + + // if this is not air, but is nonetheless transparent, move on + if (blockimages.isTransparent(node.bimgoffset)) + continue; + + // commit the node + int thisnode = sg.nodes.size(); + sg.nodes.push_back(node); + + // link our parent (the node above us in our own pseudocolumn) to us + if (prevnode != -1) + sg.nodes[prevnode].children[0] = thisnode; + // ...if we have no parent, then we're the top of this pcol + else + sg.pcols.back() = thisnode; + prevnode = thisnode; + + // if this block is opaque, we're done with this pcol + if (blockimages.isOpaque(node.bimgoffset)) + break; + } + + // check dependencies with our N, E, and SE neighbors + if (tbit.nextN != -1) + buildDependencies(sg, tbit.nextN, tbit.pos, 4); + if (tbit.nextE != -1) + buildDependencies(sg, tbit.nextE, tbit.pos, 5); + if (tbit.nextSE != -1) + buildDependencies(sg, tbit.nextSE, tbit.pos, 6); + } + + // if we didn't find anything to draw--i.e. our final image will be fully transparent--then there's + // no sense saving it to disk + if (sg.nodes.empty()) + return false; + + // step 2: traverse the graph and draw the image + for (int i = 0; i < (int)sg.nodes.size(); i++) + drawSubgraph(sg, i, tile, blockimages); + + // save the image to disk + if (!tile.writeImage(tilefile)) + cerr << "failed to write " << tilefile << endl; + return true; +} + + + +bool renderZoomTile(const ZoomTileIdx& zti, RenderJob& rj, RGBAImage& tile) +{ + // if this is a base tile, render it + if (zti.zoom == rj.mp.baseZoom) + return renderTile(zti.toTileIdx(rj.mp), rj, tile); + + // see whether this entire tile can be rejected early + if (rj.tiletable->reject(zti, rj.mp)) + return false; + + // render the four subtiles (if they're needed) + TileCache::ZoomLevel& zlevel = rj.tilecache->levels[rj.mp.baseZoom - zti.zoom - 1]; + ZoomTileIdx topleft = zti.toZoom(zti.zoom + 1); + zlevel.used[0] = renderZoomTile(topleft, rj, zlevel.tiles[0]); + zlevel.used[1] = renderZoomTile(topleft.add(0,1), rj, zlevel.tiles[1]); + zlevel.used[2] = renderZoomTile(topleft.add(1,0), rj, zlevel.tiles[2]); + zlevel.used[3] = renderZoomTile(topleft.add(1,1), rj, zlevel.tiles[3]); + + // if none of the subtiles are used, we have nothing to do + int usedcount = 0; + for (int i = 0; i < 4; i++) + if (zlevel.used[i]) + usedcount++; + if (usedcount == 0) + return false; + + // if we're in test mode, pretend we've successfully drawn + if (rj.testmode) + return true; + + // if some of the subtiles are unused and this is an incremental update, we need to + // load the existing version of this tile (if there is one) to get the unchanged portions + string tilefile = rj.outputpath + "/" + zti.toFilePath(); + if (usedcount < 4 && !rj.fullrender) + { + // if it doesn't read, no big deal (it may not exist anyway) + if (!tile.readPNG(tilefile + ".png") || tile.w != rj.mp.tileSize() || tile.h != rj.mp.tileSize()) + tile.create(rj.mp.tileSize(), rj.mp.tileSize()); + } + else + tile.create(rj.mp.tileSize(), rj.mp.tileSize()); + + // combine the four subtile images into this tile's image + int halfsize = rj.mp.tileSize() / 2; + if (zlevel.used[0]) + reduceHalf(tile, ImageRect(0, 0, halfsize, halfsize), zlevel.tiles[0]); + if (zlevel.used[1]) + reduceHalf(tile, ImageRect(0, halfsize, halfsize, halfsize), zlevel.tiles[1]); + if (zlevel.used[2]) + reduceHalf(tile, ImageRect(halfsize, 0, halfsize, halfsize), zlevel.tiles[2]); + if (zlevel.used[3]) + reduceHalf(tile, ImageRect(halfsize, halfsize, halfsize, halfsize), zlevel.tiles[3]); + + // save to disk + if (!tile.writeImage(tilefile)) + cerr << "failed to write " << tilefile << endl; + return true; +} + + + +bool renderZoomTile(const ZoomTileIdx& zti, RenderJob& rj, RGBAImage& tile, const ThreadOutputCache& tocache) +{ + // if this is at or below the ThreadOutputCache level, abort + if (zti.zoom >= tocache.zoom) + return false; + + // get the four subtiles: if we're one level above the ThreadOutputCache level, they're in the + // cache; otherwise, we recurse + // ...we use the ZoomLevel::used bits either way + TileCache::ZoomLevel& zlevel = rj.tilecache->levels[rj.mp.baseZoom - zti.zoom - 1]; + ZoomTileIdx topleft = zti.toZoom(zti.zoom + 1); + const RGBAImage *tile0, *tile1, *tile2, *tile3; + if (zti.zoom == tocache.zoom - 1) + { + int idx = tocache.getIndex(topleft); + zlevel.used[0] = tocache.used[idx]; + tile0 = &tocache.images[idx]; + idx = tocache.getIndex(topleft.add(0,1)); + zlevel.used[1] = tocache.used[idx]; + tile1 = &tocache.images[idx]; + idx = tocache.getIndex(topleft.add(1,0)); + zlevel.used[2] = tocache.used[idx]; + tile2 = &tocache.images[idx]; + idx = tocache.getIndex(topleft.add(1,1)); + zlevel.used[3] = tocache.used[idx]; + tile3 = &tocache.images[idx]; + } + else + { + zlevel.used[0] = renderZoomTile(topleft, rj, zlevel.tiles[0], tocache); + tile0 = &zlevel.tiles[0]; + zlevel.used[1] = renderZoomTile(topleft.add(0,1), rj, zlevel.tiles[1], tocache); + tile1 = &zlevel.tiles[1]; + zlevel.used[2] = renderZoomTile(topleft.add(1,0), rj, zlevel.tiles[2], tocache); + tile2 = &zlevel.tiles[2]; + zlevel.used[3] = renderZoomTile(topleft.add(1,1), rj, zlevel.tiles[3], tocache); + tile3 = &zlevel.tiles[3]; + } + + // if none of the subtiles are used, we have nothing to do + int usedcount = 0; + for (int i = 0; i < 4; i++) + if (zlevel.used[i]) + usedcount++; + if (usedcount == 0) + return false; + + // if we're in test mode, pretend we've successfully drawn + if (rj.testmode) + return true; + + // if some of the subtiles are unused and this is an incremental update, we need to + // load the existing version of this tile (if there is one) to get the unchanged portions + string tilefile = rj.outputpath + "/" + zti.toFilePath(); + if (usedcount < 4 && !rj.fullrender) + { + // if it doesn't read, no big deal (it may not exist anyway) + if (!tile.readPNG(tilefile + ".png") || tile.w != rj.mp.tileSize() || tile.h != rj.mp.tileSize()) + tile.create(rj.mp.tileSize(), rj.mp.tileSize()); + } + else + tile.create(rj.mp.tileSize(), rj.mp.tileSize()); + + // combine the four subtile images into this tile's image + int halfsize = rj.mp.tileSize() / 2; + if (zlevel.used[0]) + reduceHalf(tile, ImageRect(0, 0, halfsize, halfsize), *tile0); + if (zlevel.used[1]) + reduceHalf(tile, ImageRect(0, halfsize, halfsize, halfsize), *tile1); + if (zlevel.used[2]) + reduceHalf(tile, ImageRect(halfsize, 0, halfsize, halfsize), *tile2); + if (zlevel.used[3]) + reduceHalf(tile, ImageRect(halfsize, halfsize, halfsize, halfsize), *tile3); + + // save to disk + if (!tile.writeImage(tilefile)) + cerr << "failed to write " << tilefile << endl; + return true; +} + + + + + +void testTileIterator() +{ + MapParams mp(0,0,0); + for (mp.B = 2; mp.B <= 6; mp.B++) + for (mp.T = 1; mp.T <= 4; mp.T++) + { + cout << "B = " << mp.B << " T = " << mp.T << endl; + for (int64_t tx = -5; tx <= 5; tx++) + for (int64_t ty = -5; ty <= 5; ty++) + { + // get computed BBox + TileIdx ti(tx,ty); + BBox bbox = ti.getBBox(mp); + + // use TileBlockIterator to go through the block centers in the tile; verify that + // each is actually in the tile by getting the topmost block with that center and checking + // its bounding box against the tile's box + // ...and also make sure that the N, E, SE neighbors are where they're supposed to be + vector blocks; + for (TileBlockIterator it(ti, mp); !it.end; it.advance()) + { + BlockIdx bi = BlockIdx::topBlock(it.current, mp); + //cout << "[" << bi.x << "," << bi.z << "," << bi.y << "] pos " << it.pos << " nextE " << it.nextE << " nextN " << it.nextN << " nextSE " << it.nextSE << endl; + if (bi.getCenter(mp) != it.current) + { + cout << "topBlock mismatch: [" << it.current.x << "," << it.current.y << "] -> [" << bi.x << "," << bi.z << "," << bi.y << "] -> [" << bi.getCenter(mp).x << "," << bi.getCenter(mp).y << "]" << endl; + return; + } + if (!bi.getBBox(mp).overlaps(bbox)) + { + cout << "block centered at [" << it.current.x << "," << it.current.y << "] is not in tile!" << endl; + cout << "[" << bbox.topLeft.x << "," << bbox.topLeft.y << "] to [" << bbox.bottomRight.x << "," << bbox.bottomRight.y << "]" << endl; + return; + } + if ((unsigned) it.pos != blocks.size()) + { + cout << "block position seems to have advanced too fast!" << endl; + return; + } + blocks.push_back(bi); + if (it.nextE >= it.pos || it.nextN >= it.pos || it.nextSE >= it.pos) + { + cout << "neighbor position is *after* us!" << endl; + return; + } + if (it.nextE != -1 && blocks[it.nextE].z != bi.z - 1) + { + cout << "E neighbor pos is wrong" << endl; + return; + } + if (it.nextN != -1 && blocks[it.nextN].x != bi.x - 1) + { + cout << "N neighbor pos is wrong" << endl; + return; + } + if (it.nextSE != -1 && (blocks[it.nextSE].z != bi.z - 1 || blocks[it.nextSE].x != bi.x + 1)) + { + cout << "SE neighbor pos is wrong" << endl; + return; + } + } + } + } +} + +void testPColIterator() +{ + MapParams mp(6,1,0); + for (int64_t tx = -5; tx <= 5; tx++) + for (int64_t ty = -5; ty <= 5; ty++) + { + TileIdx ti(tx,ty); + vector centers; + for (TileBlockIterator tbit(ti, mp); !tbit.end; tbit.advance()) + { + centers.push_back(tbit.current); + + // check this pseudocolumn against its N, E, SE neighbors; make sure the blocks + // chosen by the iterator actually have the proper relationships + auto_ptr nit((tbit.nextN != -1) ? new PseudocolumnIterator(centers[tbit.nextN], mp) : NULL); + auto_ptr eit((tbit.nextE != -1) ? new PseudocolumnIterator(centers[tbit.nextE], mp) : NULL); + auto_ptr seit((tbit.nextSE != -1) ? new PseudocolumnIterator(centers[tbit.nextSE], mp) : NULL); + for (PseudocolumnIterator pcit(tbit.current, mp); !pcit.end; pcit.advance()) + { + if (nit.get() != NULL) + { + if (nit->current != pcit.current + BlockIdx(-1,0,0)) + cout << "N pcol iterator block is not actually N neighbor!" << endl; + if (nit->current.getCenter(mp) != pcit.current.getCenter(mp) + Pixel(-2*mp.B, mp.B)) + cout << "N neighbor pixel is wrong!" << endl; + nit->advance(); + } + if (eit.get() != NULL) + { + if (eit->current != pcit.current + BlockIdx(0,-1,0)) + cout << "E pcol iterator block is not actually E neighbor!" << endl; + if (eit->current.getCenter(mp) != pcit.current.getCenter(mp) + Pixel(-2*mp.B, -mp.B)) + cout << "E neighbor pixel is wrong!" << endl; + eit->advance(); + } + if (seit.get() != NULL) + { + if (seit->current != pcit.current + BlockIdx(1,-1,0)) + cout << "SE pcol iterator block is not actually SE neighbor!" << endl; + if (seit->current.getCenter(mp) != pcit.current.getCenter(mp) + Pixel(0, -2*mp.B)) + cout << "SE neighbor pixel is wrong!" << endl; + seit->advance(); + } + } + } + } +} diff --git a/render.h b/render.h old mode 100755 new mode 100644 index 93e372c..032e3a3 --- a/render.h +++ b/render.h @@ -1,214 +1,214 @@ -// Copyright 2010, 2011 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#ifndef RENDER_H -#define RENDER_H - -#include -#include - -#include "map.h" -#include "tables.h" -#include "chunk.h" -#include "blockimages.h" -#include "rgba.h" - - - -struct RenderStats -{ - int64_t reqchunkcount, reqregioncount, reqtilecount; // number of required chunks/regions and base tiles - uint64_t heapusage; // estimated peak heap memory usage (if available) - ChunkCacheStats chunkcache; - RegionCacheStats regioncache; - - RenderStats() : reqchunkcount(0), reqregioncount(0), reqtilecount(0), heapusage(0) {} -}; - - -struct SceneGraph; -struct TileCache; -struct ThreadOutputCache; - -struct RenderJob : private nocopy -{ - bool fullrender; // whether we're doing the entire world, as opposed to an incremental update - bool regionformat; // whether the world is in region format (chunk format assumed if not) - MapParams mp; - std::string inputpath, outputpath; - BlockImages blockimages; - std::auto_ptr chunktable; - std::auto_ptr chunkcache; - std::auto_ptr regiontable; - std::auto_ptr regioncache; - std::auto_ptr tiletable; - std::auto_ptr tilecache; - std::auto_ptr scenegraph; // reuse this for each tile to avoid reallocation - RenderStats stats; - - // don't actually draw anything or read chunks; just iterate through the data structures - // ...scenegraph, chunkcache, and regioncache are not required if in test mode - bool testmode; -}; - -// render a base tile into an RGBAImage, and also write it to disk -// ...do nothing and return false if the tile is not required or is out of range -bool renderTile(const TileIdx& ti, RenderJob& rj, RGBAImage& tile); - -// recursively render all the required tiles that a zoom tile depends on, and then the tile itself; -// stores the result into the supplied RGBAImage, and also writes it to disk -// do nothing and return false if the tile is not required -bool renderZoomTile(const ZoomTileIdx& zti, RenderJob& rj, RGBAImage& tile); - -// for second phase of multithreaded operation: recursively render all the required tiles that a zoom tile -// depends on, but stop recursing at the ThreadOutputCache level rather than the base tile level -bool renderZoomTile(const ZoomTileIdx& zti, RenderJob& rj, RGBAImage& tile, const ThreadOutputCache& tocache); - - - -// as we render tiles recursively, we need to be able to hold 4 intermediate results at each zoom level; -// this holds the space for those images, so we don't reallocate all the time -struct TileCache -{ - struct ZoomLevel - { - bool used[4]; // which of the images actually have data - RGBAImage tiles[4]; // actual image data for the four tiles - }; - - std::vector levels; // indexed by baseZoom - zoom - - TileCache(const MapParams& mp) : levels(mp.baseZoom) - { - // reserve memory - for (int i = 0; i < mp.baseZoom; i++) - for (int j = 0; j < 4; j++) - levels[i].tiles[j].create(mp.tileSize(), mp.tileSize()); - } -}; - - -// when rendering with multiple threads, the individual threads only go up to a certain zoom level, then -// the main thread does the last few levels on its own; the worker threads store their results in this -struct ThreadOutputCache -{ - int zoom; // which zoom level the threads are working at - - std::vector images; // use getIndex() to get index into this from zoom tile - std::vector used; // which images actually have data - - int getIndex(const ZoomTileIdx& zti) const; // get index into images, or -1 if zoom is wrong - - ThreadOutputCache(int z) : zoom(z), images((1 << zoom) * (1 << zoom)), used((1 << zoom) * (1 << zoom), false) {} -}; - - - - -// the blocks in a tile can be partitioned by their center pixels into pseudocolumns--sets of blocks that cover -// exactly the same pixels (each block covers the block immediately SED of it, and so on) -// also, each block can partially occlude blocks in 6 neighboring pseudocolumns: E, SE, S, W, NW, N (that is, -// the pseudocolumns that contain the block's immediate neighbors to the E, SE, etc.) -// ...so we can build a DAG representing the blocks in the tile: each block has up to 7 pointers, each one going to -// the topmost occluded block in a pseudocolumn -// a block can be drawn when all its descendents have been drawn - -struct SceneGraphNode -{ - int32_t xstart, ystart; // top-left corner of block bounding box in tile image coords - int bimgoffset; // offset into blockimages - // whether to darken various edges to indicate drop-off - bool darkenEU, darkenSU, darkenND, darkenWD; - bool drawn; - BlockIdx bi; - // first child is same pseudocolumn, then N, E, SE, S, W, NW; values are indices into - // the SceneGraph's nodes vector, or -1 for "null" - int children[7]; - - SceneGraphNode(int32_t x, int32_t y, const BlockIdx& bidx, int offset) - : xstart(x), ystart(y), bimgoffset(offset), darkenEU(false), darkenSU(false), darkenND(false), darkenWD(false), - drawn(false), bi(bidx) {std::fill(children, children + 7, -1);} -}; - -struct SceneGraph -{ - // all nodes from all pseudocolumns go in here, in sequence (ordered by pseudocolumn, and within - // pseudocolumns by height) - std::vector nodes; - // offset into nodes vector of each pseudocolumn (-1 for pseudocolumns with no nodes) - std::vector pcols; - - void clear() {nodes.clear(); pcols.clear();} - - int getTopNode(int pcol) {return pcols[pcol];} - - // scratch space for use while traversing the DAG - std::vector nodestack; - - SceneGraph() {nodes.reserve(2048);} -}; - - - - -// iterate over the hexagonal block-center grid pixels whose blocks touch a tile -struct TileBlockIterator -{ - bool end; // true when there are no more points - - // these guys are valid when end == false: - Pixel current; // the current grid point - int pos; // the position of this point within this tile's sequence of points - int nextN, nextE, nextSE; // the sequence positions of the neighboring points; -1 if the neighbor isn't in the tile - - const MapParams& mparams; - TileIdx tile; - // the tile's bounding box, expanded by half a block's bounding box, so that any block centered on a point - // within this box will hit the tile - BBox expandedBBox; - int lastTop, lastBottom; // positions of the most recent column top, bottom we've encountered (or -1) - - // constructor initializes to the upper-left grid point - TileBlockIterator(const TileIdx& ti, const MapParams& mp); - - // movement goes down the columns, then rightward to the next column - void advance(); -}; - -// iterate through the blocks that project to the same place, from top to bottom -struct PseudocolumnIterator -{ - bool end; // true when we've run out of blocks - BlockIdx current; // when end == false, holds current block - - const MapParams& mparams; - - // constructor initializes to topmost block - PseudocolumnIterator(const Pixel& center, const MapParams& mp); - - // move to the next block (which is one step SED), or the end - void advance(); -}; - - - - -void testTileIterator(); -void testPColIterator(); - - +// Copyright 2010, 2011 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#ifndef RENDER_H +#define RENDER_H + +#include +#include + +#include "map.h" +#include "tables.h" +#include "chunk.h" +#include "blockimages.h" +#include "rgba.h" + + + +struct RenderStats +{ + int64_t reqchunkcount, reqregioncount, reqtilecount; // number of required chunks/regions and base tiles + uint64_t heapusage; // estimated peak heap memory usage (if available) + ChunkCacheStats chunkcache; + RegionCacheStats regioncache; + + RenderStats() : reqchunkcount(0), reqregioncount(0), reqtilecount(0), heapusage(0) {} +}; + + +struct SceneGraph; +struct TileCache; +struct ThreadOutputCache; + +struct RenderJob : private nocopy +{ + bool fullrender; // whether we're doing the entire world, as opposed to an incremental update + bool regionformat; // whether the world is in region format (chunk format assumed if not) + MapParams mp; + std::string inputpath, outputpath; + BlockImages blockimages; + std::auto_ptr chunktable; + std::auto_ptr chunkcache; + std::auto_ptr regiontable; + std::auto_ptr regioncache; + std::auto_ptr tiletable; + std::auto_ptr tilecache; + std::auto_ptr scenegraph; // reuse this for each tile to avoid reallocation + RenderStats stats; + + // don't actually draw anything or read chunks; just iterate through the data structures + // ...scenegraph, chunkcache, and regioncache are not required if in test mode + bool testmode; +}; + +// render a base tile into an RGBAImage, and also write it to disk +// ...do nothing and return false if the tile is not required or is out of range +bool renderTile(const TileIdx& ti, RenderJob& rj, RGBAImage& tile); + +// recursively render all the required tiles that a zoom tile depends on, and then the tile itself; +// stores the result into the supplied RGBAImage, and also writes it to disk +// do nothing and return false if the tile is not required +bool renderZoomTile(const ZoomTileIdx& zti, RenderJob& rj, RGBAImage& tile); + +// for second phase of multithreaded operation: recursively render all the required tiles that a zoom tile +// depends on, but stop recursing at the ThreadOutputCache level rather than the base tile level +bool renderZoomTile(const ZoomTileIdx& zti, RenderJob& rj, RGBAImage& tile, const ThreadOutputCache& tocache); + + + +// as we render tiles recursively, we need to be able to hold 4 intermediate results at each zoom level; +// this holds the space for those images, so we don't reallocate all the time +struct TileCache +{ + struct ZoomLevel + { + bool used[4]; // which of the images actually have data + RGBAImage tiles[4]; // actual image data for the four tiles + }; + + std::vector levels; // indexed by baseZoom - zoom + + TileCache(const MapParams& mp) : levels(mp.baseZoom) + { + // reserve memory + for (int i = 0; i < mp.baseZoom; i++) + for (int j = 0; j < 4; j++) + levels[i].tiles[j].create(mp.tileSize(), mp.tileSize()); + } +}; + + +// when rendering with multiple threads, the individual threads only go up to a certain zoom level, then +// the main thread does the last few levels on its own; the worker threads store their results in this +struct ThreadOutputCache +{ + int zoom; // which zoom level the threads are working at + + std::vector images; // use getIndex() to get index into this from zoom tile + std::vector used; // which images actually have data + + int getIndex(const ZoomTileIdx& zti) const; // get index into images, or -1 if zoom is wrong + + ThreadOutputCache(int z) : zoom(z), images((1 << zoom) * (1 << zoom)), used((1 << zoom) * (1 << zoom), false) {} +}; + + + + +// the blocks in a tile can be partitioned by their center pixels into pseudocolumns--sets of blocks that cover +// exactly the same pixels (each block covers the block immediately SED of it, and so on) +// also, each block can partially occlude blocks in 6 neighboring pseudocolumns: E, SE, S, W, NW, N (that is, +// the pseudocolumns that contain the block's immediate neighbors to the E, SE, etc.) +// ...so we can build a DAG representing the blocks in the tile: each block has up to 7 pointers, each one going to +// the topmost occluded block in a pseudocolumn +// a block can be drawn when all its descendents have been drawn + +struct SceneGraphNode +{ + int32_t xstart, ystart; // top-left corner of block bounding box in tile image coords + int bimgoffset; // offset into blockimages + // whether to darken various edges to indicate drop-off + bool darkenEU, darkenSU, darkenND, darkenWD; + bool drawn; + BlockIdx bi; + // first child is same pseudocolumn, then N, E, SE, S, W, NW; values are indices into + // the SceneGraph's nodes vector, or -1 for "null" + int children[7]; + + SceneGraphNode(int32_t x, int32_t y, const BlockIdx& bidx, int offset) + : xstart(x), ystart(y), bimgoffset(offset), darkenEU(false), darkenSU(false), darkenND(false), darkenWD(false), + drawn(false), bi(bidx) {std::fill(children, children + 7, -1);} +}; + +struct SceneGraph +{ + // all nodes from all pseudocolumns go in here, in sequence (ordered by pseudocolumn, and within + // pseudocolumns by height) + std::vector nodes; + // offset into nodes vector of each pseudocolumn (-1 for pseudocolumns with no nodes) + std::vector pcols; + + void clear() {nodes.clear(); pcols.clear();} + + int getTopNode(int pcol) {return pcols[pcol];} + + // scratch space for use while traversing the DAG + std::vector nodestack; + + SceneGraph() {nodes.reserve(2048);} +}; + + + + +// iterate over the hexagonal block-center grid pixels whose blocks touch a tile +struct TileBlockIterator +{ + bool end; // true when there are no more points + + // these guys are valid when end == false: + Pixel current; // the current grid point + int pos; // the position of this point within this tile's sequence of points + int nextN, nextE, nextSE; // the sequence positions of the neighboring points; -1 if the neighbor isn't in the tile + + const MapParams& mparams; + TileIdx tile; + // the tile's bounding box, expanded by half a block's bounding box, so that any block centered on a point + // within this box will hit the tile + BBox expandedBBox; + int lastTop, lastBottom; // positions of the most recent column top, bottom we've encountered (or -1) + + // constructor initializes to the upper-left grid point + TileBlockIterator(const TileIdx& ti, const MapParams& mp); + + // movement goes down the columns, then rightward to the next column + void advance(); +}; + +// iterate through the blocks that project to the same place, from top to bottom +struct PseudocolumnIterator +{ + bool end; // true when we've run out of blocks + BlockIdx current; // when end == false, holds current block + + const MapParams& mparams; + + // constructor initializes to topmost block + PseudocolumnIterator(const Pixel& center, const MapParams& mp); + + // move to the next block (which is one step SED), or the end + void advance(); +}; + + + + +void testTileIterator(); +void testPColIterator(); + + #endif // RENDER_H \ No newline at end of file diff --git a/rgba.cpp b/rgba.cpp old mode 100755 new mode 100644 index f03c078..4852a04 --- a/rgba.cpp +++ b/rgba.cpp @@ -1,341 +1,455 @@ -// Copyright 2010, 2011 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#include -#include - -#include "rgba.h" -#include "utils.h" - -using namespace std; - -# ifndef UINT64_C -# if __WORDSIZE == 64 -# define UINT64_C(c) c ## UL -# else -# define UINT64_C(c) c ## ULL -# endif -# endif - - -RGBAPixel makeRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) -{ - return (a << 24) | (b << 16) | (g << 8) | r; -} - -void setAlpha(RGBAPixel& p, int a) -{ - p &= 0xffffff; - p |= (a & 0xff) << 24; -} - -void setBlue(RGBAPixel& p, int b) -{ - p &= 0xff00ffff; - p |= (b & 0xff) << 16; -} - -void setGreen(RGBAPixel& p, int g) -{ - p &= 0xffff00ff; - p |= (g & 0xff) << 8; -} - -void setRed(RGBAPixel& p, int r) -{ - p &= 0xffffff00; - p |= r & 0xff; -} - - -void RGBAImage::create(int32_t ww, int32_t hh) -{ - w = ww; - h = hh; - data.clear(); - data.resize(w*h, 0); -} - - - -struct fcloser -{ - FILE *f; - fcloser(FILE *ff) : f(ff) {} - ~fcloser() {fclose(f);} -}; - -struct PNGReadCleaner -{ - png_structp png; - png_infop info; - png_infop endinfo; - PNGReadCleaner() : png(NULL), info(NULL), endinfo(NULL) {} - ~PNGReadCleaner() {png_destroy_read_struct(&png, &info, &endinfo);} -}; - -struct PNGWriteCleaner -{ - png_structp png; - png_infop info; - PNGWriteCleaner() : png(NULL), info(NULL) {} - ~PNGWriteCleaner() {png_destroy_write_struct(&png, &info);} -}; - - - -bool RGBAImage::readPNG(const string& filename) -{ - FILE *f = fopen(filename.c_str(), "rb"); - if (f == NULL) - return false; - fcloser fc(f); - - uint8_t header[8]; - fread(header, 1, 8, f); - if (0 != png_sig_cmp(header, 0, 8)) - return false; - - PNGReadCleaner cleaner; - - png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (png == NULL) - return false; - cleaner.png = png; - - png_infop info = png_create_info_struct(png); - if (info == NULL) - return false; - cleaner.info = info; - - if (setjmp(png_jmpbuf(png))) - return false; - - png_init_io(png, f); - png_set_sig_bytes(png, 8); - - png_read_info(png, info); - if (PNG_COLOR_TYPE_RGB_ALPHA != png_get_color_type(png, info) || 8 != png_get_bit_depth(png, info)) - return false; - w = png_get_image_width(png, info); - h = png_get_image_height(png, info); - data.resize(w*h); - - png_set_interlace_handling(png); - png_read_update_info(png, info); - - png_bytep *rowPointers = new png_bytep[h]; - arrayDeleter ad(rowPointers); - RGBAPixel *p = &data[0]; - for (int32_t i = 0; i < h; i++, p += w) - rowPointers[i] = (png_bytep)p; - - if (isBigEndian()) - { - png_set_bgr(png); - png_set_swap_alpha(png); - } - - png_read_image(png, rowPointers); - - png_read_end(png, NULL); - return true; -} - -bool RGBAImage::writePNG(const string& filename) -{ - FILE *f = fopen(filename.c_str(), "wb"); - if (f == NULL) - { - // if the directory didn't exist, create it and try again - if (errno == ENOENT) - { - makePath(filename.substr(0, filename.rfind('/'))); - f = fopen(filename.c_str(), "wb"); - } - if (f == NULL) - return false; - } - fcloser fc(f); - - PNGWriteCleaner cleaner; - - png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - if (png == NULL) - return false; - cleaner.png = png; - - png_infop info = png_create_info_struct(png); - if (info == NULL) - return false; - cleaner.info = info; - - if (setjmp(png_jmpbuf(png))) - return false; - - png_init_io(png, f); - - png_set_IHDR(png, info, w, h, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); - - png_bytep *rowPointers = new png_bytep[h]; - arrayDeleter ad(rowPointers); - RGBAPixel *p = &data[0]; - for (int32_t i = 0; i < h; i++, p += w) - rowPointers[i] = (png_bytep)p; - - png_set_rows(png, info, rowPointers); - - if (isBigEndian()) - png_write_png(png, info, PNG_TRANSFORM_BGR | PNG_TRANSFORM_SWAP_ALPHA, NULL); - else - png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL); - - return true; -} - - - - - -void fullblend(RGBAPixel& dest, const RGBAPixel& source) -{ - // get sa and sainv in the range 1-256; this way, the possible results of blending 8-bit color channels sc and dc - // (using sc*sa + dc*sainv) span the range 0x0000-0xffff, so we can just truncate and shift - int64_t sa = ALPHA(source) + 1; - int64_t sainv = 257 - sa; - // compute the new RGB channels - int64_t d = dest, s = source; - d = ((d << 16) & UINT64_C(0xff00000000)) | ((d << 8) & 0xff0000) | (d & 0xff); - s = ((s << 16) & UINT64_C(0xff00000000)) | ((s << 8) & 0xff0000) | (s & 0xff); - int64_t newrgb = s*sa + d*sainv; - // compute the new alpha channel - int64_t dainv = 256 - ALPHA(dest); - int64_t newa = sainv * dainv; // result is from 1-0x10000 - newa = (newa - 1) >> 8; // result is from 0-0xff - newa = 255 - newa; // final result; if either input was 255, so is this, so opacity is preserved - // combine everything and write it out - dest = (newa << 24) | ((newrgb >> 24) & 0xff0000) | ((newrgb >> 16) & 0xff00) | ((newrgb >> 8) & 0xff); -} - -// if destination pixel is already 100% opaque, no need to calculate its new alpha -void opaqueblend(RGBAPixel& dest, const RGBAPixel& source) -{ - // get sa and sainv in the range 1-256; this way, the possible results of blending 8-bit color channels sc and dc - // (using sc*sa + dc*sainv) span the range 0x0000-0xffff, so we can just truncate and shift - int64_t sa = ALPHA(source) + 1; - int64_t sainv = 257 - sa; - // compute the new RGB channels - int64_t d = dest, s = source; - d = ((d << 16) & UINT64_C(0xff00000000)) | ((d << 8) & 0xff0000) | (d & 0xff); - s = ((s << 16) & UINT64_C(0xff00000000)) | ((s << 8) & 0xff0000) | (s & 0xff); - int64_t newrgb = s*sa + d*sainv; - // destination alpha remains 100%; combine everything and write it out - dest = 0xff000000 | ((newrgb >> 24) & 0xff0000) | ((newrgb >> 16) & 0xff00) | ((newrgb >> 8) & 0xff); -} - -void blend(RGBAPixel& dest, const RGBAPixel& source) -{ - // if source is transparent, there's nothing to do - if (source <= 0xffffff) - return; - // if source is opaque, or if destination is transparent, just copy it over - else if (source >= 0xff000000 || dest <= 0xffffff) - dest = source; - // if source is translucent and dest is opaque, the color channels need to be blended, - // but the new pixel will be opaque - else if (dest >= 0xff000000) - opaqueblend(dest, source); - // both source and dest are translucent; we need the whole deal - else - fullblend(dest, source); -} - -void alphablit(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, int32_t dxstart, int32_t dystart) -{ - int32_t ybegin = max(0, max(-srect.y, -dystart)); - int32_t yend = min(srect.h, min(source.h-srect.y, dest.h-dystart)); - int32_t xbegin = max(0, max(-srect.x, -dxstart)); - int32_t xend = min(srect.w, min(source.w-srect.x, dest.w-dxstart)); - for (int32_t yoff = ybegin, sy = srect.y + ybegin, dy = dystart + ybegin; yoff < yend; yoff++, sy++, dy++) - for (int32_t xoff = xbegin, sx = srect.x + xbegin, dx = dxstart + xbegin; xoff < xend; xoff++, sx++, dx++) - blend(dest(dx,dy), source(sx,sy)); -} - -void reduceHalf(RGBAImage& dest, const ImageRect& drect, const RGBAImage& source) -{ - if (source.w != drect.w*2 || source.h != drect.h*2) - return; - for (int32_t dy = drect.y, sy = 0; sy < source.h; dy++, sy += 2) - for (int32_t dx = drect.x, sx = 0; sx < source.w; dx++, sx += 2) - { - RGBAPixel p1 = (source(sx, sy) >> 2) & 0x3f3f3f3f; - RGBAPixel p2 = (source(sx+1, sy) >> 2) & 0x3f3f3f3f; - RGBAPixel p3 = (source(sx, sy+1) >> 2) & 0x3f3f3f3f; - RGBAPixel p4 = (source(sx+1, sy+1) >> 2) & 0x3f3f3f3f; - dest(dx, dy) = p1 + p2 + p3 + p4; - } -} - - - - -//!!!!!!!!! replace this with something non-idiotic? (it does surprisingly well, though!) -void resize(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, const ImageRect& drect) -{ - for (int y = drect.y; y < drect.y + drect.h; y++) - { - int yoff = interpolate(y - drect.y, drect.h, srect.h); - for (int x = drect.x; x < drect.x + drect.w; x++) - { - int xoff = interpolate(x - drect.x, drect.w, srect.w); - dest(x, y) = source(srect.x + xoff, srect.y + yoff); - } - } -} - -void darken(RGBAPixel& dest, double r, double g, double b) -{ - uint8_t newr = (uint8_t)(r * (double)(RED(dest))); - uint8_t newg = (uint8_t)(g * (double)(GREEN(dest))); - uint8_t newb = (uint8_t)(b * (double)(BLUE(dest))); - dest = makeRGBA(newr, newg, newb, ALPHA(dest)); -} - -void darken(RGBAImage& img, const ImageRect& rect, double r, double g, double b) -{ - for (int y = rect.y; y < rect.y + rect.h; y++) - for (int x = rect.x; x < rect.x + rect.w; x++) - darken(img(x, y), r, g, b); -} - -void blit(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, int32_t dxstart, int32_t dystart) -{ - int32_t ybegin = max(0, max(-srect.y, -dystart)); - int32_t yend = min(srect.h, min(source.h-srect.y, dest.h-dystart)); - int32_t xbegin = max(0, max(-srect.x, -dxstart)); - int32_t xend = min(srect.w, min(source.w-srect.x, dest.w-dxstart)); - for (int32_t yoff = ybegin, sy = srect.y + ybegin, dy = dystart + ybegin; yoff < yend; yoff++, sy++, dy++) - for (int32_t xoff = xbegin, sx = srect.x + xbegin, dx = dxstart + xbegin; xoff < xend; xoff++, sx++, dx++) - dest(dx,dy) = source(sx,sy); -} - -void flipX(RGBAImage& img, const ImageRect& rect) -{ - for (int y = rect.y; y < rect.y + rect.h; y++) - for (int x1 = rect.x, x2 = rect.x + rect.w - 1; x1 < rect.x + rect.w/2; x1++, x2--) - swap(img(x1, y), img(x2, y)); -} +// Copyright 2010, 2011 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#include +#include +#include + +#include "rgba.h" +#include "utils.h" + +using namespace std; + +# ifndef UINT64_C +# if __WORDSIZE == 64 +# define UINT64_C(c) c ## UL +# else +# define UINT64_C(c) c ## ULL +# endif +# endif + + +RGBAPixel makeRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + return (a << 24) | (b << 16) | (g << 8) | r; +} + +void setAlpha(RGBAPixel& p, int a) +{ + p &= 0xffffff; + p |= (a & 0xff) << 24; +} + +void setBlue(RGBAPixel& p, int b) +{ + p &= 0xff00ffff; + p |= (b & 0xff) << 16; +} + +void setGreen(RGBAPixel& p, int g) +{ + p &= 0xffff00ff; + p |= (g & 0xff) << 8; +} + +void setRed(RGBAPixel& p, int r) +{ + p &= 0xffffff00; + p |= r & 0xff; +} + + +void RGBAImage::create(int32_t ww, int32_t hh) +{ + w = ww; + h = hh; + data.clear(); + data.resize(w*h, 0); +} + + + +struct fcloser +{ + FILE *f; + fcloser(FILE *ff) : f(ff) {} + ~fcloser() {fclose(f);} +}; + +struct PNGReadCleaner +{ + png_structp png; + png_infop info; + png_infop endinfo; + PNGReadCleaner() : png(NULL), info(NULL), endinfo(NULL) {} + ~PNGReadCleaner() {png_destroy_read_struct(&png, &info, &endinfo);} +}; + +struct PNGWriteCleaner +{ + png_structp png; + png_infop info; + PNGWriteCleaner() : png(NULL), info(NULL) {} + ~PNGWriteCleaner() {png_destroy_write_struct(&png, &info);} +}; + + + +bool RGBAImage::readPNG(const string& filename) +{ + FILE *f = fopen(filename.c_str(), "rb"); + if (f == NULL) + return false; + fcloser fc(f); + + uint8_t header[8]; + if (fread(header, 1, 8, f) < 8) + return false; + if (0 != png_sig_cmp(header, 0, 8)) + return false; + + PNGReadCleaner cleaner; + + png_structp png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png == NULL) + return false; + cleaner.png = png; + + png_infop info = png_create_info_struct(png); + if (info == NULL) + return false; + cleaner.info = info; + if (setjmp(png_jmpbuf(png))) + return false; + + png_init_io(png, f); + png_set_sig_bytes(png, 8); + + png_read_info(png, info); + if (PNG_COLOR_TYPE_RGB_ALPHA != png_get_color_type(png, info) || 8 != png_get_bit_depth(png, info)) + return false; + w = png_get_image_width(png, info); + h = png_get_image_height(png, info); + data.resize(w*h); + + png_set_interlace_handling(png); + png_read_update_info(png, info); + + png_bytep *rowPointers = new png_bytep[h]; + arrayDeleter ad(rowPointers); + RGBAPixel *p = &data[0]; + for (int32_t i = 0; i < h; i++, p += w) + rowPointers[i] = (png_bytep)p; + + if (isBigEndian()) + { + png_set_bgr(png); + png_set_swap_alpha(png); + } + + png_read_image(png, rowPointers); + + png_read_end(png, NULL); + return true; +} + +namespace ImageSettings +{ + + Format format = Format_PNG; + int jpegQuality = 75; + +} + +bool RGBAImage::writeImage(const string& filename) +{ + bool success = true; + if (ImageSettings::format != ImageSettings::Format_JPEG) + success = writePNG(filename + ".png") && success; + if (ImageSettings::format != ImageSettings::Format_PNG) + success = writeJPEG(filename + ".jpeg") && success; + return success; +} + +bool RGBAImage::writePNG(const string& filename) +{ + FILE *f = fopen(filename.c_str(), "wb"); + if (f == NULL) + { + // if the directory didn't exist, create it and try again + if (errno == ENOENT) + { + makePath(filename.substr(0, filename.rfind('/'))); + f = fopen(filename.c_str(), "wb"); + } + if (f == NULL) + return false; + } + fcloser fc(f); + + PNGWriteCleaner cleaner; + + png_structp png = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if (png == NULL) + return false; + cleaner.png = png; + + png_infop info = png_create_info_struct(png); + if (info == NULL) + return false; + cleaner.info = info; + + if (setjmp(png_jmpbuf(png))) + return false; + + png_init_io(png, f); + + png_set_IHDR(png, info, w, h, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); + + png_bytep *rowPointers = new png_bytep[h]; + arrayDeleter ad(rowPointers); + RGBAPixel *p = &data[0]; + for (int32_t i = 0; i < h; i++, p += w) + rowPointers[i] = (png_bytep)p; + + png_set_rows(png, info, rowPointers); + + if (isBigEndian()) + png_write_png(png, info, PNG_TRANSFORM_BGR | PNG_TRANSFORM_SWAP_ALPHA, NULL); + else + png_write_png(png, info, PNG_TRANSFORM_IDENTITY, NULL); + + return true; +} + +bool RGBAImage::writeJPEG(const string& filename) +{ + FILE *f = fopen(filename.c_str(), "wb"); + if (f == NULL) + { + // if the directory didn't exist, create it and try again + if (errno == ENOENT) + { + makePath(filename.substr(0, filename.rfind('/'))); + f = fopen(filename.c_str(), "wb"); + } + if (f == NULL) + return false; + } + fcloser fc(f); + + jpeg_compress_struct cinfo; + jpeg_error_mgr jerr; + + cinfo.err = jpeg_std_error(&jerr); + jpeg_create_compress(&cinfo); + jpeg_stdio_dest(&cinfo, f); + + cinfo.image_width = w; + cinfo.image_height = h; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, ImageSettings::jpegQuality, true); + jpeg_start_compress(&cinfo, true); + + JSAMPLE* scanlineData = new JSAMPLE[3 * w]; + arrayDeleter ad(scanlineData); + while (cinfo.next_scanline < cinfo.image_height) + { + RGBAPixel* p = &data[w * cinfo.next_scanline]; + for (uint32_t x = 0; x < (uint32_t)w; ++x) + { + if (ALPHA(p[x]) > 0) + { + scanlineData[x * 3] = RED(p[x]); + scanlineData[x * 3 + 1] = GREEN(p[x]); + scanlineData[x * 3 + 2] = BLUE(p[x]); + } + else + { + scanlineData[x * 3] = 255; + scanlineData[x * 3 + 1] = 255; + scanlineData[x * 3 + 2] = 255; + } + } + jpeg_write_scanlines(&cinfo, &scanlineData, 1); + } + jpeg_finish_compress(&cinfo); + return true; +} + + + + +void fullblend(RGBAPixel& dest, const RGBAPixel& source) +{ + // get sa and sainv in the range 1-256; this way, the possible results of blending 8-bit color channels sc and dc + // (using sc*sa + dc*sainv) span the range 0x0000-0xffff, so we can just truncate and shift + int64_t sa = ALPHA(source) + 1; + int64_t sainv = 257 - sa; + // compute the new RGB channels + int64_t d = dest, s = source; + d = ((d << 16) & UINT64_C(0xff00000000)) | ((d << 8) & 0xff0000) | (d & 0xff); + s = ((s << 16) & UINT64_C(0xff00000000)) | ((s << 8) & 0xff0000) | (s & 0xff); + int64_t newrgb = s*sa + d*sainv; + // compute the new alpha channel + int64_t dainv = 256 - ALPHA(dest); + int64_t newa = sainv * dainv; // result is from 1-0x10000 + newa = (newa - 1) >> 8; // result is from 0-0xff + newa = 255 - newa; // final result; if either input was 255, so is this, so opacity is preserved + // combine everything and write it out + dest = (newa << 24) | ((newrgb >> 24) & 0xff0000) | ((newrgb >> 16) & 0xff00) | ((newrgb >> 8) & 0xff); +} + +// if destination pixel is already 100% opaque, no need to calculate its new alpha +void opaqueblend(RGBAPixel& dest, const RGBAPixel& source) +{ + // get sa and sainv in the range 1-256; this way, the possible results of blending 8-bit color channels sc and dc + // (using sc*sa + dc*sainv) span the range 0x0000-0xffff, so we can just truncate and shift + int64_t sa = ALPHA(source) + 1; + int64_t sainv = 257 - sa; + // compute the new RGB channels + int64_t d = dest, s = source; + d = ((d << 16) & UINT64_C(0xff00000000)) | ((d << 8) & 0xff0000) | (d & 0xff); + s = ((s << 16) & UINT64_C(0xff00000000)) | ((s << 8) & 0xff0000) | (s & 0xff); + int64_t newrgb = s*sa + d*sainv; + // destination alpha remains 100%; combine everything and write it out + dest = 0xff000000 | ((newrgb >> 24) & 0xff0000) | ((newrgb >> 16) & 0xff00) | ((newrgb >> 8) & 0xff); +} + +void blend(RGBAPixel& dest, const RGBAPixel& source) +{ + // if source is transparent, there's nothing to do + if (source <= 0xffffff) + return; + // if source is opaque, or if destination is transparent, just copy it over + else if (source >= 0xff000000 || dest <= 0xffffff) + dest = source; + // if source is translucent and dest is opaque, the color channels need to be blended, + // but the new pixel will be opaque + else if (dest >= 0xff000000) + opaqueblend(dest, source); + // both source and dest are translucent; we need the whole deal + else + fullblend(dest, source); +} + +void alphablit(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, int32_t dxstart, int32_t dystart) +{ + int32_t ybegin = max(0, max(-srect.y, -dystart)); + int32_t yend = min(srect.h, min(source.h-srect.y, dest.h-dystart)); + int32_t xbegin = max(0, max(-srect.x, -dxstart)); + int32_t xend = min(srect.w, min(source.w-srect.x, dest.w-dxstart)); + for (int32_t yoff = ybegin, sy = srect.y + ybegin, dy = dystart + ybegin; yoff < yend; yoff++, sy++, dy++) + for (int32_t xoff = xbegin, sx = srect.x + xbegin, dx = dxstart + xbegin; xoff < xend; xoff++, sx++, dx++) + blend(dest(dx,dy), source(sx,sy)); +} + +void reduceHalf(RGBAImage& dest, const ImageRect& drect, const RGBAImage& source) +{ + if (source.w != drect.w*2 || source.h != drect.h*2) + return; + for (int32_t dy = drect.y, sy = 0; sy < source.h; dy++, sy += 2) + for (int32_t dx = drect.x, sx = 0; sx < source.w; dx++, sx += 2) + { + RGBAPixel p1 = (source(sx, sy) >> 2) & 0x3f3f3f3f; + RGBAPixel p2 = (source(sx+1, sy) >> 2) & 0x3f3f3f3f; + RGBAPixel p3 = (source(sx, sy+1) >> 2) & 0x3f3f3f3f; + RGBAPixel p4 = (source(sx+1, sy+1) >> 2) & 0x3f3f3f3f; + dest(dx, dy) = p1 + p2 + p3 + p4; + } +} + + + + +//!!!!!!!!! replace this with something non-idiotic? (it does surprisingly well, though!) +void resize(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, const ImageRect& drect) +{ + for (int y = drect.y; y < drect.y + drect.h; y++) + { + int yoff = interpolate(y - drect.y, drect.h, srect.h); + for (int x = drect.x; x < drect.x + drect.w; x++) + { + int xoff = interpolate(x - drect.x, drect.w, srect.w); + dest(x, y) = source(srect.x + xoff, srect.y + yoff); + } + } +} + +void darken(RGBAPixel& dest, double r, double g, double b) +{ + uint8_t newr = (uint8_t)(r * (double)(RED(dest))); + uint8_t newg = (uint8_t)(g * (double)(GREEN(dest))); + uint8_t newb = (uint8_t)(b * (double)(BLUE(dest))); + dest = makeRGBA(newr, newg, newb, ALPHA(dest)); +} + +void darken(RGBAImage& img, const ImageRect& rect, double r, double g, double b) +{ + for (int y = rect.y; y < rect.y + rect.h; y++) + for (int x = rect.x; x < rect.x + rect.w; x++) + darken(img(x, y), r, g, b); +} + +void blit(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, int32_t dxstart, int32_t dystart) +{ + int32_t ybegin = max(0, max(-srect.y, -dystart)); + int32_t yend = min(srect.h, min(source.h-srect.y, dest.h-dystart)); + int32_t xbegin = max(0, max(-srect.x, -dxstart)); + int32_t xend = min(srect.w, min(source.w-srect.x, dest.w-dxstart)); + for (int32_t yoff = ybegin, sy = srect.y + ybegin, dy = dystart + ybegin; yoff < yend; yoff++, sy++, dy++) + for (int32_t xoff = xbegin, sx = srect.x + xbegin, dx = dxstart + xbegin; xoff < xend; xoff++, sx++, dx++) + dest(dx,dy) = source(sx,sy); +} + +void imgoffset(RGBAImage& dest, int32_t dxoffset, int32_t dyoffset) +{ + dxoffset = dxoffset % dest.w; + dyoffset = dyoffset % dest.h; + int32_t xbegin = max(0, -dxoffset); + int32_t xend = min(dest.w, dest.w -dxoffset); + int32_t ybegin = max(0, -dyoffset); + int32_t yend = min(dest.h, dest.h -dyoffset); + vector newdata; + newdata.resize(dest.w*dest.h); + for(int sy = ybegin, dy = ybegin + dyoffset; sy < yend; sy++, dy++) + for (int sx = xbegin, dx = xbegin + dxoffset; sx < xend; sx++, dx++) + newdata[dy*dest.w + dx] = dest.data[sy*dest.w+sx]; + dest.data.swap(newdata); +} + +void imgtileoffset(RGBAImage& dest, int32_t dxoffset, int32_t dyoffset) +{ + dxoffset = dxoffset % dest.w; + dyoffset = dyoffset % dest.h; + vector newdata; + newdata.resize(dest.w*dest.h); + for(int y = 0; y < dest.h; y++) + for (int x = 0; x < dest.w; x++) + newdata[((y + dyoffset)%dest.h)*dest.w+(x + dxoffset)%dest.w] = dest.data[y*dest.w+x]; + dest.data.swap(newdata); +} + +void imgcrop(RGBAImage& dest, const ImageRect& drect) +{ + int32_t dw = drect.x + drect.w; + int32_t dh = drect.y + drect.h; + for(int y = 0; y < dest.h; y++) + for (int x = 0; x < dest.w; x++) + if(x < drect.x || x >= dw || y < drect.y || y >= dh) + dest(x,y) = dest(x,y) & 0x00ffffff; +} + +void flipX(RGBAImage& img, const ImageRect& rect) +{ + for (int y = rect.y; y < rect.y + rect.h; y++) + for (int x1 = rect.x, x2 = rect.x + rect.w - 1; x1 < rect.x + rect.w/2; x1++, x2--) + swap(img(x1, y), img(x2, y)); +} diff --git a/rgba.h b/rgba.h old mode 100755 new mode 100644 index 533ff11..6e46481 --- a/rgba.h +++ b/rgba.h @@ -1,94 +1,117 @@ -// Copyright 2010, 2011 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#ifndef RGBA_H -#define RGBA_H - -#include -#include -#include - - -typedef uint32_t RGBAPixel; -#define ALPHA(x) ((x & 0xff000000) >> 24) -#define BLUE(x) ((x & 0xff0000) >> 16) -#define GREEN(x) ((x & 0xff00) >> 8) -#define RED(x) (x & 0xff) - -RGBAPixel makeRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); - -void setAlpha(RGBAPixel& p, int a); -void setBlue(RGBAPixel& p, int b); -void setGreen(RGBAPixel& p, int g); -void setRed(RGBAPixel& p, int r); - -struct RGBAImage -{ - std::vector data; - int32_t w, h; - - // get pixel - RGBAPixel& operator()(int32_t x, int32_t y) {return data[y*w+x];} - const RGBAPixel& operator()(int32_t x, int32_t y) const {return data[y*w+x];} - - // resize data and initialize to 0 (clear out any existing data) - void create(int32_t ww, int32_t hh); - - bool readPNG(const std::string& filename); - bool writePNG(const std::string& filename); -}; - -struct ImageRect -{ - int32_t x, y, w, h; - - ImageRect(int32_t xx, int32_t yy, int32_t ww, int32_t hh) : x(xx), y(yy), w(ww), h(hh) {} -}; - -//------- these are used by the inner rendering loops and must be fast - -// alpha-blend source pixel onto destination pixel -// ...note that the alpha channel of the result is not computed the same way as the RGB channels: -// instead of interpolating between ALPHA(source) and ALPHA(dest), it is the inverse product of the -// inverses of ALPHA(source) and ALPHA(dest), so that when you draw a translucent pixel on top of an -// opaque one, the result stays opaque -void blend(RGBAPixel& dest, const RGBAPixel& source); - -// alpha-blend source rect onto destination rect of same size -void alphablit(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, int32_t dxstart, int32_t dystart); - -// reduce source image into destination rect half its size -// (does nothing if the ImageRect isn't exactly half the size of the source image) -void reduceHalf(RGBAImage& dest, const ImageRect& drect, const RGBAImage& source); - - -//--------- these are used only to generate block images from terrain.png and may be crappy - -// copy source rect into destination rect of possibly different size -void resize(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, const ImageRect& drect); - -// darken a pixel by multiplying its RGB components by some number from 0 to 1 -void darken(RGBAPixel& dest, double r, double g, double b); -void darken(RGBAImage& img, const ImageRect& rect, double r, double g, double b); - -// copy source rect into destination rect of same size -void blit(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, int32_t dxstart, int32_t dystart); - -// flip the target rect in the X direction -void flipX(RGBAImage& img, const ImageRect& rect); - -#endif // RGBA_H \ No newline at end of file +// Copyright 2010, 2011 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#ifndef RGBA_H +#define RGBA_H + +#include +#include +#include + + +typedef uint32_t RGBAPixel; +#define ALPHA(x) ((x & 0xff000000) >> 24) +#define BLUE(x) ((x & 0xff0000) >> 16) +#define GREEN(x) ((x & 0xff00) >> 8) +#define RED(x) (x & 0xff) + +RGBAPixel makeRGBA(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +void setAlpha(RGBAPixel& p, int a); +void setBlue(RGBAPixel& p, int b); +void setGreen(RGBAPixel& p, int g); +void setRed(RGBAPixel& p, int r); + +struct RGBAImage +{ + std::vector data; + int32_t w, h; + + // get pixel + RGBAPixel& operator()(int32_t x, int32_t y) {return data[y*w+x];} + const RGBAPixel& operator()(int32_t x, int32_t y) const {return data[y*w+x];} + + // resize data and initialize to 0 (clear out any existing data) + void create(int32_t ww, int32_t hh); + + bool readPNG(const std::string& filename); + bool writePNG(const std::string& filename); + bool writeJPEG(const std::string& filename); + + bool writeImage(const std::string& filename); // writes png and/or jpeg +}; + +namespace ImageSettings +{ + enum Format + { + Format_PNG, + Format_JPEG, + Format_Both + }; + + extern Format format; + extern int jpegQuality; +} + +struct ImageRect +{ + int32_t x, y, w, h; + + ImageRect(int32_t xx, int32_t yy, int32_t ww, int32_t hh) : x(xx), y(yy), w(ww), h(hh) {} +}; + +//------- these are used by the inner rendering loops and must be fast + +// alpha-blend source pixel onto destination pixel +// ...note that the alpha channel of the result is not computed the same way as the RGB channels: +// instead of interpolating between ALPHA(source) and ALPHA(dest), it is the inverse product of the +// inverses of ALPHA(source) and ALPHA(dest), so that when you draw a translucent pixel on top of an +// opaque one, the result stays opaque +void blend(RGBAPixel& dest, const RGBAPixel& source); + +// alpha-blend source rect onto destination rect of same size +void alphablit(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, int32_t dxstart, int32_t dystart); + +// reduce source image into destination rect half its size +// (does nothing if the ImageRect isn't exactly half the size of the source image) +void reduceHalf(RGBAImage& dest, const ImageRect& drect, const RGBAImage& source); + + +//--------- these are used only to generate block images from terrain.png and may be crappy + +// copy source rect into destination rect of possibly different size +void resize(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, const ImageRect& drect); + +// darken a pixel by multiplying its RGB components by some number from 0 to 1 +void darken(RGBAPixel& dest, double r, double g, double b); +void darken(RGBAImage& img, const ImageRect& rect, double r, double g, double b); + +// copy source rect into destination rect of same size +void blit(const RGBAImage& source, const ImageRect& srect, RGBAImage& dest, int32_t dxstart, int32_t dystart); + +// offset image tile +void imgoffset(RGBAImage& dest, int32_t dxoffset, int32_t dyoffset); +// offset image tile (repeat tile) +void imgtileoffset(RGBAImage& dest, int32_t dxoffset, int32_t dyoffset); +// crop image and fill cropped space with empty pixels +void imgcrop(RGBAImage& dest, const ImageRect& drect); + +// flip the target rect in the X direction +void flipX(RGBAImage& img, const ImageRect& rect); + +#endif // RGBA_H diff --git a/style.css b/style.css old mode 100755 new mode 100644 index 60fc998..6b47816 --- a/style.css +++ b/style.css @@ -1,18 +1,18 @@ -html { height: 100% } -body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; } -#mcmap { height: 100% } - -.infoWindow { - height: 100px; -} - -.infoWindow>img { - width:80px; - float: left; - -} - -.infoWindow>p { - text-align: center; - font-family: monospace; -} +html { height: 100% } +body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; } +#mcmap { height: 100% } + +.infoWindow { + height: 100px; +} + +.infoWindow>img { + width:80px; + float: left; + +} + +.infoWindow>p { + text-align: center; + font-family: monospace; +} diff --git a/tables.cpp b/tables.cpp old mode 100755 new mode 100644 index 5f283f2..c18d537 --- a/tables.cpp +++ b/tables.cpp @@ -1,431 +1,431 @@ -// Copyright 2010, 2011 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#include - -#include "tables.h" - -using namespace std; - - - -void ChunkGroup::setRequired(const PosChunkIdx& ci) -{ - int csi = chunkSetIdx(ci); - if (chunksets[csi] == NULL) - chunksets[csi] = new ChunkSet; - chunksets[csi]->setRequired(ci); -} - -void ChunkGroup::setDiskState(const PosChunkIdx& ci, int state) -{ - int csi = chunkSetIdx(ci); - if (chunksets[csi] == NULL) - chunksets[csi] = new ChunkSet; - chunksets[csi]->setDiskState(ci, state); -} - - - - -PosChunkIdx ChunkTable::toPosChunkIdx(int cgi, int csi, int bi) -{ - PosChunkIdx ci(0,0); - ci.x += (cgi % CTLEVEL3SIZE) * CTLEVEL1SIZE * CTLEVEL2SIZE; - ci.z += (cgi / CTLEVEL3SIZE) * CTLEVEL1SIZE * CTLEVEL2SIZE; - ci.x += (csi % CTLEVEL2SIZE) * CTLEVEL1SIZE; - ci.z += (csi / CTLEVEL2SIZE) * CTLEVEL1SIZE; - ci.x += ((bi / CTDATASIZE) % CTLEVEL1SIZE); - ci.z += ((bi / CTDATASIZE) / CTLEVEL1SIZE); - return ci; -} - -void ChunkTable::setRequired(const PosChunkIdx& ci) -{ - int cgi = chunkGroupIdx(ci); - if (chunkgroups[cgi] == NULL) - chunkgroups[cgi] = new ChunkGroup; - chunkgroups[cgi]->setRequired(ci); -} - -void ChunkTable::setDiskState(const PosChunkIdx& ci, int state) -{ - int cgi = chunkGroupIdx(ci); - if (chunkgroups[cgi] == NULL) - chunkgroups[cgi] = new ChunkGroup; - chunkgroups[cgi]->setDiskState(ci, state); -} - -void ChunkTable::copyFrom(const ChunkTable& ctable) -{ - for (int cgi = 0; cgi < CTLEVEL3SIZE*CTLEVEL3SIZE; cgi++) - { - if (ctable.chunkgroups[cgi] != NULL) - { - chunkgroups[cgi] = new ChunkGroup; - for (int csi = 0; csi < CTLEVEL2SIZE*CTLEVEL2SIZE; csi++) - { - if (ctable.chunkgroups[cgi]->chunksets[csi] != NULL) - { - chunkgroups[cgi]->chunksets[csi] = new ChunkSet(*(ctable.chunkgroups[cgi]->chunksets[csi])); - } - } - } - } -} - - - -RequiredChunkIterator::RequiredChunkIterator(ChunkTable& ctable) : chunktable(ctable), current(-1,-1) -{ - // if the very first chunk is required, use it - cgi = csi = bi = 0; - current = ChunkTable::toPosChunkIdx(cgi, csi, bi); - if (chunktable.isRequired(current)) - { - end = false; - return; - } - // ...otherwise, advance to the next one after it - advance(); -} - -void RequiredChunkIterator::advance() -{ - bi += CTDATASIZE; - for (; cgi < CTLEVEL3SIZE*CTLEVEL3SIZE; cgi++) - { - ChunkGroup *cg = chunktable.chunkgroups[cgi]; - if (cg == NULL) - continue; - for (; csi < CTLEVEL2SIZE*CTLEVEL2SIZE; csi++) - { - ChunkSet *cs = cg->chunksets[csi]; - if (cs == NULL) - continue; - for (; bi < CTLEVEL1SIZE*CTLEVEL1SIZE*CTDATASIZE; bi += CTDATASIZE) - { - if (cs->bits[bi]) - { - end = false; - current = chunktable.toPosChunkIdx(cgi, csi, bi); - return; - } - } - bi = 0; - } - csi = 0; - bi = 0; - } - end = true; -} - - - - - - -bool TileGroup::setRequired(const PosTileIdx& ti) -{ - int tsi = tileSetIdx(ti); - if (tilesets[tsi] == NULL) - tilesets[tsi] = new TileSet; - bool prevset = tilesets[tsi]->setRequired(ti); - if (!prevset) - reqcount++; - return prevset; -} - -void TileGroup::setDrawn(const PosTileIdx& ti) -{ - int tsi = tileSetIdx(ti); - if (tilesets[tsi] == NULL) - tilesets[tsi] = new TileSet; - tilesets[tsi]->setDrawn(ti); -} - -PosTileIdx TileTable::toPosTileIdx(int tgi, int tsi, int bi) -{ - PosTileIdx ti(0,0); - ti.x += (tgi % TTLEVEL3SIZE) * TTLEVEL1SIZE * TTLEVEL2SIZE; - ti.y += (tgi / TTLEVEL3SIZE) * TTLEVEL1SIZE * TTLEVEL2SIZE; - ti.x += (tsi % TTLEVEL2SIZE) * TTLEVEL1SIZE; - ti.y += (tsi / TTLEVEL2SIZE) * TTLEVEL1SIZE; - ti.x += ((bi / TTDATASIZE) % TTLEVEL1SIZE); - ti.y += ((bi / TTDATASIZE) / TTLEVEL1SIZE); - return ti; -} - -bool TileTable::setRequired(const PosTileIdx& ti) -{ - int tgi = tileGroupIdx(ti); - if (tilegroups[tgi] == NULL) - tilegroups[tgi] = new TileGroup; - bool prevset = tilegroups[tgi]->setRequired(ti); - if (!prevset) - reqcount++; - return prevset; -} - -void TileTable::setDrawn(const PosTileIdx& ti) -{ - int tgi = tileGroupIdx(ti); - if (tilegroups[tgi] == NULL) - tilegroups[tgi] = new TileGroup; - tilegroups[tgi]->setDrawn(ti); -} - -bool TileTable::reject(const ZoomTileIdx& zti, const MapParams& mp) const -{ - // if this zoom tile includes more than one TileGroup, we can't reject early - if (zti.zoom < mp.baseZoom - TTLEVEL1BITS - TTLEVEL2BITS) - return false; - // zoom tiles anywhere except level 0 have the property of not crossing TileSet/TileGroup - // boundaries--either they're entirely inside a set/group, or they contain entire - // sets/groups--but for 0, that's not the case, so we'd have to check multiple sets/groups; - // instead, we just don't bother trying, since the tile at level 0 is going to have to be - // drawn anyway - if (zti.zoom == 0) - return false; - TileIdx ti = zti.toTileIdx(mp); - // if this zoom tile is contained within a TileSet, see if the set is NULL - if (zti.zoom >= mp.baseZoom - TTLEVEL1BITS) - return getTileSet(ti) == NULL; - // otherwise, the tile is within a TileGroup, but covers more than one TileSet; see if the TileGroup is NULL - return getTileGroup(ti) == NULL; -} - -int64_t TileTable::getNumRequired(const ZoomTileIdx& zti, const MapParams& mp) const -{ - // if this is the very top level, we already know the answer - if (zti.zoom == 0) - return reqcount; - // if this zoom tile is smaller than a TileSet, get its TileSet and check the tiles individually - if (zti.zoom > mp.baseZoom - TTLEVEL1BITS) - { - TileIdx topleft = zti.toTileIdx(mp); - TileSet *ts = getTileSet(topleft); - if (ts == NULL) - return 0; - int64_t count = 0; - int64_t size = 1 << (mp.baseZoom - zti.zoom); - for (int64_t x = 0; x < size; x++) - for (int64_t y = 0; y < size; y++) - if (ts->isRequired(topleft + TileIdx(x, y))) - count++; - return count; - } - // if >= TileSet size, but < TileGroup size, get the TileGroup and check the sets individually - if (zti.zoom > mp.baseZoom - TTLEVEL1BITS - TTLEVEL2BITS) - { - TileIdx topleft = zti.toTileIdx(mp); - TileGroup *tg = getTileGroup(topleft); - if (tg == NULL) - return 0; - int64_t count = 0; - int64_t size = 1 << (mp.baseZoom - TTLEVEL1BITS - zti.zoom); - for (int64_t x = 0; x < size; x++) - for (int64_t y = 0; y < size; y++) - { - TileSet *ts = tg->getTileSet(topleft + TileIdx(x << TTLEVEL1BITS, y << TTLEVEL1BITS)); - if (ts != NULL) - count += ts->bits.count(); - } - return count; - } - // if >= TileGroup size, check the TileGroups individually - TileIdx topleft = zti.toTileIdx(mp); - int64_t count = 0; - int64_t size = 1 << (mp.baseZoom - TTLEVEL1BITS - TTLEVEL2BITS - zti.zoom); - for (int64_t x = 0; x < size; x++) - for (int64_t y = 0; y < size; y++) - { - TileGroup *tg = getTileGroup(topleft + TileIdx(x << (TTLEVEL1BITS + TTLEVEL2BITS), y << (TTLEVEL1BITS + TTLEVEL2BITS))); - if (tg != NULL) - count += tg->reqcount; - } - return count; -} - -void TileTable::copyFrom(const TileTable& ttable) -{ - for (int tgi = 0; tgi < TTLEVEL3SIZE*TTLEVEL3SIZE; tgi++) - { - if (ttable.tilegroups[tgi] != NULL) - { - tilegroups[tgi] = new TileGroup; - for (int tsi = 0; tsi < TTLEVEL2SIZE*TTLEVEL2SIZE; tsi++) - { - if (ttable.tilegroups[tgi]->tilesets[tsi] != NULL) - { - tilegroups[tgi]->tilesets[tsi] = new TileSet(*(ttable.tilegroups[tgi]->tilesets[tsi])); - } - } - } - } -} - - - -RequiredTileIterator::RequiredTileIterator(TileTable& ttable) : tiletable(ttable), current(-1,-1) -{ - // if the very first tile is required, use it - ztgi = ztsi = zbi = 0; - current = TileTable::toPosTileIdx(fromZOrder(ztgi, TTLEVEL3SIZE), fromZOrder(ztsi, TTLEVEL2SIZE), fromZOrder(zbi, TTLEVEL1SIZE)*TTDATASIZE); - if (tiletable.isRequired(current)) - { - end = false; - return; - } - // ...otherwise, advance to the next one after it - advance(); -} - -void RequiredTileIterator::advance() -{ - zbi++; - for (; ztgi < TTLEVEL3SIZE*TTLEVEL3SIZE; ztgi++) - { - int tgi = fromZOrder(ztgi, TTLEVEL3SIZE); - TileGroup *tg = tiletable.tilegroups[tgi]; - if (tg == NULL) - continue; - for (; ztsi < TTLEVEL2SIZE*TTLEVEL2SIZE; ztsi++) - { - int tsi = fromZOrder(ztsi, TTLEVEL2SIZE); - TileSet *ts = tg->tilesets[tsi]; - if (ts == NULL) - continue; - for (; zbi < TTLEVEL1SIZE*TTLEVEL1SIZE; zbi++) - { - int bi = fromZOrder(zbi, TTLEVEL1SIZE); - if (ts->bits[bi*TTDATASIZE]) - { - end = false; - current = tiletable.toPosTileIdx(tgi, tsi, bi*TTDATASIZE); - return; - } - } - zbi = 0; - } - ztsi = 0; - zbi = 0; - } - end = true; -} - - - -ZoomTileIdx getZoomTile(int tgi, const MapParams& mp) -{ - TileIdx ti = TileTable::toPosTileIdx(tgi, 0, 0).toTileIdx(); - ZoomTileIdx zti = ti.toZoomTileIdx(mp); - return zti.toZoom(mp.baseZoom - TTLEVEL1BITS - TTLEVEL2BITS); -} - -TileGroupIterator::TileGroupIterator(TileTable& ttable, const MapParams& mparams) - : tiletable(ttable), mp(mparams), zti(-1,-1,-1) -{ - // if the very first TileGroup is non-NULL, use it - tgi = 0; - zti = getZoomTile(tgi, mp); - end = false; - if (tiletable.tilegroups[tgi] != NULL) - return; - // ...otherwise, advance to the next one - advance(); -} - -void TileGroupIterator::advance() -{ - tgi++; - for (; tgi < TTLEVEL3SIZE*TTLEVEL3SIZE; tgi++) - { - if (tiletable.tilegroups[tgi] != NULL) - { - zti = getZoomTile(tgi, mp); - return; - } - } - end = true; -} - - - - - -void RegionGroup::setRequired(const PosRegionIdx& ri) -{ - int rsi = regionSetIdx(ri); - if (regionsets[rsi] == NULL) - regionsets[rsi] = new RegionSet; - regionsets[rsi]->setRequired(ri); -} - -void RegionGroup::setDiskState(const PosRegionIdx& ri, int state) -{ - int rsi = regionSetIdx(ri); - if (regionsets[rsi] == NULL) - regionsets[rsi] = new RegionSet; - regionsets[rsi]->setDiskState(ri, state); -} - -PosRegionIdx RegionTable::toPosRegionIdx(int rgi, int rsi, int bi) -{ - PosRegionIdx ri(0,0); - ri.x += (rgi % RTLEVEL3SIZE) * RTLEVEL1SIZE * RTLEVEL2SIZE; - ri.z += (rgi / RTLEVEL3SIZE) * RTLEVEL1SIZE * RTLEVEL2SIZE; - ri.x += (rsi % RTLEVEL2SIZE) * RTLEVEL1SIZE; - ri.z += (rsi / RTLEVEL2SIZE) * RTLEVEL1SIZE; - ri.x += ((bi / RTDATASIZE) % RTLEVEL1SIZE); - ri.z += ((bi / RTDATASIZE) / RTLEVEL1SIZE); - return ri; -} - -void RegionTable::setRequired(const PosRegionIdx& ri) -{ - int rgi = regionGroupIdx(ri); - if (regiongroups[rgi] == NULL) - regiongroups[rgi] = new RegionGroup; - regiongroups[rgi]->setRequired(ri); -} - -void RegionTable::setDiskState(const PosRegionIdx& ri, int state) -{ - int rgi = regionGroupIdx(ri); - if (regiongroups[rgi] == NULL) - regiongroups[rgi] = new RegionGroup; - regiongroups[rgi]->setDiskState(ri, state); -} - -void RegionTable::copyFrom(const RegionTable& rtable) -{ - for (int rgi = 0; rgi < RTLEVEL3SIZE*RTLEVEL3SIZE; rgi++) - { - if (rtable.regiongroups[rgi] != NULL) - { - regiongroups[rgi] = new RegionGroup; - for (int rsi = 0; rsi < RTLEVEL2SIZE*RTLEVEL2SIZE; rsi++) - { - if (rtable.regiongroups[rgi]->regionsets[rsi] != NULL) - { - regiongroups[rgi]->regionsets[rsi] = new RegionSet(*(rtable.regiongroups[rgi]->regionsets[rsi])); - } - } - } - } -} +// Copyright 2010, 2011 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#include + +#include "tables.h" + +using namespace std; + + + +void ChunkGroup::setRequired(const PosChunkIdx& ci) +{ + int csi = chunkSetIdx(ci); + if (chunksets[csi] == NULL) + chunksets[csi] = new ChunkSet; + chunksets[csi]->setRequired(ci); +} + +void ChunkGroup::setDiskState(const PosChunkIdx& ci, int state) +{ + int csi = chunkSetIdx(ci); + if (chunksets[csi] == NULL) + chunksets[csi] = new ChunkSet; + chunksets[csi]->setDiskState(ci, state); +} + + + + +PosChunkIdx ChunkTable::toPosChunkIdx(int cgi, int csi, int bi) +{ + PosChunkIdx ci(0,0); + ci.x += (cgi % CTLEVEL3SIZE) * CTLEVEL1SIZE * CTLEVEL2SIZE; + ci.z += (cgi / CTLEVEL3SIZE) * CTLEVEL1SIZE * CTLEVEL2SIZE; + ci.x += (csi % CTLEVEL2SIZE) * CTLEVEL1SIZE; + ci.z += (csi / CTLEVEL2SIZE) * CTLEVEL1SIZE; + ci.x += ((bi / CTDATASIZE) % CTLEVEL1SIZE); + ci.z += ((bi / CTDATASIZE) / CTLEVEL1SIZE); + return ci; +} + +void ChunkTable::setRequired(const PosChunkIdx& ci) +{ + int cgi = chunkGroupIdx(ci); + if (chunkgroups[cgi] == NULL) + chunkgroups[cgi] = new ChunkGroup; + chunkgroups[cgi]->setRequired(ci); +} + +void ChunkTable::setDiskState(const PosChunkIdx& ci, int state) +{ + int cgi = chunkGroupIdx(ci); + if (chunkgroups[cgi] == NULL) + chunkgroups[cgi] = new ChunkGroup; + chunkgroups[cgi]->setDiskState(ci, state); +} + +void ChunkTable::copyFrom(const ChunkTable& ctable) +{ + for (int cgi = 0; cgi < CTLEVEL3SIZE*CTLEVEL3SIZE; cgi++) + { + if (ctable.chunkgroups[cgi] != NULL) + { + chunkgroups[cgi] = new ChunkGroup; + for (int csi = 0; csi < CTLEVEL2SIZE*CTLEVEL2SIZE; csi++) + { + if (ctable.chunkgroups[cgi]->chunksets[csi] != NULL) + { + chunkgroups[cgi]->chunksets[csi] = new ChunkSet(*(ctable.chunkgroups[cgi]->chunksets[csi])); + } + } + } + } +} + + + +RequiredChunkIterator::RequiredChunkIterator(ChunkTable& ctable) : current(-1,-1), chunktable(ctable) +{ + // if the very first chunk is required, use it + cgi = csi = bi = 0; + current = ChunkTable::toPosChunkIdx(cgi, csi, bi); + if (chunktable.isRequired(current)) + { + end = false; + return; + } + // ...otherwise, advance to the next one after it + advance(); +} + +void RequiredChunkIterator::advance() +{ + bi += CTDATASIZE; + for (; cgi < CTLEVEL3SIZE*CTLEVEL3SIZE; cgi++) + { + ChunkGroup *cg = chunktable.chunkgroups[cgi]; + if (cg == NULL) + continue; + for (; csi < CTLEVEL2SIZE*CTLEVEL2SIZE; csi++) + { + ChunkSet *cs = cg->chunksets[csi]; + if (cs == NULL) + continue; + for (; bi < CTLEVEL1SIZE*CTLEVEL1SIZE*CTDATASIZE; bi += CTDATASIZE) + { + if (cs->bits[bi]) + { + end = false; + current = chunktable.toPosChunkIdx(cgi, csi, bi); + return; + } + } + bi = 0; + } + csi = 0; + bi = 0; + } + end = true; +} + + + + + + +bool TileGroup::setRequired(const PosTileIdx& ti) +{ + int tsi = tileSetIdx(ti); + if (tilesets[tsi] == NULL) + tilesets[tsi] = new TileSet; + bool prevset = tilesets[tsi]->setRequired(ti); + if (!prevset) + reqcount++; + return prevset; +} + +void TileGroup::setDrawn(const PosTileIdx& ti) +{ + int tsi = tileSetIdx(ti); + if (tilesets[tsi] == NULL) + tilesets[tsi] = new TileSet; + tilesets[tsi]->setDrawn(ti); +} + +PosTileIdx TileTable::toPosTileIdx(int tgi, int tsi, int bi) +{ + PosTileIdx ti(0,0); + ti.x += (tgi % TTLEVEL3SIZE) * TTLEVEL1SIZE * TTLEVEL2SIZE; + ti.y += (tgi / TTLEVEL3SIZE) * TTLEVEL1SIZE * TTLEVEL2SIZE; + ti.x += (tsi % TTLEVEL2SIZE) * TTLEVEL1SIZE; + ti.y += (tsi / TTLEVEL2SIZE) * TTLEVEL1SIZE; + ti.x += ((bi / TTDATASIZE) % TTLEVEL1SIZE); + ti.y += ((bi / TTDATASIZE) / TTLEVEL1SIZE); + return ti; +} + +bool TileTable::setRequired(const PosTileIdx& ti) +{ + int tgi = tileGroupIdx(ti); + if (tilegroups[tgi] == NULL) + tilegroups[tgi] = new TileGroup; + bool prevset = tilegroups[tgi]->setRequired(ti); + if (!prevset) + reqcount++; + return prevset; +} + +void TileTable::setDrawn(const PosTileIdx& ti) +{ + int tgi = tileGroupIdx(ti); + if (tilegroups[tgi] == NULL) + tilegroups[tgi] = new TileGroup; + tilegroups[tgi]->setDrawn(ti); +} + +bool TileTable::reject(const ZoomTileIdx& zti, const MapParams& mp) const +{ + // if this zoom tile includes more than one TileGroup, we can't reject early + if (zti.zoom < mp.baseZoom - TTLEVEL1BITS - TTLEVEL2BITS) + return false; + // zoom tiles anywhere except level 0 have the property of not crossing TileSet/TileGroup + // boundaries--either they're entirely inside a set/group, or they contain entire + // sets/groups--but for 0, that's not the case, so we'd have to check multiple sets/groups; + // instead, we just don't bother trying, since the tile at level 0 is going to have to be + // drawn anyway + if (zti.zoom == 0) + return false; + TileIdx ti = zti.toTileIdx(mp); + // if this zoom tile is contained within a TileSet, see if the set is NULL + if (zti.zoom >= mp.baseZoom - TTLEVEL1BITS) + return getTileSet(ti) == NULL; + // otherwise, the tile is within a TileGroup, but covers more than one TileSet; see if the TileGroup is NULL + return getTileGroup(ti) == NULL; +} + +int64_t TileTable::getNumRequired(const ZoomTileIdx& zti, const MapParams& mp) const +{ + // if this is the very top level, we already know the answer + if (zti.zoom == 0) + return reqcount; + // if this zoom tile is smaller than a TileSet, get its TileSet and check the tiles individually + if (zti.zoom > mp.baseZoom - TTLEVEL1BITS) + { + TileIdx topleft = zti.toTileIdx(mp); + TileSet *ts = getTileSet(topleft); + if (ts == NULL) + return 0; + int64_t count = 0; + int64_t size = 1 << (mp.baseZoom - zti.zoom); + for (int64_t x = 0; x < size; x++) + for (int64_t y = 0; y < size; y++) + if (ts->isRequired(topleft + TileIdx(x, y))) + count++; + return count; + } + // if >= TileSet size, but < TileGroup size, get the TileGroup and check the sets individually + if (zti.zoom > mp.baseZoom - TTLEVEL1BITS - TTLEVEL2BITS) + { + TileIdx topleft = zti.toTileIdx(mp); + TileGroup *tg = getTileGroup(topleft); + if (tg == NULL) + return 0; + int64_t count = 0; + int64_t size = 1 << (mp.baseZoom - TTLEVEL1BITS - zti.zoom); + for (int64_t x = 0; x < size; x++) + for (int64_t y = 0; y < size; y++) + { + TileSet *ts = tg->getTileSet(topleft + TileIdx(x << TTLEVEL1BITS, y << TTLEVEL1BITS)); + if (ts != NULL) + count += ts->bits.count(); + } + return count; + } + // if >= TileGroup size, check the TileGroups individually + TileIdx topleft = zti.toTileIdx(mp); + int64_t count = 0; + int64_t size = 1 << (mp.baseZoom - TTLEVEL1BITS - TTLEVEL2BITS - zti.zoom); + for (int64_t x = 0; x < size; x++) + for (int64_t y = 0; y < size; y++) + { + TileGroup *tg = getTileGroup(topleft + TileIdx(x << (TTLEVEL1BITS + TTLEVEL2BITS), y << (TTLEVEL1BITS + TTLEVEL2BITS))); + if (tg != NULL) + count += tg->reqcount; + } + return count; +} + +void TileTable::copyFrom(const TileTable& ttable) +{ + for (int tgi = 0; tgi < TTLEVEL3SIZE*TTLEVEL3SIZE; tgi++) + { + if (ttable.tilegroups[tgi] != NULL) + { + tilegroups[tgi] = new TileGroup; + for (int tsi = 0; tsi < TTLEVEL2SIZE*TTLEVEL2SIZE; tsi++) + { + if (ttable.tilegroups[tgi]->tilesets[tsi] != NULL) + { + tilegroups[tgi]->tilesets[tsi] = new TileSet(*(ttable.tilegroups[tgi]->tilesets[tsi])); + } + } + } + } +} + + + +RequiredTileIterator::RequiredTileIterator(TileTable& ttable) : current(-1,-1), tiletable(ttable) +{ + // if the very first tile is required, use it + ztgi = ztsi = zbi = 0; + current = TileTable::toPosTileIdx(fromZOrder(ztgi, TTLEVEL3SIZE), fromZOrder(ztsi, TTLEVEL2SIZE), fromZOrder(zbi, TTLEVEL1SIZE)*TTDATASIZE); + if (tiletable.isRequired(current)) + { + end = false; + return; + } + // ...otherwise, advance to the next one after it + advance(); +} + +void RequiredTileIterator::advance() +{ + zbi++; + for (; ztgi < TTLEVEL3SIZE*TTLEVEL3SIZE; ztgi++) + { + int tgi = fromZOrder(ztgi, TTLEVEL3SIZE); + TileGroup *tg = tiletable.tilegroups[tgi]; + if (tg == NULL) + continue; + for (; ztsi < TTLEVEL2SIZE*TTLEVEL2SIZE; ztsi++) + { + int tsi = fromZOrder(ztsi, TTLEVEL2SIZE); + TileSet *ts = tg->tilesets[tsi]; + if (ts == NULL) + continue; + for (; zbi < TTLEVEL1SIZE*TTLEVEL1SIZE; zbi++) + { + int bi = fromZOrder(zbi, TTLEVEL1SIZE); + if (ts->bits[bi*TTDATASIZE]) + { + end = false; + current = tiletable.toPosTileIdx(tgi, tsi, bi*TTDATASIZE); + return; + } + } + zbi = 0; + } + ztsi = 0; + zbi = 0; + } + end = true; +} + + + +ZoomTileIdx getZoomTile(int tgi, const MapParams& mp) +{ + TileIdx ti = TileTable::toPosTileIdx(tgi, 0, 0).toTileIdx(); + ZoomTileIdx zti = ti.toZoomTileIdx(mp); + return zti.toZoom(mp.baseZoom - TTLEVEL1BITS - TTLEVEL2BITS); +} + +TileGroupIterator::TileGroupIterator(TileTable& ttable, const MapParams& mparams) + : zti(-1,-1,-1), tiletable(ttable), mp(mparams) +{ + // if the very first TileGroup is non-NULL, use it + tgi = 0; + zti = getZoomTile(tgi, mp); + end = false; + if (tiletable.tilegroups[tgi] != NULL) + return; + // ...otherwise, advance to the next one + advance(); +} + +void TileGroupIterator::advance() +{ + tgi++; + for (; tgi < TTLEVEL3SIZE*TTLEVEL3SIZE; tgi++) + { + if (tiletable.tilegroups[tgi] != NULL) + { + zti = getZoomTile(tgi, mp); + return; + } + } + end = true; +} + + + + + +void RegionGroup::setRequired(const PosRegionIdx& ri) +{ + int rsi = regionSetIdx(ri); + if (regionsets[rsi] == NULL) + regionsets[rsi] = new RegionSet; + regionsets[rsi]->setRequired(ri); +} + +void RegionGroup::setDiskState(const PosRegionIdx& ri, int state) +{ + int rsi = regionSetIdx(ri); + if (regionsets[rsi] == NULL) + regionsets[rsi] = new RegionSet; + regionsets[rsi]->setDiskState(ri, state); +} + +PosRegionIdx RegionTable::toPosRegionIdx(int rgi, int rsi, int bi) +{ + PosRegionIdx ri(0,0); + ri.x += (rgi % RTLEVEL3SIZE) * RTLEVEL1SIZE * RTLEVEL2SIZE; + ri.z += (rgi / RTLEVEL3SIZE) * RTLEVEL1SIZE * RTLEVEL2SIZE; + ri.x += (rsi % RTLEVEL2SIZE) * RTLEVEL1SIZE; + ri.z += (rsi / RTLEVEL2SIZE) * RTLEVEL1SIZE; + ri.x += ((bi / RTDATASIZE) % RTLEVEL1SIZE); + ri.z += ((bi / RTDATASIZE) / RTLEVEL1SIZE); + return ri; +} + +void RegionTable::setRequired(const PosRegionIdx& ri) +{ + int rgi = regionGroupIdx(ri); + if (regiongroups[rgi] == NULL) + regiongroups[rgi] = new RegionGroup; + regiongroups[rgi]->setRequired(ri); +} + +void RegionTable::setDiskState(const PosRegionIdx& ri, int state) +{ + int rgi = regionGroupIdx(ri); + if (regiongroups[rgi] == NULL) + regiongroups[rgi] = new RegionGroup; + regiongroups[rgi]->setDiskState(ri, state); +} + +void RegionTable::copyFrom(const RegionTable& rtable) +{ + for (int rgi = 0; rgi < RTLEVEL3SIZE*RTLEVEL3SIZE; rgi++) + { + if (rtable.regiongroups[rgi] != NULL) + { + regiongroups[rgi] = new RegionGroup; + for (int rsi = 0; rsi < RTLEVEL2SIZE*RTLEVEL2SIZE; rsi++) + { + if (rtable.regiongroups[rgi]->regionsets[rsi] != NULL) + { + regiongroups[rgi]->regionsets[rsi] = new RegionSet(*(rtable.regiongroups[rgi]->regionsets[rsi])); + } + } + } + } +} diff --git a/tables.h b/tables.h old mode 100755 new mode 100644 index 29c390f..e1a816d --- a/tables.h +++ b/tables.h @@ -1,385 +1,402 @@ -// Copyright 2010, 2011 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#ifndef TABLES_H -#define TABLES_H - -#include -#include - -#include "map.h" -#include "utils.h" - - - -#define CTDATASIZE 3 - -#define CTLEVEL1BITS 5 -#define CTLEVEL2BITS 5 -#define CTLEVEL3BITS 8 - -#define CTLEVEL1SIZE (1 << CTLEVEL1BITS) -#define CTLEVEL2SIZE (1 << CTLEVEL2BITS) -#define CTLEVEL3SIZE (1 << CTLEVEL3BITS) -#define CTTOTALSIZE (CTLEVEL1SIZE * CTLEVEL2SIZE * CTLEVEL3SIZE) - -#define CTLEVEL1MASK (CTLEVEL1SIZE - 1) -#define CTLEVEL2MASK ((CTLEVEL2SIZE - 1) << CTLEVEL1BITS) -#define CTLEVEL3MASK (((CTLEVEL3SIZE - 1) << CTLEVEL1BITS) << CTLEVEL2BITS) - -#define CTGETLEVEL1(a) (a & CTLEVEL1MASK) -#define CTGETLEVEL2(a) ((a & CTLEVEL2MASK) >> CTLEVEL1BITS) -#define CTGETLEVEL3(a) (((a & CTLEVEL3MASK) >> CTLEVEL2BITS) >> CTLEVEL1BITS) - -// variation of ChunkIdx for use with the ChunkTable: translates so that all coords are positive -// ...can also be used to check for the map being too big -struct PosChunkIdx -{ - int64_t x, z; - - PosChunkIdx(int64_t xx, int64_t zz) : x(xx), z(zz) {} - PosChunkIdx(const ChunkIdx& ci) : x(ci.x + CTTOTALSIZE/2), z(ci.z + CTTOTALSIZE/2) {} - ChunkIdx toChunkIdx() const {return ChunkIdx(x - CTTOTALSIZE/2, z - CTTOTALSIZE/2);} - bool valid() const {return x >= 0 && x < CTTOTALSIZE && z >= 0 && z < CTTOTALSIZE;} - - bool operator==(const PosChunkIdx& ci) const {return x == ci.x && z == ci.z;} - bool operator!=(const PosChunkIdx& ci) const {return !operator==(ci);} -}; - -// structure to hold information about a 32x32 set of chunks: for each chunk, whether it needs to be drawn, -// whether it's even present on disk, etc. -struct ChunkSet -{ - // each chunk gets 3 bits: - // -first bit is 1 for required (must be drawn), 0 for not required - // -last two bits describe state of chunk on disk: - // 00: have not tried to find chunk on disk yet - // 01: have successfully read chunk from disk (i.e. it should be in the cache, if we still need it) - // 10: chunk does not exist on disk - // 11: chunk file is corrupted - static const int CHUNK_UNKNOWN = 0; - static const int CHUNK_CACHED = 1; - static const int CHUNK_MISSING = 2; - static const int CHUNK_CORRUPTED = 3; - std::bitset bits; - - size_t bitIdx(const PosChunkIdx& ci) const {return (CTGETLEVEL1(ci.z) * CTLEVEL1SIZE + CTGETLEVEL1(ci.x)) * CTDATASIZE;} - - void setRequired(const PosChunkIdx& ci) {bits.set(bitIdx(ci));} - void setDiskState(const PosChunkIdx& ci, int state) {size_t bi = bitIdx(ci); bits[bi+1] = state & 0x2; bits[bi+2] = state & 0x1;} -}; - -// first level of indirection: information about a 32x32 group of ChunkSets, and hence a 1024x1024 set of chunks -struct ChunkGroup -{ - // pointers to ChunkSets with the data, or NULL for sets that aren't used - ChunkSet *chunksets[CTLEVEL2SIZE*CTLEVEL2SIZE]; - - ChunkGroup() {for (int i = 0; i < CTLEVEL2SIZE*CTLEVEL2SIZE; i++) chunksets[i] = NULL;} - ~ChunkGroup() {for (int i = 0; i < CTLEVEL2SIZE*CTLEVEL2SIZE; i++) if (chunksets[i] != NULL) delete chunksets[i];} - - int chunkSetIdx(const PosChunkIdx& ci) const {return CTGETLEVEL2(ci.z) * CTLEVEL2SIZE + CTGETLEVEL2(ci.x);} - ChunkSet* getChunkSet(const PosChunkIdx& ci) const {return chunksets[chunkSetIdx(ci)];} - - void setRequired(const PosChunkIdx& ci); - void setDiskState(const PosChunkIdx& ci, int state); -}; - -// second (and final) level of indirection: 256x256 groups, so 262144x262144 possible chunks -struct ChunkTable : private nocopy -{ - ChunkGroup *chunkgroups[CTLEVEL3SIZE*CTLEVEL3SIZE]; - - ChunkTable() {for (int i = 0; i < CTLEVEL3SIZE*CTLEVEL3SIZE; i++) chunkgroups[i] = NULL;} - ~ChunkTable() {for (int i = 0; i < CTLEVEL3SIZE*CTLEVEL3SIZE; i++) if (chunkgroups[i] != NULL) delete chunkgroups[i];} - - int chunkGroupIdx(const PosChunkIdx& ci) const {return CTGETLEVEL3(ci.z) * CTLEVEL3SIZE + CTGETLEVEL3(ci.x);} - ChunkGroup* getChunkGroup(const PosChunkIdx& ci) const {return chunkgroups[chunkGroupIdx(ci)];} - ChunkSet* getChunkSet(const PosChunkIdx& ci) const {ChunkGroup *cg = getChunkGroup(ci); return (cg == NULL) ? NULL : cg->getChunkSet(ci);} - - // given indices into the ChunkGroups/ChunkSets/bitset, construct a PosChunkIdx - static PosChunkIdx toPosChunkIdx(int cgi, int csi, int bi); - - bool isRequired(const PosChunkIdx& ci) const {ChunkSet *cs = getChunkSet(ci); return (cs == NULL) ? false : cs->bits[cs->bitIdx(ci)];} - 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));} - - void setRequired(const PosChunkIdx& ci); - void setDiskState(const PosChunkIdx& ci, int state); - - void copyFrom(const ChunkTable& ctable); -}; - - -// given a ChunkTable, iterates over the required chunks -// ...this is obsolete and not used, except in some test functions -struct RequiredChunkIterator -{ - bool end; // true once we've reached the end - PosChunkIdx current; // if end == false, holds the current chunk - - ChunkTable& chunktable; - int cgi, csi, bi; - - // constructor initializes us to the first required chunk - RequiredChunkIterator(ChunkTable& ctable); - - // move to the next required chunk, or to the end - void advance(); -}; - - - - - - - - - - -#define TTDATASIZE 2 - -#define TTLEVEL1BITS 4 -#define TTLEVEL2BITS 4 -#define TTLEVEL3BITS 8 - -#define TTLEVEL1SIZE (1 << TTLEVEL1BITS) -#define TTLEVEL2SIZE (1 << TTLEVEL2BITS) -#define TTLEVEL3SIZE (1 << TTLEVEL3BITS) -#define TTTOTALSIZE (TTLEVEL1SIZE * TTLEVEL2SIZE * TTLEVEL3SIZE) - -#define TTLEVEL1MASK (TTLEVEL1SIZE - 1) -#define TTLEVEL2MASK ((TTLEVEL2SIZE - 1) << TTLEVEL1BITS) -#define TTLEVEL3MASK (((TTLEVEL3SIZE - 1) << TTLEVEL1BITS) << TTLEVEL2BITS) - -#define TTGETLEVEL1(a) (a & TTLEVEL1MASK) -#define TTGETLEVEL2(a) ((a & TTLEVEL2MASK) >> TTLEVEL1BITS) -#define TTGETLEVEL3(a) (((a & TTLEVEL3MASK) >> TTLEVEL2BITS) >> TTLEVEL1BITS) - -// variation of TileIdx for use with the TileTable: translates so that all coords are positive -// ...can also be used to check for the map being too big -struct PosTileIdx -{ - int64_t x, y; - - PosTileIdx(int64_t xx, int64_t yy) : x(xx), y(yy) {} - PosTileIdx(const TileIdx& ti) : x(ti.x + TTTOTALSIZE/2), y(ti.y + TTTOTALSIZE/2) {} - TileIdx toTileIdx() const {return TileIdx(x - TTTOTALSIZE/2, y - TTTOTALSIZE/2);} - bool valid() const {return x >= 0 && x < TTTOTALSIZE && y >= 0 && y < TTTOTALSIZE;} - - bool operator==(const PosTileIdx& ti) const {return x == ti.x && y == ti.y;} - bool operator!=(const PosTileIdx& ti) const {return !operator==(ti);} -}; - -// structure to hold information about a 16x16 set of tiles: for each tile, whether it's been drawn yet -struct TileSet -{ - // each tile gets two bits: first is whether it's required, second is whether it's been drawn - std::bitset bits; - - size_t bitIdx(const PosTileIdx& ti) const {return (TTGETLEVEL1(ti.y) * TTLEVEL1SIZE + TTGETLEVEL1(ti.x)) * TTDATASIZE;} - - // assumes that ti actually belongs to this set - bool isRequired(const PosTileIdx& ti) const {return bits[bitIdx(ti)];} - - // set tile's required bit and return previous state of bit - bool setRequired(const PosTileIdx& ti) {size_t bi = bitIdx(ti); bool rv = bits[bi]; bits.set(bi); return rv;} - void setDrawn(const PosTileIdx& ti) {bits.set(bitIdx(ti)+1);} -}; - -// first level of indirection: information about a 256x256 set of tiles -struct TileGroup -{ - // pointers to TileSets with the data, or NULL for 16x16 sets that aren't used - TileSet *tilesets[TTLEVEL2SIZE*TTLEVEL2SIZE]; - - // number of tiles in this group that have been set to required - int64_t reqcount; - - TileGroup() : reqcount(0) {for (int i = 0; i < TTLEVEL2SIZE*TTLEVEL2SIZE; i++) tilesets[i] = NULL;} - ~TileGroup() {for (int i = 0; i < TTLEVEL2SIZE*TTLEVEL2SIZE; i++) if (tilesets[i] != NULL) delete tilesets[i];} - - int tileSetIdx(const PosTileIdx& ti) const {return TTGETLEVEL2(ti.y) * TTLEVEL2SIZE + TTGETLEVEL2(ti.x);} - TileSet* getTileSet(const PosTileIdx& ti) const {return tilesets[tileSetIdx(ti)];} - - bool setRequired(const PosTileIdx& ti); // set tile's required bit and return previous state of bit - void setDrawn(const PosTileIdx& ti); -}; - -// second (and final) level of indirection: a 65536x65536 set of tiles -struct TileTable : private nocopy -{ - TileGroup *tilegroups[TTLEVEL3SIZE*TTLEVEL3SIZE]; - - int64_t reqcount; - - TileTable() : reqcount(0) {for (int i = 0; i < TTLEVEL3SIZE*TTLEVEL3SIZE; i++) tilegroups[i] = NULL;} - ~TileTable() {for (int i = 0; i < TTLEVEL3SIZE*TTLEVEL3SIZE; i++) if (tilegroups[i] != NULL) delete tilegroups[i];} - - int tileGroupIdx(const PosTileIdx& ti) const {return TTGETLEVEL3(ti.y) * TTLEVEL3SIZE + TTGETLEVEL3(ti.x);} - TileGroup* getTileGroup(const PosTileIdx& ti) const {return tilegroups[tileGroupIdx(ti)];} - TileSet* getTileSet(const PosTileIdx& ti) const {TileGroup *tg = getTileGroup(ti); return (tg == NULL) ? NULL : tg->getTileSet(ti);} - - // given indices into the TileGroups/TileSets/bitset, construct a PosTileIdx - static PosTileIdx toPosTileIdx(int tgi, int tsi, int bi); - - bool isRequired(const PosTileIdx& ti) const {TileSet *ts = getTileSet(ti); return (ts == NULL) ? false : ts->bits[ts->bitIdx(ti)];} - bool isDrawn(const PosTileIdx& ti) const {TileSet *ts = getTileSet(ti); return (ts == NULL) ? false : ts->bits[ts->bitIdx(ti)+1];} - - bool setRequired(const PosTileIdx& ti); // set tile's required bit and return previous state of bit - void setDrawn(const PosTileIdx& ti); - - // see if an entire zoom tile can be rejected because its TileGroup or TileSet is NULL - bool reject(const ZoomTileIdx& zti, const MapParams& mp) const; - - // get the total number of base tiles required to draw a zoom tile - int64_t getNumRequired(const ZoomTileIdx& zti, const MapParams& mp) const; - - void copyFrom(const TileTable& ttable); -}; - - - -// given a TileTable, iterates over the required tiles -struct RequiredTileIterator -{ - bool end; // true once we've reached the end - PosTileIdx current; // if end == false, holds the current tile - - TileTable& tiletable; - // these guys are Z-order indices and must be converted to row-major when accessing the TileTable - int ztgi, ztsi, zbi; - - // constructor initializes us to the first required tile - RequiredTileIterator(TileTable& ttable); - - // move in Z-order to the next required tile, or to the end - void advance(); -}; - -// given a TileTable, iterates over the non-NULL TileGroups -struct TileGroupIterator -{ - bool end; // true once we've reached the end - int tgi; // if end == false, holds the current index into TileTable::tilegroups - ZoomTileIdx zti; // if end == false, holds the zoom tile corresponding to the current TileGroup - - TileTable& tiletable; - MapParams mp; - - // constructor initializes to first non-NULL TileGroup - TileGroupIterator(TileTable& ttable, const MapParams& mparams); - - // move to the next non-NULL TileGroup, or to the end - void advance(); -}; - - - - -#define RTDATASIZE 3 - -#define RTLEVEL1BITS 4 -#define RTLEVEL2BITS 4 -#define RTLEVEL3BITS 6 - -#define RTLEVEL1SIZE (1 << RTLEVEL1BITS) -#define RTLEVEL2SIZE (1 << RTLEVEL2BITS) -#define RTLEVEL3SIZE (1 << RTLEVEL3BITS) -#define RTTOTALSIZE (RTLEVEL1SIZE * RTLEVEL2SIZE * RTLEVEL3SIZE) - -#define RTLEVEL1MASK (RTLEVEL1SIZE - 1) -#define RTLEVEL2MASK ((RTLEVEL2SIZE - 1) << RTLEVEL1BITS) -#define RTLEVEL3MASK (((RTLEVEL3SIZE - 1) << RTLEVEL1BITS) << RTLEVEL2BITS) - -#define RTGETLEVEL1(a) (a & RTLEVEL1MASK) -#define RTGETLEVEL2(a) ((a & RTLEVEL2MASK) >> RTLEVEL1BITS) -#define RTGETLEVEL3(a) (((a & RTLEVEL3MASK) >> RTLEVEL2BITS) >> RTLEVEL1BITS) - -struct PosRegionIdx -{ - int64_t x, z; - - PosRegionIdx(int64_t xx, int64_t zz) : x(xx), z(zz) {} - PosRegionIdx(const RegionIdx& ri) : x(ri.x + RTTOTALSIZE/2), z(ri.z + RTTOTALSIZE/2) {} - RegionIdx toRegionIdx() const {return RegionIdx(x - RTTOTALSIZE/2, z - RTTOTALSIZE/2);} - bool valid() const {return x >= 0 && x < RTTOTALSIZE && z >= 0 && z < RTTOTALSIZE;} - - bool operator==(const PosRegionIdx& ri) const {return x == ri.x && z == ri.z;} - bool operator!=(const PosRegionIdx& ri) const {return !operator==(ri);} -}; - -struct RegionSet -{ - // each region gets 3 bits: - // -first bit is 1 for required (must be drawn), 0 for not required - // -last two bits describe state of region on disk: - // 00: have not tried to find region on disk yet - // 01: have successfully read region from disk (i.e. it should be in the cache, if we still need it) - // 10: region does not exist on disk - // 11: region file is corrupted - static const int REGION_UNKNOWN = 0; - static const int REGION_CACHED = 1; - static const int REGION_MISSING = 2; - static const int REGION_CORRUPTED = 3; - std::bitset bits; - - size_t bitIdx(const PosRegionIdx& ri) const {return (RTGETLEVEL1(ri.z) * RTLEVEL1SIZE + RTGETLEVEL1(ri.x)) * RTDATASIZE;} - - void setRequired(const PosRegionIdx& ri) {bits.set(bitIdx(ri));} - void setDiskState(const PosRegionIdx& ri, int state) {size_t bi = bitIdx(ri); bits[bi+1] = state & 0x2; bits[bi+2] = state & 0x1;} -}; - -struct RegionGroup -{ - RegionSet *regionsets[RTLEVEL2SIZE*RTLEVEL2SIZE]; - - RegionGroup() {for (int i = 0; i < RTLEVEL2SIZE*RTLEVEL2SIZE; i++) regionsets[i] = NULL;} - ~RegionGroup() {for (int i = 0; i < RTLEVEL2SIZE*RTLEVEL2SIZE; i++) if (regionsets[i] != NULL) delete regionsets[i];} - - int regionSetIdx(const PosRegionIdx& ri) const {return RTGETLEVEL2(ri.z) * RTLEVEL2SIZE + RTGETLEVEL2(ri.x);} - RegionSet* getRegionSet(const PosRegionIdx& ri) const {return regionsets[regionSetIdx(ri)];} - - void setRequired(const PosRegionIdx& ri); - void setDiskState(const PosRegionIdx& ri, int state); -}; - -struct RegionTable : private nocopy -{ - RegionGroup *regiongroups[RTLEVEL3SIZE*RTLEVEL3SIZE]; - - RegionTable() {for (int i = 0; i < RTLEVEL3SIZE*RTLEVEL3SIZE; i++) regiongroups[i] = NULL;} - ~RegionTable() {for (int i = 0; i < RTLEVEL3SIZE*RTLEVEL3SIZE; i++) if (regiongroups[i] != NULL) delete regiongroups[i];} - - int regionGroupIdx(const PosRegionIdx& ri) const {return RTGETLEVEL3(ri.z) * RTLEVEL3SIZE + RTGETLEVEL3(ri.x);} - RegionGroup* getRegionGroup(const PosRegionIdx& ri) const {return regiongroups[regionGroupIdx(ri)];} - RegionSet* getRegionSet(const PosRegionIdx& ri) const {RegionGroup *rg = getRegionGroup(ri); return (rg == NULL) ? NULL : rg->getRegionSet(ri);} - - // given indices into the RegionGroups/RegionSets/bitset, construct a PosRegionIdx - static PosRegionIdx toPosRegionIdx(int rgi, int rsi, int bi); - - bool isRequired(const PosRegionIdx& ri) const {RegionSet *rs = getRegionSet(ri); return (rs == NULL) ? false : rs->bits[rs->bitIdx(ri)];} - 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));} - - void setRequired(const PosRegionIdx& ri); - void setDiskState(const PosRegionIdx& ri, int state); - - void copyFrom(const RegionTable& rtable); -}; - - - -#endif // TABLES_H \ No newline at end of file +// Copyright 2010, 2011 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#ifndef TABLES_H +#define TABLES_H + +#include +#include + +#include "map.h" +#include "utils.h" + + + +#define CTDATASIZE 3 + +#define CTLEVEL1BITS 5 +#define CTLEVEL2BITS 5 +#define CTLEVEL3BITS 8 + +#define CTLEVEL1SIZE (1 << CTLEVEL1BITS) +#define CTLEVEL2SIZE (1 << CTLEVEL2BITS) +#define CTLEVEL3SIZE (1 << CTLEVEL3BITS) +#define CTTOTALSIZE (CTLEVEL1SIZE * CTLEVEL2SIZE * CTLEVEL3SIZE) + +#define CTLEVEL1MASK (CTLEVEL1SIZE - 1) +#define CTLEVEL2MASK ((CTLEVEL2SIZE - 1) << CTLEVEL1BITS) +#define CTLEVEL3MASK (((CTLEVEL3SIZE - 1) << CTLEVEL1BITS) << CTLEVEL2BITS) + +#define CTGETLEVEL1(a) (a & CTLEVEL1MASK) +#define CTGETLEVEL2(a) ((a & CTLEVEL2MASK) >> CTLEVEL1BITS) +#define CTGETLEVEL3(a) (((a & CTLEVEL3MASK) >> CTLEVEL2BITS) >> CTLEVEL1BITS) + +// variation of ChunkIdx for use with the ChunkTable: translates so that all coords are positive +// ...can also be used to check for the map being too big +struct PosChunkIdx +{ + int64_t x, z; + + PosChunkIdx(int64_t xx, int64_t zz) : x(xx), z(zz) {} + PosChunkIdx(const ChunkIdx& ci) : x(ci.x + CTTOTALSIZE/2), z(ci.z + CTTOTALSIZE/2) {} + ChunkIdx toChunkIdx() const {return ChunkIdx(x - CTTOTALSIZE/2, z - CTTOTALSIZE/2);} + bool valid() const {return x >= 0 && x < CTTOTALSIZE && z >= 0 && z < CTTOTALSIZE;} + + bool operator==(const PosChunkIdx& ci) const {return x == ci.x && z == ci.z;} + bool operator!=(const PosChunkIdx& ci) const {return !operator==(ci);} +}; + +// structure to hold information about a 32x32 set of chunks: for each chunk, whether it needs to be drawn, +// whether it's even present on disk, etc. +struct ChunkSet +{ + // each chunk gets 3 bits: + // -first bit is 1 for required (must be drawn), 0 for not required + // -last two bits describe state of chunk on disk: + // 00: have not tried to find chunk on disk yet + // 01: have successfully read chunk from disk (i.e. it should be in the cache, if we still need it) + // 10: chunk does not exist on disk + // 11: chunk file is corrupted + static const int CHUNK_UNKNOWN = 0; + static const int CHUNK_CACHED = 1; + static const int CHUNK_MISSING = 2; + static const int CHUNK_CORRUPTED = 3; + std::bitset bits; + + size_t bitIdx(const PosChunkIdx& ci) const {return (CTGETLEVEL1(ci.z) * CTLEVEL1SIZE + CTGETLEVEL1(ci.x)) * CTDATASIZE;} + + void setRequired(const PosChunkIdx& ci) {bits.set(bitIdx(ci));} + void setDiskState(const PosChunkIdx& ci, int state) {size_t bi = bitIdx(ci); bits[bi+1] = state & 0x2; bits[bi+2] = state & 0x1;} +}; + +// first level of indirection: information about a 32x32 group of ChunkSets, and hence a 1024x1024 set of chunks +struct ChunkGroup +{ + // pointers to ChunkSets with the data, or NULL for sets that aren't used + ChunkSet *chunksets[CTLEVEL2SIZE*CTLEVEL2SIZE]; + + ChunkGroup() {for (int i = 0; i < CTLEVEL2SIZE*CTLEVEL2SIZE; i++) chunksets[i] = NULL;} + ~ChunkGroup() {for (int i = 0; i < CTLEVEL2SIZE*CTLEVEL2SIZE; i++) if (chunksets[i] != NULL) delete chunksets[i];} + + int chunkSetIdx(const PosChunkIdx& ci) const {return CTGETLEVEL2(ci.z) * CTLEVEL2SIZE + CTGETLEVEL2(ci.x);} + ChunkSet* getChunkSet(const PosChunkIdx& ci) const {return chunksets[chunkSetIdx(ci)];} + + void setRequired(const PosChunkIdx& ci); + void setDiskState(const PosChunkIdx& ci, int state); +}; + +// second (and final) level of indirection: 256x256 groups, so 262144x262144 possible chunks +struct ChunkTable : private nocopy +{ + ChunkGroup *chunkgroups[CTLEVEL3SIZE*CTLEVEL3SIZE]; + + ChunkTable() {for (int i = 0; i < CTLEVEL3SIZE*CTLEVEL3SIZE; i++) chunkgroups[i] = NULL;} + ~ChunkTable() {for (int i = 0; i < CTLEVEL3SIZE*CTLEVEL3SIZE; i++) delete chunkgroups[i];} + + int chunkGroupIdx(const PosChunkIdx& ci) const {return CTGETLEVEL3(ci.z) * CTLEVEL3SIZE + CTGETLEVEL3(ci.x);} + ChunkGroup* getChunkGroup(const PosChunkIdx& ci) const {return chunkgroups[chunkGroupIdx(ci)];} + ChunkSet* getChunkSet(const PosChunkIdx& ci) const + { + if (ChunkGroup *cg = getChunkGroup(ci)) + return cg->getChunkSet(ci); + return NULL; + } + + // given indices into the ChunkGroups/ChunkSets/bitset, construct a PosChunkIdx + static PosChunkIdx toPosChunkIdx(int cgi, int csi, int bi); + + bool isRequired(const PosChunkIdx& ci) const { + if (ChunkSet *cs = getChunkSet(ci)) + return cs->bits[cs->bitIdx(ci)]; + return false; + } + int getDiskState(const PosChunkIdx& ci) const + { + if (ChunkSet *cs = getChunkSet(ci)) + { + int bitIdx = cs->bitIdx(ci); + return (cs->bits[bitIdx+1] << 1) | cs->bits[bitIdx+2]; + } + return 0; + } + + void setRequired(const PosChunkIdx& ci); + void setDiskState(const PosChunkIdx& ci, int state); + + void copyFrom(const ChunkTable& ctable); +}; + + +// given a ChunkTable, iterates over the required chunks +// ...this is obsolete and not used, except in some test functions +struct RequiredChunkIterator +{ + bool end; // true once we've reached the end + PosChunkIdx current; // if end == false, holds the current chunk + + ChunkTable& chunktable; + int cgi, csi, bi; + + // constructor initializes us to the first required chunk + RequiredChunkIterator(ChunkTable& ctable); + + // move to the next required chunk, or to the end + void advance(); +}; + + + + + + + + + + +#define TTDATASIZE 2 + +#define TTLEVEL1BITS 4 +#define TTLEVEL2BITS 4 +#define TTLEVEL3BITS 8 + +#define TTLEVEL1SIZE (1 << TTLEVEL1BITS) +#define TTLEVEL2SIZE (1 << TTLEVEL2BITS) +#define TTLEVEL3SIZE (1 << TTLEVEL3BITS) +#define TTTOTALSIZE (TTLEVEL1SIZE * TTLEVEL2SIZE * TTLEVEL3SIZE) + +#define TTLEVEL1MASK (TTLEVEL1SIZE - 1) +#define TTLEVEL2MASK ((TTLEVEL2SIZE - 1) << TTLEVEL1BITS) +#define TTLEVEL3MASK (((TTLEVEL3SIZE - 1) << TTLEVEL1BITS) << TTLEVEL2BITS) + +#define TTGETLEVEL1(a) (a & TTLEVEL1MASK) +#define TTGETLEVEL2(a) ((a & TTLEVEL2MASK) >> TTLEVEL1BITS) +#define TTGETLEVEL3(a) (((a & TTLEVEL3MASK) >> TTLEVEL2BITS) >> TTLEVEL1BITS) + +// variation of TileIdx for use with the TileTable: translates so that all coords are positive +// ...can also be used to check for the map being too big +struct PosTileIdx +{ + int64_t x, y; + + PosTileIdx(int64_t xx, int64_t yy) : x(xx), y(yy) {} + PosTileIdx(const TileIdx& ti) : x(ti.x + TTTOTALSIZE/2), y(ti.y + TTTOTALSIZE/2) {} + TileIdx toTileIdx() const {return TileIdx(x - TTTOTALSIZE/2, y - TTTOTALSIZE/2);} + bool valid() const {return x >= 0 && x < TTTOTALSIZE && y >= 0 && y < TTTOTALSIZE;} + + bool operator==(const PosTileIdx& ti) const {return x == ti.x && y == ti.y;} + bool operator!=(const PosTileIdx& ti) const {return !operator==(ti);} +}; + +// structure to hold information about a 16x16 set of tiles: for each tile, whether it's been drawn yet +struct TileSet +{ + // each tile gets two bits: first is whether it's required, second is whether it's been drawn + std::bitset bits; + + size_t bitIdx(const PosTileIdx& ti) const {return (TTGETLEVEL1(ti.y) * TTLEVEL1SIZE + TTGETLEVEL1(ti.x)) * TTDATASIZE;} + + // assumes that ti actually belongs to this set + bool isRequired(const PosTileIdx& ti) const {return bits[bitIdx(ti)];} + + // set tile's required bit and return previous state of bit + bool setRequired(const PosTileIdx& ti) {size_t bi = bitIdx(ti); bool rv = bits[bi]; bits.set(bi); return rv;} + void setDrawn(const PosTileIdx& ti) {bits.set(bitIdx(ti)+1);} +}; + +// first level of indirection: information about a 256x256 set of tiles +struct TileGroup +{ + // pointers to TileSets with the data, or NULL for 16x16 sets that aren't used + TileSet *tilesets[TTLEVEL2SIZE*TTLEVEL2SIZE]; + + // number of tiles in this group that have been set to required + int64_t reqcount; + + TileGroup() : reqcount(0) {for (int i = 0; i < TTLEVEL2SIZE*TTLEVEL2SIZE; i++) tilesets[i] = NULL;} + ~TileGroup() {for (int i = 0; i < TTLEVEL2SIZE*TTLEVEL2SIZE; i++) if (tilesets[i] != NULL) delete tilesets[i];} + + int tileSetIdx(const PosTileIdx& ti) const {return TTGETLEVEL2(ti.y) * TTLEVEL2SIZE + TTGETLEVEL2(ti.x);} + TileSet* getTileSet(const PosTileIdx& ti) const {return tilesets[tileSetIdx(ti)];} + + bool setRequired(const PosTileIdx& ti); // set tile's required bit and return previous state of bit + void setDrawn(const PosTileIdx& ti); +}; + +// second (and final) level of indirection: a 65536x65536 set of tiles +struct TileTable : private nocopy +{ + TileGroup *tilegroups[TTLEVEL3SIZE*TTLEVEL3SIZE]; + + int64_t reqcount; + + TileTable() : reqcount(0) {for (int i = 0; i < TTLEVEL3SIZE*TTLEVEL3SIZE; i++) tilegroups[i] = NULL;} + ~TileTable() {for (int i = 0; i < TTLEVEL3SIZE*TTLEVEL3SIZE; i++) if (tilegroups[i] != NULL) delete tilegroups[i];} + + int tileGroupIdx(const PosTileIdx& ti) const {return TTGETLEVEL3(ti.y) * TTLEVEL3SIZE + TTGETLEVEL3(ti.x);} + TileGroup* getTileGroup(const PosTileIdx& ti) const {return tilegroups[tileGroupIdx(ti)];} + TileSet* getTileSet(const PosTileIdx& ti) const {TileGroup *tg = getTileGroup(ti); return (tg == NULL) ? NULL : tg->getTileSet(ti);} + + // given indices into the TileGroups/TileSets/bitset, construct a PosTileIdx + static PosTileIdx toPosTileIdx(int tgi, int tsi, int bi); + + bool isRequired(const PosTileIdx& ti) const {TileSet *ts = getTileSet(ti); return (ts == NULL) ? false : ts->bits[ts->bitIdx(ti)];} + bool isDrawn(const PosTileIdx& ti) const {TileSet *ts = getTileSet(ti); return (ts == NULL) ? false : ts->bits[ts->bitIdx(ti)+1];} + + bool setRequired(const PosTileIdx& ti); // set tile's required bit and return previous state of bit + void setDrawn(const PosTileIdx& ti); + + // see if an entire zoom tile can be rejected because its TileGroup or TileSet is NULL + bool reject(const ZoomTileIdx& zti, const MapParams& mp) const; + + // get the total number of base tiles required to draw a zoom tile + int64_t getNumRequired(const ZoomTileIdx& zti, const MapParams& mp) const; + + void copyFrom(const TileTable& ttable); +}; + + + +// given a TileTable, iterates over the required tiles +struct RequiredTileIterator +{ + bool end; // true once we've reached the end + PosTileIdx current; // if end == false, holds the current tile + + TileTable& tiletable; + // these guys are Z-order indices and must be converted to row-major when accessing the TileTable + int ztgi, ztsi, zbi; + + // constructor initializes us to the first required tile + RequiredTileIterator(TileTable& ttable); + + // move in Z-order to the next required tile, or to the end + void advance(); +}; + +// given a TileTable, iterates over the non-NULL TileGroups +struct TileGroupIterator +{ + bool end; // true once we've reached the end + int tgi; // if end == false, holds the current index into TileTable::tilegroups + ZoomTileIdx zti; // if end == false, holds the zoom tile corresponding to the current TileGroup + + TileTable& tiletable; + MapParams mp; + + // constructor initializes to first non-NULL TileGroup + TileGroupIterator(TileTable& ttable, const MapParams& mparams); + + // move to the next non-NULL TileGroup, or to the end + void advance(); +}; + + + + +#define RTDATASIZE 3 + +#define RTLEVEL1BITS 4 +#define RTLEVEL2BITS 4 +#define RTLEVEL3BITS 6 + +#define RTLEVEL1SIZE (1 << RTLEVEL1BITS) +#define RTLEVEL2SIZE (1 << RTLEVEL2BITS) +#define RTLEVEL3SIZE (1 << RTLEVEL3BITS) +#define RTTOTALSIZE (RTLEVEL1SIZE * RTLEVEL2SIZE * RTLEVEL3SIZE) + +#define RTLEVEL1MASK (RTLEVEL1SIZE - 1) +#define RTLEVEL2MASK ((RTLEVEL2SIZE - 1) << RTLEVEL1BITS) +#define RTLEVEL3MASK (((RTLEVEL3SIZE - 1) << RTLEVEL1BITS) << RTLEVEL2BITS) + +#define RTGETLEVEL1(a) (a & RTLEVEL1MASK) +#define RTGETLEVEL2(a) ((a & RTLEVEL2MASK) >> RTLEVEL1BITS) +#define RTGETLEVEL3(a) (((a & RTLEVEL3MASK) >> RTLEVEL2BITS) >> RTLEVEL1BITS) + +struct PosRegionIdx +{ + int64_t x, z; + + PosRegionIdx(int64_t xx, int64_t zz) : x(xx), z(zz) {} + PosRegionIdx(const RegionIdx& ri) : x(ri.x + RTTOTALSIZE/2), z(ri.z + RTTOTALSIZE/2) {} + RegionIdx toRegionIdx() const {return RegionIdx(x - RTTOTALSIZE/2, z - RTTOTALSIZE/2);} + bool valid() const {return x >= 0 && x < RTTOTALSIZE && z >= 0 && z < RTTOTALSIZE;} + + bool operator==(const PosRegionIdx& ri) const {return x == ri.x && z == ri.z;} + bool operator!=(const PosRegionIdx& ri) const {return !operator==(ri);} +}; + +struct RegionSet +{ + // each region gets 3 bits: + // -first bit is 1 for required (must be drawn), 0 for not required + // -last two bits describe state of region on disk: + // 00: have not tried to find region on disk yet + // 01: have successfully read region from disk (i.e. it should be in the cache, if we still need it) + // 10: region does not exist on disk + // 11: region file is corrupted + static const int REGION_UNKNOWN = 0; + static const int REGION_CACHED = 1; + static const int REGION_MISSING = 2; + static const int REGION_CORRUPTED = 3; + std::bitset bits; + + size_t bitIdx(const PosRegionIdx& ri) const {return (RTGETLEVEL1(ri.z) * RTLEVEL1SIZE + RTGETLEVEL1(ri.x)) * RTDATASIZE;} + + void setRequired(const PosRegionIdx& ri) {bits.set(bitIdx(ri));} + void setDiskState(const PosRegionIdx& ri, int state) {size_t bi = bitIdx(ri); bits[bi+1] = state & 0x2; bits[bi+2] = state & 0x1;} +}; + +struct RegionGroup +{ + RegionSet *regionsets[RTLEVEL2SIZE*RTLEVEL2SIZE]; + + RegionGroup() {for (int i = 0; i < RTLEVEL2SIZE*RTLEVEL2SIZE; i++) regionsets[i] = NULL;} + ~RegionGroup() {for (int i = 0; i < RTLEVEL2SIZE*RTLEVEL2SIZE; i++) if (regionsets[i] != NULL) delete regionsets[i];} + + int regionSetIdx(const PosRegionIdx& ri) const {return RTGETLEVEL2(ri.z) * RTLEVEL2SIZE + RTGETLEVEL2(ri.x);} + RegionSet* getRegionSet(const PosRegionIdx& ri) const {return regionsets[regionSetIdx(ri)];} + + void setRequired(const PosRegionIdx& ri); + void setDiskState(const PosRegionIdx& ri, int state); +}; + +struct RegionTable : private nocopy +{ + RegionGroup *regiongroups[RTLEVEL3SIZE*RTLEVEL3SIZE]; + + RegionTable() {for (int i = 0; i < RTLEVEL3SIZE*RTLEVEL3SIZE; i++) regiongroups[i] = NULL;} + ~RegionTable() {for (int i = 0; i < RTLEVEL3SIZE*RTLEVEL3SIZE; i++) if (regiongroups[i] != NULL) delete regiongroups[i];} + + int regionGroupIdx(const PosRegionIdx& ri) const {return RTGETLEVEL3(ri.z) * RTLEVEL3SIZE + RTGETLEVEL3(ri.x);} + RegionGroup* getRegionGroup(const PosRegionIdx& ri) const {return regiongroups[regionGroupIdx(ri)];} + RegionSet* getRegionSet(const PosRegionIdx& ri) const {RegionGroup *rg = getRegionGroup(ri); return (rg == NULL) ? NULL : rg->getRegionSet(ri);} + + // given indices into the RegionGroups/RegionSets/bitset, construct a PosRegionIdx + static PosRegionIdx toPosRegionIdx(int rgi, int rsi, int bi); + + bool isRequired(const PosRegionIdx& ri) const {RegionSet *rs = getRegionSet(ri); return (rs == NULL) ? false : rs->bits[rs->bitIdx(ri)];} + 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));} + + void setRequired(const PosRegionIdx& ri); + void setDiskState(const PosRegionIdx& ri, int state); + + void copyFrom(const RegionTable& rtable); +}; + + + +#endif // TABLES_H diff --git a/template.html b/template.html old mode 100755 new mode 100644 index d22c094..9ac0940 --- a/template.html +++ b/template.html @@ -1,183 +1,183 @@ - - - - - - - + - - -
- - + + + // our custom projection maps Latitude to Y, and Longitude to X as normal, + // but it maps the range [0.0, 1.0] to [0, tileSize] in both directions + // so it is easier to position markers, etc. based on their position + // (find their position in the lowest-zoom image, and divide by tileSize) + function MCMapProjection() { + this.inverseTileSize = 1.0 / config.tileSize; + } + + MCMapProjection.prototype.fromLatLngToPoint = function(latLng) { + var x = latLng.lng() * config.tileSize; + var y = latLng.lat() * config.tileSize; + return new google.maps.Point(x, y); + }; + + MCMapProjection.prototype.fromPointToLatLng = function(point) { + var lng = point.x * this.inverseTileSize; + var lat = point.y * this.inverseTileSize; + return new google.maps.LatLng(lat, lng); + }; + + // pigmap lat/long converter + // this function takes its arguments in the *same order* as the previous + // Overviewer version--minecraft X, minecraft Y, minecraft Z--so callers + // do not need to be changed + // ...however, this one does not rename the variables, so what we call "y" + // here is also called "y" in both minecraft and pigmap + // (the pigmap docs write coords in X,Z,Y order, so unfortunately + // confusion is still possible, but at least the *names* are the same) + function fromWorldToLatLng(x, y, z) + { + // the width and height of all the highest-zoom tiles combined, inverted + var perPixel = 1.0 / (config.tileSize * Math.pow(2, config.maxZoom)); + + var B = config.B; + var T = config.T; + + // fail in a conspicuous way if tileSize doesn't match B and T + if (config.tileSize != 64*B*T) { + console.log("Tile size does not match 64*B*T"); + return new google.maps.LatLng(0.5, 0.5); + } + // the center of block [0,0,0] is at [2B, 64BT-17B] in the tile [tiles/2, tiles/2] + var lng = 0.5 + 2*B * perPixel; + var lat = 0.5 + (config.tileSize - 17*B) * perPixel; + + // each block on X adds [2B,-B] + lng += 2*B * x * perPixel; + lat += -B * x * perPixel; + + // each block on Y adds [0,-2B] + lat += -2*B * y * perPixel; + + // each block on Z adds [2B,B] + lng += 2*B * z * perPixel; + lat += B * z * perPixel; + + return new google.maps.LatLng(lat, lng); + } + + var MCMapOptions = { + getTileUrl: function(tile, zoom) { + var url = config.path; + if(tile.x < 0 || tile.x >= Math.pow(2, zoom) || tile.y < 0 || tile.y >= Math.pow(2, zoom)) { + url += '/blank'; + } else if(zoom == 0) { + url += '/base'; + } else { + for(var z = zoom - 1; z >= 0; --z) { + var x = Math.floor(tile.x / Math.pow(2, z)) % 2; + var y = Math.floor(tile.y / Math.pow(2, z)) % 2; + url += '/' + (x + 2 * y); + } + } + url = url + '.' + config.fileExt; + return(url); + }, + tileSize: new google.maps.Size(config.tileSize, config.tileSize), + maxZoom: config.maxZoom, + minZoom: 0, + isPng: !(config.fileExt.match(/^png$/i) == null) + }; + + var MCMapType = new google.maps.ImageMapType(MCMapOptions); + MCMapType.name = "MC Map"; + MCMapType.alt = "Minecraft Map"; + MCMapType.projection = new MCMapProjection(); + + function CoordMapType() { + } + + function CoordMapType(tileSize) { + this.tileSize = tileSize; + } + + CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) { + var div = ownerDocument.createElement('DIV'); + div.innerHTML = "(" + coord.x + ", " + coord.y + ", " + zoom + ")"; + div.innerHTML += "
"; + div.innerHTML += MCMapOptions.getTileUrl(coord, zoom); + div.style.width = this.tileSize.width + 'px'; + div.style.height = this.tileSize.height + 'px'; + div.style.fontSize = '10'; + div.style.borderStyle = 'solid'; + div.style.borderWidth = '1px'; + div.style.borderColor = '#AAAAAA'; + return div; + }; + + var map; + + var markersInit = false; + + function initMarkers() { + if (markersInit) { return; } + + markersInit = true; + + for (i in markerData) { + var item = markerData[i]; + + var converted = fromWorldToLatLng(item.x, item.y, item.z); + var marker = new google.maps.Marker({ + position: converted, + map: map, + title: item.msg, + + }); + + } + } + + function initialize() { + var mapOptions = { + zoom: config.defaultZoom, + center: new google.maps.LatLng(0.5, 0.5), + navigationControl: true, + scaleControl: false, + mapTypeControl: false, + streetViewControl: false, + mapTypeId: 'mcmap' + }; + map = new google.maps.Map(document.getElementById("mcmap"), mapOptions); + + + // Now attach the coordinate map type to the map's registry + map.mapTypes.set('mcmap', MCMapType); + + // We can now set the map to use the 'coordinate' map type + map.setMapTypeId('mcmap'); + + initMarkers(); + + } + + + +
+ + diff --git a/textures/blocks/lava.png b/textures/blocks/lava.png new file mode 100644 index 0000000..24cff99 Binary files /dev/null and b/textures/blocks/lava.png differ diff --git a/utils.cpp b/utils.cpp old mode 100755 new mode 100644 index 414744f..3cbe4af --- a/utils.cpp +++ b/utils.cpp @@ -1,487 +1,487 @@ -// Copyright 2010, 2011 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "utils.h" - -#if USE_MALLINFO -#include -#endif - -using namespace std; - - -//!!!!!!! do this more carefully, like actually checking return values, etc. -void makePath(const string& path) -{ - if (path.empty()) - return; - string::size_type pos = path.find('/'); - mkdir(path.substr(0, pos).c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); - while (pos != string::npos) - { - pos = path.find('/', pos+1); - mkdir(path.substr(0, pos).c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); - } -} - -//!!!!!! same here -void renameFile(const string& oldpath, const string& newpath) -{ - if (oldpath.empty() || newpath.empty()) - return; - rename(oldpath.c_str(), newpath.c_str()); -} - -void copyFile(const string& oldpath, const string& newpath) -{ - ifstream infile(oldpath.c_str()); - ofstream outfile(newpath.c_str()); - outfile << infile.rdbuf(); -} - -bool readLines(const string& filename, vector& lines) -{ - ifstream infile(filename.c_str()); - if (infile.fail()) - return false; - string line; - while (!getline(infile, line).eof()) - lines.push_back(line); - return true; -} - - -void listEntries(const string& dirpath, vector& entries) -{ - DIR *dir = opendir(dirpath.c_str()); - if (dir == NULL) - return; - dirent *de = readdir(dir); - while (de != NULL) - { - string e(de->d_name); - if (e != "." && e != "..") - entries.push_back(dirpath + "/" + e); - de = readdir(dir); - } - closedir(dir); -} - -bool dirExists(const string& dirpath) -{ - DIR *dir = opendir(dirpath.c_str()); - if (dir == NULL) - return false; - closedir(dir); - return true; -} - -uint64_t getHeapUsage() -{ -#if USE_MALLINFO - struct mallinfo minfo = mallinfo(); - return minfo.uordblks + minfo.hblkhd; -#else - return 0; -#endif -} - - -struct gzCloser -{ - gzFile gzfile; - gzCloser(gzFile gzf) : gzfile(gzf) {} - ~gzCloser() {gzclose(gzfile);} -}; - -int readGzFile(const string& filename, vector& data) -{ - gzFile gzf = gzopen(filename.c_str(), "rb"); - if (gzf == NULL) - { - if (errno == ENOENT) - return -1; - return -2; - } - gzCloser gc(gzf); - // start by resizing vector to entire capacity; we'll shrink back down to the - // proper size later - data.resize(data.capacity()); - if (data.empty()) - { - data.resize(131072); - data.resize(data.capacity()); // just in case extra space was allocated - } - // read as much as we can - vector::size_type pos = 0; - unsigned requestSize = data.size() - pos; // this is plain old unsigned to match the zlib call - int bytesRead = gzread(gzf, &data[pos], requestSize); - if (bytesRead == -1) - return -2; - pos += bytesRead; - while (bytesRead == requestSize) - { - // if there's still more, reallocate and read more - data.resize(data.size() * 2); - data.resize(data.capacity()); // just in case extra space was allocated - requestSize = data.size() - pos; - bytesRead = gzread(gzf, &data[pos], requestSize); - if (bytesRead == -1) - return -2; - pos += bytesRead; - } - // resize buffer back down to the end of the actual data - data.resize(pos); - return 0; -} - -struct inflateEnder -{ - z_stream *zstr; - inflateEnder(z_stream *zs) : zstr(zs) {} - ~inflateEnder() {inflateEnd(zstr);} -}; - -bool readGzOrZlib(uint8_t *inbuf, size_t size, vector& data) -{ - // start by resizing vector to entire capacity; we'll shrink back down to the - // proper size later - data.resize(data.capacity()); - if (data.empty()) - { - data.resize(131072); - data.resize(data.capacity()); // just in case extra space was allocated - } - // initialize zlib stream - z_stream zstr; - zstr.next_in = inbuf; - zstr.avail_in = size; - zstr.next_out = &(data[0]); - zstr.avail_out = data.size(); - zstr.zalloc = Z_NULL; - zstr.zfree = Z_NULL; - int result = inflateInit2(&zstr, 15 + 32); // adding 32 to window size means "detect both gzip and zlib" - if (result != Z_OK) - return false; - inflateEnder ie(&zstr); - // read as much as we can - result = inflate(&zstr, Z_SYNC_FLUSH); - while (result != Z_STREAM_END) - { - // if we failed for some reason other than not having enough room to read into, abort - if (result != Z_OK) - return false; - // reallocate and read more - ptrdiff_t diff = zstr.next_out - &(data[0]); - size_t addedsize = data.size(); - data.resize(data.size() + addedsize); - data.resize(data.capacity()); // just in case more was allocated - zstr.next_out = &(data[0]) + diff; - zstr.avail_out += addedsize; - result = inflate(&zstr, Z_SYNC_FLUSH); - } - // resize buffer back down to end of the actual data - data.resize(zstr.total_out); - return true; -} - - - -uint32_t fromBigEndian(uint32_t i) -{ - uint8_t *b = (uint8_t*)(&i); - return (*b << 24) | (*(b+1) << 16) | (*(b+2) << 8) | (*(b+3)); -} - -uint16_t fromBigEndian(uint16_t i) -{ - uint8_t *b = (uint8_t*)(&i); - return (*b << 8) | (*(b+1)); -} - -bool isBigEndian() -{ - uint32_t i = 0xff000000; - uint8_t *b = (uint8_t*)(&i); - return *b == 0xff; -} - -void swapEndian(uint32_t& i) -{ - uint8_t *b = (uint8_t*)(&i); - swap(b[0], b[3]); - swap(b[1], b[2]); -} - - - -int64_t floordiv(int64_t a, int64_t b) -{ - if (b < 0) - { - a = -a; - b = -b; - } - if (a < 0) - return (a - b + 1) / b; - return a / b; -} - -int64_t ceildiv(int64_t a, int64_t b) -{ - if (b < 0) - { - a = -a; - b = -b; - } - if (a > 0) - return (a + b - 1) / b; - return a / b; -} - -int64_t mod64pos(int64_t a) -{ - if (a >= 0) - return a % 64; - int64_t m = a % 64; - return (m == 0) ? 0 : (64 + m); -} - -int64_t interpolate(int64_t i, int64_t destrange, int64_t srcrange) -{ - double f = (double)i / (double)(destrange - 1); - f = f * (double)(srcrange - 1); - int64_t j = (int64_t)f; - return (f - (double)j >= 0.5) ? j+1 : j; -} - - - -// technically, these use "upside-down-N-order", not Z-order--that is, the Y-coord is incremented -// first, not the X-coord--because that way, no special way to detect the end of the array is -// needed; advancing past the final valid element leads to the index one past the end of the -// array, as usual - -uint32_t toZOrder(uint32_t i, const uint32_t SIZE) -{ - // get x and y coords - uint32_t x = i % SIZE, y = i / SIZE; - // interleave bits; this (public domain) code taken from Sean Eron Anderson's website - // ...this assumes that x and y are <= 0xffff; this is safe because if they weren't, - // SIZE would have to be > 0x10000, so 32 bits wouldn't have been enough to hold an - // index into a SIZExSIZE array - x = (x | (x << 8)) & 0xff00ff; - x = (x | (x << 4)) & 0xf0f0f0f; - x = (x | (x << 2)) & 0x33333333; - x = (x | (x << 1)) & 0x55555555; - y = (y | (y << 8)) & 0xff00ff; - y = (y | (y << 4)) & 0xf0f0f0f; - y = (y | (y << 2)) & 0x33333333; - y = (y | (y << 1)) & 0x55555555; - return (x << 1) | y; -} - -uint32_t fromZOrder(uint32_t i, const uint32_t SIZE) -{ - // de-interleave - uint32_t x = (i >> 1) & 0x55555555; - x = (x | (x >> 1)) & 0x33333333; - x = (x | (x >> 2)) & 0xf0f0f0f; - x = (x | (x >> 4)) & 0xff00ff; - x = (x | (x >> 8)) & 0xffff; - uint32_t y = i & 0x55555555; - y = (y | (y >> 1)) & 0x33333333; - y = (y | (y >> 2)) & 0xf0f0f0f; - y = (y | (y >> 4)) & 0xff00ff; - y = (y | (y >> 8)) & 0xffff; - // convert to row-major - return y*SIZE + x; - -} - - - -bool fromBase36(const string& s, string::size_type pos, string::size_type n, int64_t& result) -{ - if (s.empty()) - return false; - if (n == string::npos) - n = s.size(); - string::size_type i = pos; - int64_t sign = 1; - if (s[i] == '-') - { - sign = -1; - i++; - } - int64_t total = 0; - while (i != pos + n) - { - total *= 36; - if (s[i] >= '0' && s[i] <= '9') - total += s[i] - '0'; - else if (s[i] >= 'a' && s[i] <= 'z') - total += s[i] - 'a' + 10; - else if (s[i] >= 'A' && s[i] <= 'Z') - total += s[i] - 'A' + 10; - else - return false; - i++; - } - result = total * sign; - return true; -} - -int64_t fromBase36(const string& s) -{ - int64_t result; - if (fromBase36(s, 0, string::npos, result)) - return result; - return 0; -} - -string toBase36(int64_t i) -{ - bool neg = false; - if (i < 0) - { - neg = true; - i = -i; - } - string s; - while (i > 0) - { - int64_t d = i % 36; - if (d < 10) - s += ('0' + d); - else - s += ('a' + d - 10); - i /= 36; - } - if (s.empty()) - return "0"; - if (neg) - s += '-'; - reverse(s.begin(), s.end()); - return s; -} - - -string tostring(int i) -{ - ostringstream ss; - ss << i; - return ss.str(); -} - -string tostring(int64_t i) -{ - ostringstream ss; - ss << i; - return ss.str(); -} - -bool fromstring(const string& s, int64_t& result) -{ - istringstream ss(s); - ss >> result; - return !ss.fail(); -} - -bool fromstring(const string& s, int& result) -{ - istringstream ss(s); - ss >> result; - return !ss.fail(); -} - -bool replace(string& text, const string& oldstr, const string& newstr) -{ - string::size_type pos = text.find(oldstr); - if (pos == string::npos) - return false; - while (pos != string::npos) - { - text.replace(pos, oldstr.size(), newstr); - pos = text.find(oldstr, pos + 1); - } - return true; -} - -vector tokenize(const string& instr, char separator) -{ - vector tokens; - istringstream stream(instr); - string token; - while (getline(stream, token, separator)) - tokens.push_back(token); - return tokens; -} - - - -pair schedule(const vector& costs, vector& assignments, int threads) -{ - // simple scheduler: go through the costs in descending order, assigning - // each to the thread with the lowest cost so far - - // sort costs - vector > sortedcosts; // first is cost, second is index in original vector - for (int i = 0; i < costs.size(); i++) - sortedcosts.push_back(make_pair(costs[i], i)); - sort(sortedcosts.begin(), sortedcosts.end(), greater >()); - - vector totals(threads, 0); - assignments.resize(costs.size(), -1); - - // go through sorted costs - int next = 0; - for (vector >::const_iterator it = sortedcosts.begin(); it != sortedcosts.end(); it++) - { - // assign to waiting (min-cost) thread - assignments[it->second] = next; - totals[next] += it->first; - // find the new min-cost thread - for (int i = 0; i < threads; i++) - if (totals[i] < totals[next]) - next = i; - } - - // compute error fraction - int mintotal = totals[0], maxtotal = totals[0]; - for (int i = 1; i < threads; i++) - { - if (totals[i] < mintotal) - mintotal = totals[i]; - else if (totals[i] > maxtotal) - maxtotal = totals[i]; - } - return make_pair(maxtotal - mintotal, (double)(maxtotal - mintotal) / (double)maxtotal); -} - - - +// Copyright 2010, 2011 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +#if USE_MALLINFO +#include +#endif + +using namespace std; + + +//!!!!!!! do this more carefully, like actually checking return values, etc. +void makePath(const string& path) +{ + if (path.empty()) + return; + string::size_type pos = path.find('/'); + mkdir(path.substr(0, pos).c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + while (pos != string::npos) + { + pos = path.find('/', pos+1); + mkdir(path.substr(0, pos).c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + } +} + +//!!!!!! same here +void renameFile(const string& oldpath, const string& newpath) +{ + if (oldpath.empty() || newpath.empty()) + return; + rename(oldpath.c_str(), newpath.c_str()); +} + +void copyFile(const string& oldpath, const string& newpath) +{ + ifstream infile(oldpath.c_str()); + ofstream outfile(newpath.c_str()); + outfile << infile.rdbuf(); +} + +bool readLines(const string& filename, vector& lines) +{ + ifstream infile(filename.c_str()); + if (infile.fail()) + return false; + string line; + while (!getline(infile, line).eof()) + lines.push_back(line); + return true; +} + + +void listEntries(const string& dirpath, vector& entries) +{ + DIR *dir = opendir(dirpath.c_str()); + if (dir == NULL) + return; + dirent *de = readdir(dir); + while (de != NULL) + { + string e(de->d_name); + if (e != "." && e != "..") + entries.push_back(dirpath + "/" + e); + de = readdir(dir); + } + closedir(dir); +} + +bool dirExists(const string& dirpath) +{ + DIR *dir = opendir(dirpath.c_str()); + if (dir == NULL) + return false; + closedir(dir); + return true; +} + +uint64_t getHeapUsage() +{ +#if USE_MALLINFO + struct mallinfo minfo = mallinfo(); + return minfo.uordblks + minfo.hblkhd; +#else + return 0; +#endif +} + + +struct gzCloser +{ + gzFile gzfile; + gzCloser(gzFile gzf) : gzfile(gzf) {} + ~gzCloser() {gzclose(gzfile);} +}; + +int readGzFile(const string& filename, vector& data) +{ + gzFile gzf = gzopen(filename.c_str(), "rb"); + if (gzf == NULL) + { + if (errno == ENOENT) + return -1; + return -2; + } + gzCloser gc(gzf); + // start by resizing vector to entire capacity; we'll shrink back down to the + // proper size later + data.resize(data.capacity()); + if (data.empty()) + { + data.resize(131072); + data.resize(data.capacity()); // just in case extra space was allocated + } + // read as much as we can + vector::size_type pos = 0; + unsigned requestSize = data.size() - pos; // this is plain old unsigned to match the zlib call + int bytesRead = gzread(gzf, &data[pos], requestSize); + if (bytesRead == -1) + return -2; + pos += bytesRead; + while ((uint) bytesRead == requestSize) + { + // if there's still more, reallocate and read more + data.resize(data.size() * 2); + data.resize(data.capacity()); // just in case extra space was allocated + requestSize = data.size() - pos; + bytesRead = gzread(gzf, &data[pos], requestSize); + if (bytesRead == -1) + return -2; + pos += bytesRead; + } + // resize buffer back down to the end of the actual data + data.resize(pos); + return 0; +} + +struct inflateEnder +{ + z_stream *zstr; + inflateEnder(z_stream *zs) : zstr(zs) {} + ~inflateEnder() {inflateEnd(zstr);} +}; + +bool readGzOrZlib(uint8_t *inbuf, size_t size, vector& data) +{ + // start by resizing vector to entire capacity; we'll shrink back down to the + // proper size later + data.resize(data.capacity()); + if (data.empty()) + { + data.resize(131072); + data.resize(data.capacity()); // just in case extra space was allocated + } + // initialize zlib stream + z_stream zstr; + zstr.next_in = inbuf; + zstr.avail_in = size; + zstr.next_out = &(data[0]); + zstr.avail_out = data.size(); + zstr.zalloc = Z_NULL; + zstr.zfree = Z_NULL; + int result = inflateInit2(&zstr, 15 + 32); // adding 32 to window size means "detect both gzip and zlib" + if (result != Z_OK) + return false; + inflateEnder ie(&zstr); + // read as much as we can + result = inflate(&zstr, Z_SYNC_FLUSH); + while (result != Z_STREAM_END) + { + // if we failed for some reason other than not having enough room to read into, abort + if (result != Z_OK) + return false; + // reallocate and read more + ptrdiff_t diff = zstr.next_out - &(data[0]); + size_t addedsize = data.size(); + data.resize(data.size() + addedsize); + data.resize(data.capacity()); // just in case more was allocated + zstr.next_out = &(data[0]) + diff; + zstr.avail_out += addedsize; + result = inflate(&zstr, Z_SYNC_FLUSH); + } + // resize buffer back down to end of the actual data + data.resize(zstr.total_out); + return true; +} + + + +uint32_t fromBigEndian(uint32_t i) +{ + uint8_t *b = (uint8_t*)(&i); + return (*b << 24) | (*(b+1) << 16) | (*(b+2) << 8) | (*(b+3)); +} + +uint16_t fromBigEndian(uint16_t i) +{ + uint8_t *b = (uint8_t*)(&i); + return (*b << 8) | (*(b+1)); +} + +bool isBigEndian() +{ + uint32_t i = 0xff000000; + uint8_t *b = (uint8_t*)(&i); + return *b == 0xff; +} + +void swapEndian(uint32_t& i) +{ + uint8_t *b = (uint8_t*)(&i); + swap(b[0], b[3]); + swap(b[1], b[2]); +} + + + +int64_t floordiv(int64_t a, int64_t b) +{ + if (b < 0) + { + a = -a; + b = -b; + } + if (a < 0) + return (a - b + 1) / b; + return a / b; +} + +int64_t ceildiv(int64_t a, int64_t b) +{ + if (b < 0) + { + a = -a; + b = -b; + } + if (a > 0) + return (a + b - 1) / b; + return a / b; +} + +int64_t mod64pos(int64_t a) +{ + if (a >= 0) + return a % 64; + int64_t m = a % 64; + return (m == 0) ? 0 : (64 + m); +} + +int64_t interpolate(int64_t i, int64_t destrange, int64_t srcrange) +{ + double f = (double)i / (double)(destrange - 1); + f = f * (double)(srcrange - 1); + int64_t j = (int64_t)f; + return (f - (double)j >= 0.5) ? j+1 : j; +} + + + +// technically, these use "upside-down-N-order", not Z-order--that is, the Y-coord is incremented +// first, not the X-coord--because that way, no special way to detect the end of the array is +// needed; advancing past the final valid element leads to the index one past the end of the +// array, as usual + +uint32_t toZOrder(uint32_t i, const uint32_t SIZE) +{ + // get x and y coords + uint32_t x = i % SIZE, y = i / SIZE; + // interleave bits; this (public domain) code taken from Sean Eron Anderson's website + // ...this assumes that x and y are <= 0xffff; this is safe because if they weren't, + // SIZE would have to be > 0x10000, so 32 bits wouldn't have been enough to hold an + // index into a SIZExSIZE array + x = (x | (x << 8)) & 0xff00ff; + x = (x | (x << 4)) & 0xf0f0f0f; + x = (x | (x << 2)) & 0x33333333; + x = (x | (x << 1)) & 0x55555555; + y = (y | (y << 8)) & 0xff00ff; + y = (y | (y << 4)) & 0xf0f0f0f; + y = (y | (y << 2)) & 0x33333333; + y = (y | (y << 1)) & 0x55555555; + return (x << 1) | y; +} + +uint32_t fromZOrder(uint32_t i, const uint32_t SIZE) +{ + // de-interleave + uint32_t x = (i >> 1) & 0x55555555; + x = (x | (x >> 1)) & 0x33333333; + x = (x | (x >> 2)) & 0xf0f0f0f; + x = (x | (x >> 4)) & 0xff00ff; + x = (x | (x >> 8)) & 0xffff; + uint32_t y = i & 0x55555555; + y = (y | (y >> 1)) & 0x33333333; + y = (y | (y >> 2)) & 0xf0f0f0f; + y = (y | (y >> 4)) & 0xff00ff; + y = (y | (y >> 8)) & 0xffff; + // convert to row-major + return y*SIZE + x; + +} + + + +bool fromBase36(const string& s, string::size_type pos, string::size_type n, int64_t& result) +{ + if (s.empty()) + return false; + if (n == string::npos) + n = s.size(); + string::size_type i = pos; + int64_t sign = 1; + if (s[i] == '-') + { + sign = -1; + i++; + } + int64_t total = 0; + while (i != pos + n) + { + total *= 36; + if (s[i] >= '0' && s[i] <= '9') + total += s[i] - '0'; + else if (s[i] >= 'a' && s[i] <= 'z') + total += s[i] - 'a' + 10; + else if (s[i] >= 'A' && s[i] <= 'Z') + total += s[i] - 'A' + 10; + else + return false; + i++; + } + result = total * sign; + return true; +} + +int64_t fromBase36(const string& s) +{ + int64_t result; + if (fromBase36(s, 0, string::npos, result)) + return result; + return 0; +} + +string toBase36(int64_t i) +{ + bool neg = false; + if (i < 0) + { + neg = true; + i = -i; + } + string s; + while (i > 0) + { + int64_t d = i % 36; + if (d < 10) + s += ('0' + d); + else + s += ('a' + d - 10); + i /= 36; + } + if (s.empty()) + return "0"; + if (neg) + s += '-'; + reverse(s.begin(), s.end()); + return s; +} + + +string tostring(int i) +{ + ostringstream ss; + ss << i; + return ss.str(); +} + +string tostring(int64_t i) +{ + ostringstream ss; + ss << i; + return ss.str(); +} + +bool fromstring(const string& s, int64_t& result) +{ + istringstream ss(s); + ss >> result; + return !ss.fail(); +} + +bool fromstring(const string& s, int& result) +{ + istringstream ss(s); + ss >> result; + return !ss.fail(); +} + +bool replace(string& text, const string& oldstr, const string& newstr) +{ + string::size_type pos = text.find(oldstr); + if (pos == string::npos) + return false; + while (pos != string::npos) + { + text.replace(pos, oldstr.size(), newstr); + pos = text.find(oldstr, pos + 1); + } + return true; +} + +vector tokenize(const string& instr, char separator) +{ + vector tokens; + istringstream stream(instr); + string token; + while (getline(stream, token, separator)) + tokens.push_back(token); + return tokens; +} + + + +pair schedule(const vector& costs, vector& assignments, int threads) +{ + // simple scheduler: go through the costs in descending order, assigning + // each to the thread with the lowest cost so far + + // sort costs + vector > sortedcosts; // first is cost, second is index in original vector + for (uint i = 0; i < costs.size(); i++) + sortedcosts.push_back(make_pair(costs[i], i)); + sort(sortedcosts.begin(), sortedcosts.end(), greater >()); + + vector totals(threads, 0); + assignments.resize(costs.size(), -1); + + // go through sorted costs + int next = 0; + for (vector >::const_iterator it = sortedcosts.begin(); it != sortedcosts.end(); it++) + { + // assign to waiting (min-cost) thread + assignments[it->second] = next; + totals[next] += it->first; + // find the new min-cost thread + for (int i = 0; i < threads; i++) + if (totals[i] < totals[next]) + next = i; + } + + // compute error fraction + int mintotal = totals[0], maxtotal = totals[0]; + for (int i = 1; i < threads; i++) + { + if (totals[i] < mintotal) + mintotal = totals[i]; + else if (totals[i] > maxtotal) + maxtotal = totals[i]; + } + return make_pair(maxtotal - mintotal, (double)(maxtotal - mintotal) / (double)maxtotal); +} + + + diff --git a/utils.h b/utils.h old mode 100755 new mode 100644 index dccdc19..3c0ef4a --- a/utils.h +++ b/utils.h @@ -1,145 +1,145 @@ -// Copyright 2010-2012 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#ifndef UTILS_H -#define UTILS_H - -#include -#include -#include - - -// ensure that a directory exists (create any missing directories on path) -void makePath(const std::string& path); - -void renameFile(const std::string& oldpath, const std::string& newpath); -void copyFile(const std::string& oldpath, const std::string& newpath); - -// read a text file and append each of its non-empty lines to a vector -bool readLines(const std::string& filename, std::vector& lines); - -// list names of entries in a directory, not including "." and ".." -// ...returns relative paths beginning with dirpath; appends results to vector -void listEntries(const std::string& dirpath, std::vector& entries); - -bool dirExists(const std::string& dirpath); - -// -read a gzipped file into a vector, overwriting its contents, and expanding it if necessary -// -return 0 on success, -1 for nonexistent file, -2 for other errors -int readGzFile(const std::string& filename, std::vector& data); - -// extract gzip- or zlib-compressed data into a vector, overwriting its contents, and -// expanding it if necessary -// (inbuf is not const only because zlib won't take const pointers for input) -bool readGzOrZlib(uint8_t* inbuf, size_t size, std::vector& data); - - -#define USE_MALLINFO 0 - -uint64_t getHeapUsage(); - - -// convert a big-endian int into whatever the current platform endianness is -uint32_t fromBigEndian(uint32_t i); -uint16_t fromBigEndian(uint16_t i); - -// detect whether the platform is big-endian -bool isBigEndian(); - -// switch endianness of an int -void swapEndian(uint32_t& i); - - -// floored division; real value of a/b is floored instead of truncated toward 0 -int64_t floordiv(int64_t a, int64_t b); -// ...same thing for ceiling -int64_t ceildiv(int64_t a, int64_t b); - -// positive remainder mod 64, for chunk subdirectories -int64_t mod64pos(int64_t a); - -// given i in [0,destrange), find j in [0,srcrange) -int64_t interpolate(int64_t i, int64_t destrange, int64_t srcrange); - - -// take a row-major index into a SIZExSIZE array and convert it to Z-order -uint32_t toZOrder(uint32_t i, const uint32_t SIZE); -// ...and vice versa -uint32_t fromZOrder(uint32_t i, const uint32_t SIZE); - - -bool fromBase36(const std::string& s, std::string::size_type pos, std::string::size_type n, int64_t& result); -int64_t fromBase36(const std::string& s); -std::string toBase36(int64_t i); - -std::string tostring(int i); -std::string tostring(int64_t i); -bool fromstring(const std::string& s, int64_t& result); -bool fromstring(const std::string& s, int& result); - - -// replace all occurrences of oldstr in text with newstr; return false if none found -bool replace(std::string& text, const std::string& oldstr, const std::string& newstr); - -std::vector tokenize(const std::string& instr, char separator); - - -// find an assignment of costs to threads that attempts to minimize the difference -// between the min and max total thread costs; return the difference by itself, and -// also as a fraction of the max thread cost -std::pair schedule(const std::vector& costs, std::vector& assignments, int threads); - - -class nocopy -{ -protected: - nocopy() {} - ~nocopy() {} -private: - nocopy(const nocopy& n); - const nocopy& operator=(const nocopy& n); -}; - - -template struct arrayDeleter -{ - T *array; - arrayDeleter(T *a) : array(a) {} - ~arrayDeleter() {delete[] array;} -}; - - -template struct stackPusher -{ - std::vector& vec; - stackPusher(std::vector& v, const T& item) : vec(v) {vec.push_back(item);} - ~stackPusher() {vec.pop_back();} -}; - - -// fast version for dividing by 16 (important for BlockIdx::getChunkIdx, which is called very very frequently) -inline int64_t floordiv16(int64_t a) -{ - // right-shifting a negative is undefined, so just do the division--the compiler will probably know - // whether it can use a shift anyway - if (a < 0) - return (a - 15) / 16; - return a >> 4; -} - - +// Copyright 2010-2012 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#ifndef UTILS_H +#define UTILS_H + +#include +#include +#include + + +// ensure that a directory exists (create any missing directories on path) +void makePath(const std::string& path); + +void renameFile(const std::string& oldpath, const std::string& newpath); +void copyFile(const std::string& oldpath, const std::string& newpath); + +// read a text file and append each of its non-empty lines to a vector +bool readLines(const std::string& filename, std::vector& lines); + +// list names of entries in a directory, not including "." and ".." +// ...returns relative paths beginning with dirpath; appends results to vector +void listEntries(const std::string& dirpath, std::vector& entries); + +bool dirExists(const std::string& dirpath); + +// -read a gzipped file into a vector, overwriting its contents, and expanding it if necessary +// -return 0 on success, -1 for nonexistent file, -2 for other errors +int readGzFile(const std::string& filename, std::vector& data); + +// extract gzip- or zlib-compressed data into a vector, overwriting its contents, and +// expanding it if necessary +// (inbuf is not const only because zlib won't take const pointers for input) +bool readGzOrZlib(uint8_t* inbuf, size_t size, std::vector& data); + + +#define USE_MALLINFO 0 + +uint64_t getHeapUsage(); + + +// convert a big-endian int into whatever the current platform endianness is +uint32_t fromBigEndian(uint32_t i); +uint16_t fromBigEndian(uint16_t i); + +// detect whether the platform is big-endian +bool isBigEndian(); + +// switch endianness of an int +void swapEndian(uint32_t& i); + + +// floored division; real value of a/b is floored instead of truncated toward 0 +int64_t floordiv(int64_t a, int64_t b); +// ...same thing for ceiling +int64_t ceildiv(int64_t a, int64_t b); + +// positive remainder mod 64, for chunk subdirectories +int64_t mod64pos(int64_t a); + +// given i in [0,destrange), find j in [0,srcrange) +int64_t interpolate(int64_t i, int64_t destrange, int64_t srcrange); + + +// take a row-major index into a SIZExSIZE array and convert it to Z-order +uint32_t toZOrder(uint32_t i, const uint32_t SIZE); +// ...and vice versa +uint32_t fromZOrder(uint32_t i, const uint32_t SIZE); + + +bool fromBase36(const std::string& s, std::string::size_type pos, std::string::size_type n, int64_t& result); +int64_t fromBase36(const std::string& s); +std::string toBase36(int64_t i); + +std::string tostring(int i); +std::string tostring(int64_t i); +bool fromstring(const std::string& s, int64_t& result); +bool fromstring(const std::string& s, int& result); + + +// replace all occurrences of oldstr in text with newstr; return false if none found +bool replace(std::string& text, const std::string& oldstr, const std::string& newstr); + +std::vector tokenize(const std::string& instr, char separator); + + +// find an assignment of costs to threads that attempts to minimize the difference +// between the min and max total thread costs; return the difference by itself, and +// also as a fraction of the max thread cost +std::pair schedule(const std::vector& costs, std::vector& assignments, int threads); + + +class nocopy +{ +protected: + nocopy() {} + ~nocopy() {} +private: + nocopy(const nocopy& n); + const nocopy& operator=(const nocopy& n); +}; + + +template struct arrayDeleter +{ + T *array; + arrayDeleter(T *a) : array(a) {} + ~arrayDeleter() {delete[] array;} +}; + + +template struct stackPusher +{ + std::vector& vec; + stackPusher(std::vector& v, const T& item) : vec(v) {vec.push_back(item);} + ~stackPusher() {vec.pop_back();} +}; + + +// fast version for dividing by 16 (important for BlockIdx::getChunkIdx, which is called very very frequently) +inline int64_t floordiv16(int64_t a) +{ + // right-shifting a negative is undefined, so just do the division--the compiler will probably know + // whether it can use a shift anyway + if (a < 0) + return (a - 15) / 16; + return a >> 4; +} + + #endif // UTILS_H \ No newline at end of file diff --git a/world.cpp b/world.cpp old mode 100755 new mode 100644 index d686368..fd0b1e5 --- a/world.cpp +++ b/world.cpp @@ -1,453 +1,453 @@ -// Copyright 2011 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#include -#include -#include - -#include "world.h" -#include "region.h" - -using namespace std; - - - - -bool detectRegionFormat(const string& inputdir) -{ - return dirExists(inputdir + "/region"); -} - - - - - -bool makeAllRegionsRequired(const string& topdir, ChunkTable& chunktable, TileTable& tiletable, RegionTable& regiontable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount, int64_t& reqregioncount) -{ - bool findBaseZoom = mp.baseZoom == -1; - // if finding the baseZoom, we'll just start from 0 and increase it whenever we hit a tile that's out of bounds - if (findBaseZoom) - mp.baseZoom = 0; - reqregioncount = 0; - // get all files in the region directory - RegionFileReader rfreader; - vector regionpaths; - listEntries(topdir + "/region", regionpaths); - for (vector::const_iterator it = regionpaths.begin(); it != regionpaths.end(); it++) - { - RegionIdx ri(0,0); - // if this is a proper region filename, use it - if (RegionIdx::fromFilePath(*it, ri)) - { - PosRegionIdx pri(ri); - if (!pri.valid()) - { - cerr << "ignoring extremely-distant region " << *it << " (world may be corrupt)" << endl; - continue; - } - // we might have found this region already, if the world data contains both .mca and .mcr files - if (regiontable.isRequired(pri)) - continue; - // get the chunks that currently exist in this region; if there aren't any, ignore it - vector chunks; - if (0 != rfreader.getContainedChunks(ri, string84(topdir), chunks)) - { - cerr << "can't open region " << *it << " to list chunks" << endl; - continue; - } - if (chunks.empty()) - continue; - // mark the region required - regiontable.setRequired(pri); - reqregioncount++; - // go through the contained chunks - for (vector::const_iterator chunk = chunks.begin(); chunk != chunks.end(); chunk++) - { - // mark the chunk required - PosChunkIdx pci(*chunk); - if (pci.valid()) - { - chunktable.setRequired(pci); - reqchunkcount++; - } - else - { - cerr << "ignoring extremely-distant chunk " << chunk->toFileName() << " (world may be corrupt)" << endl; - continue; - } - // get the tiles it touches and mark them required - vector tiles = chunk->getTiles(mp); - for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) - { - // first check if this tile fits in the TileTable, whose size is fixed - PosTileIdx pti(*tile); - if (pti.valid()) - tiletable.setRequired(pti); - else - { - cerr << "ignoring extremely-distant tile [" << tile->x << "," << tile->y << "]" << endl; - cerr << "(world may be corrupt; is region " << *it << " supposed to exist?)" << endl; - continue; - } - // now see if the tile fits on the Google map - if (!tile->valid(mp)) - { - // if we're supposed to be finding baseZoom, then bump it up until this tile fits - if (findBaseZoom) - { - while (!tile->valid(mp)) - mp.baseZoom++; - } - // otherwise, abort - else - { - cerr << "baseZoom too small! can't fit tile [" << tile->x << "," << tile->y << "]" << endl; - return false; - } - } - } - } - } - } - reqtilecount = tiletable.reqcount; - if (findBaseZoom) - cout << "baseZoom set to " << mp.baseZoom << endl; - return true; -} - -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) -{ - ifstream infile(regionlist.c_str()); - if (infile.fail()) - { - cerr << "couldn't open regionlist " << regionlist << endl; - return -2; - } - reqregioncount = 0; - RegionFileReader rfreader; - while (!infile.eof() && !infile.fail()) - { - string regionfile; - getline(infile, regionfile); - if (regionfile.empty()) - continue; - RegionIdx ri(0,0); - if (RegionIdx::fromFilePath(regionfile, ri)) - { - PosRegionIdx pri(ri); - if (!pri.valid()) - { - cerr << "ignoring extremely-distant region " << regionfile << " (world may be corrupt)" << endl; - continue; - } - if (regiontable.isRequired(pri)) - continue; - vector chunks; - if (0 != rfreader.getContainedChunks(ri, string84(inputdir), chunks)) - { - cerr << "can't open region " << regionfile << " to list chunks" << endl; - continue; - } - if (chunks.empty()) - continue; - regiontable.setRequired(pri); - reqregioncount++; - for (vector::const_iterator chunk = chunks.begin(); chunk != chunks.end(); chunk++) - { - PosChunkIdx pci(*chunk); - if (pci.valid()) - { - chunktable.setRequired(pci); - reqchunkcount++; - } - else - { - cerr << "ignoring extremely-distant chunk " << chunk->toFileName() << " (world may be corrupt)" << endl; - continue; - } - vector tiles = chunk->getTiles(mp); - for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) - { - PosTileIdx pti(*tile); - if (pti.valid()) - tiletable.setRequired(pti); - else - { - cerr << "ignoring extremely-distant tile [" << tile->x << "," << tile->y << "]" << endl; - cerr << "(world may be corrupt; is region " << regionfile << " supposed to exist?)" << endl; - continue; - } - if (!tile->valid(mp)) - { - cerr << "baseZoom too small! can't fit tile [" << tile->x << "," << tile->y << "]" << endl; - return -1; - } - } - } - } - } - reqtilecount = tiletable.reqcount; - return 0; -} - - - - - -const char *chunkdirs[64] = {"/0", "/1", "/2", "/3", "/4", "/5", "/6", "/7", "/8", "/9", "/a", "/b", "/c", "/d", "/e", "/f", - "/g", "/h", "/i", "/j", "/k", "/l", "/m", "/n", "/o", "/p", "/q", "/r", "/s", "/t", "/u", "/v", - "/w", "/x", "/y", "/z", "/10", "/11", "/12", "/13", "/14", "/15", "/16", "/17", "/18", "/19", "/1a", "/1b", - "/1c", "/1d", "/1e", "/1f", "/1g", "/1h", "/1i", "/1j", "/1k", "/1l", "/1m", "/1n", "/1o", "/1p", "/1q", "/1r",}; - -bool makeAllChunksRequired(const string& topdir, ChunkTable& chunktable, TileTable& tiletable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount) -{ - bool findBaseZoom = mp.baseZoom == -1; - // if finding the baseZoom, we'll just start from 0 and increase it whenever we hit a tile that's out of bounds - if (findBaseZoom) - mp.baseZoom = 0; - reqchunkcount = 0; - // go through each world subdirectory - for (int x = 0; x < 64; x++) - for (int z = 0; z < 64; z++) - { - // get all files in the subdirectory - vector chunkpaths; - string path = topdir + chunkdirs[x] + chunkdirs[z]; - listEntries(path, chunkpaths); - for (vector::const_iterator it = chunkpaths.begin(); it != chunkpaths.end(); it++) - { - ChunkIdx ci(0,0); - // if this is a proper chunk filename, use it - if (ChunkIdx::fromFilePath(*it, ci)) - { - // mark the chunk required - PosChunkIdx pci(ci); - if (pci.valid()) - { - chunktable.setRequired(pci); - reqchunkcount++; - } - else - { - cerr << "ignoring extremely-distant chunk " << ci.toFileName() << " (world may be corrupt)" << endl; - continue; - } - // get the tiles it touches and mark them required - vector tiles = ci.getTiles(mp); - for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) - { - // first check if this tile fits in the TileTable, whose size is fixed - PosTileIdx pti(*tile); - if (pti.valid()) - tiletable.setRequired(pti); - else - { - cerr << "ignoring extremely-distant tile [" << tile->x << "," << tile->y << "]" << endl; - cerr << "(world may be corrupt; is chunk " << ci.toFileName() << " supposed to exist?)" << endl; - continue; - } - // now see if the tile fits on the Google map - if (!tile->valid(mp)) - { - // if we're supposed to be finding baseZoom, then bump it up until this tile fits - if (findBaseZoom) - { - while (!tile->valid(mp)) - mp.baseZoom++; - } - // otherwise, abort - else - { - cerr << "baseZoom too small! can't fit tile [" << tile->x << "," << tile->y << "]" << endl; - return false; - } - } - } - } - } - } - reqtilecount = tiletable.reqcount; - if (findBaseZoom) - cout << "baseZoom set to " << mp.baseZoom << endl; - return true; -} - -int readChunklist(const string& chunklist, ChunkTable& chunktable, TileTable& tiletable, const MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount) -{ - ifstream infile(chunklist.c_str()); - if (infile.fail()) - { - cerr << "couldn't open chunklist " << chunklist << endl; - return -2; - } - reqchunkcount = 0; - while (!infile.eof() && !infile.fail()) - { - string chunkfile; - getline(infile, chunkfile); - if (chunkfile.empty()) - continue; - ChunkIdx ci(0,0); - if (ChunkIdx::fromFilePath(chunkfile, ci)) - { - PosChunkIdx pci(ci); - if (pci.valid()) - { - chunktable.setRequired(pci); - reqchunkcount++; - } - else - { - cerr << "ignoring extremely-distant chunk " << ci.toFileName() << " (world may be corrupt)" << endl; - continue; - } - vector tiles = ci.getTiles(mp); - for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) - { - PosTileIdx pti(*tile); - if (pti.valid()) - tiletable.setRequired(pti); - else - { - cerr << "ignoring extremely-distant tile [" << tile->x << "," << tile->y << "]" << endl; - cerr << "(world may be corrupt; is chunk " << ci.toFileName() << " supposed to exist?)" << endl; - continue; - } - if (!tile->valid(mp)) - { - cerr << "baseZoom too small! can't fit tile [" << tile->x << "," << tile->y << "]" << endl; - return -1; - } - } - } - } - reqtilecount = tiletable.reqcount; - return 0; -} - -void makeTestWorld(int size, ChunkTable& chunktable, TileTable& tiletable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount) -{ - bool findBaseZoom = mp.baseZoom == -1; - // if finding the baseZoom, we'll just start from 0 and increase it whenever we hit a tile that's out of bounds - if (findBaseZoom) - mp.baseZoom = 0; - reqchunkcount = 0; - // we'll start by putting 95% of the chunks in a solid block at the center - int size2 = (int)(sqrt((double)size * 0.95) / 2.0); - ChunkIdx ci(0,0); - for (ci.x = -size2; ci.x < size2; ci.x++) - for (ci.z = -size2; ci.z < size2; ci.z++) - { - chunktable.setRequired(ci); - reqchunkcount++; - vector tiles = ci.getTiles(mp); - for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) - { - tiletable.setRequired(*tile); - while (findBaseZoom && !tile->valid(mp)) - mp.baseZoom++; - } - } - // now add some circles of required chunks with radii up to four times the (minimum) radius of the - // center block - for (int m = 2; m <= 4; m++) - { - double rad = (double)size2 * (double)m; - for (double t = -3.14159; t < 3.14159; t += 0.002) - { - ChunkIdx ci((int)(cos(t) * rad), (int)(sin(t) * rad)); - chunktable.setRequired(ci); - reqchunkcount++; - vector tiles = ci.getTiles(mp); - for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) - { - tiletable.setRequired(*tile); - while (findBaseZoom && !tile->valid(mp)) - mp.baseZoom++; - } - } - } - // now add some spokes going from the center out to the circle - int irad = size2 * 4; - for (ci.x = 0, ci.z = -irad; ci.z < irad; ci.z++) - { - chunktable.setRequired(ci); - reqchunkcount++; - vector tiles = ci.getTiles(mp); - for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) - { - tiletable.setRequired(*tile); - while (findBaseZoom && !tile->valid(mp)) - mp.baseZoom++; - } - } - for (ci.x = -irad, ci.z = 0; ci.x < irad; ci.x++) - { - chunktable.setRequired(ci); - reqchunkcount++; - vector tiles = ci.getTiles(mp); - for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) - { - tiletable.setRequired(*tile); - while (findBaseZoom && !tile->valid(mp)) - mp.baseZoom++; - } - } - for (ci.x = -irad, ci.z = -irad; ci.z < irad; ci.x++, ci.z++) - { - chunktable.setRequired(ci); - reqchunkcount++; - vector tiles = ci.getTiles(mp); - for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) - { - tiletable.setRequired(*tile); - while (findBaseZoom && !tile->valid(mp)) - mp.baseZoom++; - } - } - for (ci.x = irad, ci.z = -irad; ci.z < irad; ci.x--, ci.z++) - { - chunktable.setRequired(ci); - reqchunkcount++; - vector tiles = ci.getTiles(mp); - for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) - { - tiletable.setRequired(*tile); - while (findBaseZoom && !tile->valid(mp)) - mp.baseZoom++; - } - } - reqtilecount = tiletable.reqcount; - if (findBaseZoom) - cout << "baseZoom set to " << mp.baseZoom << endl; -} - - - - - -// used only for testing -void findAllChunks(const string& topdir, vector& chunkpaths) -{ - for (int x = 0; x < 64; x++) - for (int z = 0; z < 64; z++) - { - string path = topdir + chunkdirs[x] + chunkdirs[z]; - listEntries(path, chunkpaths); - } -} - +// Copyright 2011 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#include +#include +#include + +#include "world.h" +#include "region.h" + +using namespace std; + + + + +bool detectRegionFormat(const string& inputdir) +{ + return dirExists(inputdir + "/region"); +} + + + + + +bool makeAllRegionsRequired(const string& topdir, ChunkTable& chunktable, TileTable& tiletable, RegionTable& regiontable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount, int64_t& reqregioncount) +{ + bool findBaseZoom = mp.baseZoom == -1; + // if finding the baseZoom, we'll just start from 0 and increase it whenever we hit a tile that's out of bounds + if (findBaseZoom) + mp.baseZoom = 0; + reqregioncount = 0; + // get all files in the region directory + RegionFileReader rfreader; + vector regionpaths; + listEntries(topdir + "/region", regionpaths); + for (vector::const_iterator it = regionpaths.begin(); it != regionpaths.end(); it++) + { + RegionIdx ri(0,0); + // if this is a proper region filename, use it + if (RegionIdx::fromFilePath(*it, ri)) + { + PosRegionIdx pri(ri); + if (!pri.valid()) + { + cerr << "ignoring extremely-distant region " << *it << " (world may be corrupt)" << endl; + continue; + } + // we might have found this region already, if the world data contains both .mca and .mcr files + if (regiontable.isRequired(pri)) + continue; + // get the chunks that currently exist in this region; if there aren't any, ignore it + vector chunks; + if (0 != rfreader.getContainedChunks(ri, string84(topdir), chunks)) + { + cerr << "can't open region " << *it << " to list chunks" << endl; + continue; + } + if (chunks.empty()) + continue; + // mark the region required + regiontable.setRequired(pri); + reqregioncount++; + // go through the contained chunks + for (vector::const_iterator chunk = chunks.begin(); chunk != chunks.end(); chunk++) + { + // mark the chunk required + PosChunkIdx pci(*chunk); + if (pci.valid()) + { + chunktable.setRequired(pci); + reqchunkcount++; + } + else + { + cerr << "ignoring extremely-distant chunk " << chunk->toFileName() << " (world may be corrupt)" << endl; + continue; + } + // get the tiles it touches and mark them required + vector tiles = chunk->getTiles(mp); + for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) + { + // first check if this tile fits in the TileTable, whose size is fixed + PosTileIdx pti(*tile); + if (pti.valid()) + tiletable.setRequired(pti); + else + { + cerr << "ignoring extremely-distant tile [" << tile->x << "," << tile->y << "]" << endl; + cerr << "(world may be corrupt; is region " << *it << " supposed to exist?)" << endl; + continue; + } + // now see if the tile fits on the Google map + if (!tile->valid(mp)) + { + // if we're supposed to be finding baseZoom, then bump it up until this tile fits + if (findBaseZoom) + { + while (!tile->valid(mp)) + mp.baseZoom++; + } + // otherwise, abort + else + { + cerr << "baseZoom too small! can't fit tile [" << tile->x << "," << tile->y << "]" << endl; + return false; + } + } + } + } + } + } + reqtilecount = tiletable.reqcount; + if (findBaseZoom) + cout << "baseZoom set to " << mp.baseZoom << endl; + return true; +} + +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) +{ + ifstream infile(regionlist.c_str()); + if (infile.fail()) + { + cerr << "couldn't open regionlist " << regionlist << endl; + return -2; + } + reqregioncount = 0; + RegionFileReader rfreader; + while (!infile.eof() && !infile.fail()) + { + string regionfile; + getline(infile, regionfile); + if (regionfile.empty()) + continue; + RegionIdx ri(0,0); + if (RegionIdx::fromFilePath(regionfile, ri)) + { + PosRegionIdx pri(ri); + if (!pri.valid()) + { + cerr << "ignoring extremely-distant region " << regionfile << " (world may be corrupt)" << endl; + continue; + } + if (regiontable.isRequired(pri)) + continue; + vector chunks; + if (0 != rfreader.getContainedChunks(ri, string84(inputdir), chunks)) + { + cerr << "can't open region " << regionfile << " to list chunks" << endl; + continue; + } + if (chunks.empty()) + continue; + regiontable.setRequired(pri); + reqregioncount++; + for (vector::const_iterator chunk = chunks.begin(); chunk != chunks.end(); chunk++) + { + PosChunkIdx pci(*chunk); + if (pci.valid()) + { + chunktable.setRequired(pci); + reqchunkcount++; + } + else + { + cerr << "ignoring extremely-distant chunk " << chunk->toFileName() << " (world may be corrupt)" << endl; + continue; + } + vector tiles = chunk->getTiles(mp); + for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) + { + PosTileIdx pti(*tile); + if (pti.valid()) + tiletable.setRequired(pti); + else + { + cerr << "ignoring extremely-distant tile [" << tile->x << "," << tile->y << "]" << endl; + cerr << "(world may be corrupt; is region " << regionfile << " supposed to exist?)" << endl; + continue; + } + if (!tile->valid(mp)) + { + cerr << "baseZoom too small! can't fit tile [" << tile->x << "," << tile->y << "]" << endl; + return -1; + } + } + } + } + } + reqtilecount = tiletable.reqcount; + return 0; +} + + + + + +const char *chunkdirs[64] = {"/0", "/1", "/2", "/3", "/4", "/5", "/6", "/7", "/8", "/9", "/a", "/b", "/c", "/d", "/e", "/f", + "/g", "/h", "/i", "/j", "/k", "/l", "/m", "/n", "/o", "/p", "/q", "/r", "/s", "/t", "/u", "/v", + "/w", "/x", "/y", "/z", "/10", "/11", "/12", "/13", "/14", "/15", "/16", "/17", "/18", "/19", "/1a", "/1b", + "/1c", "/1d", "/1e", "/1f", "/1g", "/1h", "/1i", "/1j", "/1k", "/1l", "/1m", "/1n", "/1o", "/1p", "/1q", "/1r",}; + +bool makeAllChunksRequired(const string& topdir, ChunkTable& chunktable, TileTable& tiletable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount) +{ + bool findBaseZoom = mp.baseZoom == -1; + // if finding the baseZoom, we'll just start from 0 and increase it whenever we hit a tile that's out of bounds + if (findBaseZoom) + mp.baseZoom = 0; + reqchunkcount = 0; + // go through each world subdirectory + for (int x = 0; x < 64; x++) + for (int z = 0; z < 64; z++) + { + // get all files in the subdirectory + vector chunkpaths; + string path = topdir + chunkdirs[x] + chunkdirs[z]; + listEntries(path, chunkpaths); + for (vector::const_iterator it = chunkpaths.begin(); it != chunkpaths.end(); it++) + { + ChunkIdx ci(0,0); + // if this is a proper chunk filename, use it + if (ChunkIdx::fromFilePath(*it, ci)) + { + // mark the chunk required + PosChunkIdx pci(ci); + if (pci.valid()) + { + chunktable.setRequired(pci); + reqchunkcount++; + } + else + { + cerr << "ignoring extremely-distant chunk " << ci.toFileName() << " (world may be corrupt)" << endl; + continue; + } + // get the tiles it touches and mark them required + vector tiles = ci.getTiles(mp); + for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) + { + // first check if this tile fits in the TileTable, whose size is fixed + PosTileIdx pti(*tile); + if (pti.valid()) + tiletable.setRequired(pti); + else + { + cerr << "ignoring extremely-distant tile [" << tile->x << "," << tile->y << "]" << endl; + cerr << "(world may be corrupt; is chunk " << ci.toFileName() << " supposed to exist?)" << endl; + continue; + } + // now see if the tile fits on the Google map + if (!tile->valid(mp)) + { + // if we're supposed to be finding baseZoom, then bump it up until this tile fits + if (findBaseZoom) + { + while (!tile->valid(mp)) + mp.baseZoom++; + } + // otherwise, abort + else + { + cerr << "baseZoom too small! can't fit tile [" << tile->x << "," << tile->y << "]" << endl; + return false; + } + } + } + } + } + } + reqtilecount = tiletable.reqcount; + if (findBaseZoom) + cout << "baseZoom set to " << mp.baseZoom << endl; + return true; +} + +int readChunklist(const string& chunklist, ChunkTable& chunktable, TileTable& tiletable, const MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount) +{ + ifstream infile(chunklist.c_str()); + if (infile.fail()) + { + cerr << "couldn't open chunklist " << chunklist << endl; + return -2; + } + reqchunkcount = 0; + while (!infile.eof() && !infile.fail()) + { + string chunkfile; + getline(infile, chunkfile); + if (chunkfile.empty()) + continue; + ChunkIdx ci(0,0); + if (ChunkIdx::fromFilePath(chunkfile, ci)) + { + PosChunkIdx pci(ci); + if (pci.valid()) + { + chunktable.setRequired(pci); + reqchunkcount++; + } + else + { + cerr << "ignoring extremely-distant chunk " << ci.toFileName() << " (world may be corrupt)" << endl; + continue; + } + vector tiles = ci.getTiles(mp); + for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) + { + PosTileIdx pti(*tile); + if (pti.valid()) + tiletable.setRequired(pti); + else + { + cerr << "ignoring extremely-distant tile [" << tile->x << "," << tile->y << "]" << endl; + cerr << "(world may be corrupt; is chunk " << ci.toFileName() << " supposed to exist?)" << endl; + continue; + } + if (!tile->valid(mp)) + { + cerr << "baseZoom too small! can't fit tile [" << tile->x << "," << tile->y << "]" << endl; + return -1; + } + } + } + } + reqtilecount = tiletable.reqcount; + return 0; +} + +void makeTestWorld(int size, ChunkTable& chunktable, TileTable& tiletable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount) +{ + bool findBaseZoom = mp.baseZoom == -1; + // if finding the baseZoom, we'll just start from 0 and increase it whenever we hit a tile that's out of bounds + if (findBaseZoom) + mp.baseZoom = 0; + reqchunkcount = 0; + // we'll start by putting 95% of the chunks in a solid block at the center + int size2 = (int)(sqrt((double)size * 0.95) / 2.0); + ChunkIdx ci(0,0); + for (ci.x = -size2; ci.x < size2; ci.x++) + for (ci.z = -size2; ci.z < size2; ci.z++) + { + chunktable.setRequired(ci); + reqchunkcount++; + vector tiles = ci.getTiles(mp); + for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) + { + tiletable.setRequired(*tile); + while (findBaseZoom && !tile->valid(mp)) + mp.baseZoom++; + } + } + // now add some circles of required chunks with radii up to four times the (minimum) radius of the + // center block + for (int m = 2; m <= 4; m++) + { + double rad = (double)size2 * (double)m; + for (double t = -3.14159; t < 3.14159; t += 0.002) + { + ChunkIdx ci((int)(cos(t) * rad), (int)(sin(t) * rad)); + chunktable.setRequired(ci); + reqchunkcount++; + vector tiles = ci.getTiles(mp); + for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) + { + tiletable.setRequired(*tile); + while (findBaseZoom && !tile->valid(mp)) + mp.baseZoom++; + } + } + } + // now add some spokes going from the center out to the circle + int irad = size2 * 4; + for (ci.x = 0, ci.z = -irad; ci.z < irad; ci.z++) + { + chunktable.setRequired(ci); + reqchunkcount++; + vector tiles = ci.getTiles(mp); + for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) + { + tiletable.setRequired(*tile); + while (findBaseZoom && !tile->valid(mp)) + mp.baseZoom++; + } + } + for (ci.x = -irad, ci.z = 0; ci.x < irad; ci.x++) + { + chunktable.setRequired(ci); + reqchunkcount++; + vector tiles = ci.getTiles(mp); + for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) + { + tiletable.setRequired(*tile); + while (findBaseZoom && !tile->valid(mp)) + mp.baseZoom++; + } + } + for (ci.x = -irad, ci.z = -irad; ci.z < irad; ci.x++, ci.z++) + { + chunktable.setRequired(ci); + reqchunkcount++; + vector tiles = ci.getTiles(mp); + for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) + { + tiletable.setRequired(*tile); + while (findBaseZoom && !tile->valid(mp)) + mp.baseZoom++; + } + } + for (ci.x = irad, ci.z = -irad; ci.z < irad; ci.x--, ci.z++) + { + chunktable.setRequired(ci); + reqchunkcount++; + vector tiles = ci.getTiles(mp); + for (vector::const_iterator tile = tiles.begin(); tile != tiles.end(); tile++) + { + tiletable.setRequired(*tile); + while (findBaseZoom && !tile->valid(mp)) + mp.baseZoom++; + } + } + reqtilecount = tiletable.reqcount; + if (findBaseZoom) + cout << "baseZoom set to " << mp.baseZoom << endl; +} + + + + + +// used only for testing +void findAllChunks(const string& topdir, vector& chunkpaths) +{ + for (int x = 0; x < 64; x++) + for (int z = 0; z < 64; z++) + { + string path = topdir + chunkdirs[x] + chunkdirs[z]; + listEntries(path, chunkpaths); + } +} + diff --git a/world.h b/world.h old mode 100755 new mode 100644 index 92c584c..628c5c8 --- a/world.h +++ b/world.h @@ -1,72 +1,72 @@ -// Copyright 2011 Michael J. Nelson -// -// This file is part of pigmap. -// -// pigmap is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// pigmap is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with pigmap. If not, see . - -#ifndef WORLD_H -#define WORLD_H - -#include - -#include "map.h" -#include "tables.h" - - -// see whether the input world is in region format -bool detectRegionFormat(const std::string& inputdir); - - -// find all regions on disk; set them to required in the RegionTable; set all chunks they contain to -// required in the ChunkTable; set all tiles touched by those chunks to required in the TileTable -// returns false if the world is too big to fit in one of the tables -// if mp.baseZoom is set to -1 coming in, then this function will set it to the smallest zoom -// that can fit everything -bool makeAllRegionsRequired(const std::string& inputdir, ChunkTable& chunktable, TileTable& tiletable, RegionTable& regiontable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount, int64_t& reqregioncount); - -// read a list of region filenames from a file; set the regions to required in the RegionTable; set the chunks they -// contain to required in the ChunkTable; set all tiles touched by those chunks to required in the TileTable -// the region filenames can be either old-style (".mcr") or Anvil (".mca"), but only the coordinates from the filename -// will be considered--when rendering is actually performed, Anvil regions will be preferred to old-style regions -// even if ".mcr" was used in this regionlist -// returns 0 on success, -1 if baseZoom is too small, -2 for other errors (can't read regionlist, world too big -// for our internal data structures, etc.) -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); - - -// find all chunks on disk, set them to required in the ChunkTable, and set all tiles they -// touch to required in the TileTable -// returns false if the world is too big to fit in one of the tables -// if mp.baseZoom is set to -1 coming in, then this function will set it to the smallest zoom -// that can fit everything -bool makeAllChunksRequired(const std::string& inputdir, ChunkTable& chunktable, TileTable& tiletable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount); - -// read a list of chunk filenames from a file and set the chunks to required in the ChunkTable, and set -// any tiles they touch to required in the TileTable -// returns 0 on success, -1 if baseZoom is too small, -2 for other errors (can't read chunklist, world too big -// for our internal data structures, etc.) -int readChunklist(const std::string& chunklist, ChunkTable& chunktable, TileTable& tiletable, const MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount); - - - - -// build a test world by making approximately size chunks required -// if mp.baseZoom is set to -1 coming in, it will be set to the smallest zoom that can fit everything -void makeTestWorld(int size, ChunkTable& chunktable, TileTable& tiletable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount); - -// get the filepaths of all chunks on disk (used only for testing) -void findAllChunks(const std::string& inputdir, std::vector& chunkpaths); - - +// Copyright 2011 Michael J. Nelson +// +// This file is part of pigmap. +// +// pigmap is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// pigmap is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with pigmap. If not, see . + +#ifndef WORLD_H +#define WORLD_H + +#include + +#include "map.h" +#include "tables.h" + + +// see whether the input world is in region format +bool detectRegionFormat(const std::string& inputdir); + + +// find all regions on disk; set them to required in the RegionTable; set all chunks they contain to +// required in the ChunkTable; set all tiles touched by those chunks to required in the TileTable +// returns false if the world is too big to fit in one of the tables +// if mp.baseZoom is set to -1 coming in, then this function will set it to the smallest zoom +// that can fit everything +bool makeAllRegionsRequired(const std::string& inputdir, ChunkTable& chunktable, TileTable& tiletable, RegionTable& regiontable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount, int64_t& reqregioncount); + +// read a list of region filenames from a file; set the regions to required in the RegionTable; set the chunks they +// contain to required in the ChunkTable; set all tiles touched by those chunks to required in the TileTable +// the region filenames can be either old-style (".mcr") or Anvil (".mca"), but only the coordinates from the filename +// will be considered--when rendering is actually performed, Anvil regions will be preferred to old-style regions +// even if ".mcr" was used in this regionlist +// returns 0 on success, -1 if baseZoom is too small, -2 for other errors (can't read regionlist, world too big +// for our internal data structures, etc.) +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); + + +// find all chunks on disk, set them to required in the ChunkTable, and set all tiles they +// touch to required in the TileTable +// returns false if the world is too big to fit in one of the tables +// if mp.baseZoom is set to -1 coming in, then this function will set it to the smallest zoom +// that can fit everything +bool makeAllChunksRequired(const std::string& inputdir, ChunkTable& chunktable, TileTable& tiletable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount); + +// read a list of chunk filenames from a file and set the chunks to required in the ChunkTable, and set +// any tiles they touch to required in the TileTable +// returns 0 on success, -1 if baseZoom is too small, -2 for other errors (can't read chunklist, world too big +// for our internal data structures, etc.) +int readChunklist(const std::string& chunklist, ChunkTable& chunktable, TileTable& tiletable, const MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount); + + + + +// build a test world by making approximately size chunks required +// if mp.baseZoom is set to -1 coming in, it will be set to the smallest zoom that can fit everything +void makeTestWorld(int size, ChunkTable& chunktable, TileTable& tiletable, MapParams& mp, int64_t& reqchunkcount, int64_t& reqtilecount); + +// get the filepaths of all chunks on disk (used only for testing) +void findAllChunks(const std::string& inputdir, std::vector& chunkpaths); + + #endif // WORLD_H \ No newline at end of file