diff --git a/chunk.py b/chunk.py index 59ccae0..048b650 100644 --- a/chunk.py +++ b/chunk.py @@ -114,9 +114,10 @@ def get_tileentity_data(level): return data # This set holds blocks ids that can be seen through, for occlusion calculations -transparent_blocks = set([ 0, 6, 8, 9, 18, 20, 27, 28, 37, 38, 39, 40, 44, 50, - 51, 52, 53, 55, 59, 63, 64, 65, 66, 67, 68, 69, 70, 71, - 72, 74, 75, 76, 77, 78, 79, 81, 83, 85, 92]) +transparent_blocks = set([ 0, 6, 8, 9, 18, 20, 26, 27, 28, 30, 37, 38, 39, 40, + 44, 50, 51, 52, 53, 55, 59, 63, 64, 65, 66, 67, 68, 69, + 70, 71, 72, 74, 75, 76, 77, 78, 79, 81, 83, 85, 90, 92, + 93, 94]) # This set holds block ids that are solid blocks solid_blocks = set([1, 2, 3, 4, 5, 7, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, diff --git a/overviewer.py b/overviewer.py index c8e7500..bd4ef7b 100755 --- a/overviewer.py +++ b/overviewer.py @@ -164,10 +164,18 @@ def main(): logging.error("Invalid world number") sys.exit(1) - if len(args) != 2: + if len(args) < 2: if options.delete: return delete_all(worlddir, None) - parser.error("Where do you want to save the tiles?") + logging.error("Where do you want to save the tiles?") + sys.exit(1) + elif len(args) > 2: + if options.delete: + return delete_all(worlddir, None) + parser.print_help() + logging.error("Sorry, you specified too many arguments") + sys.exit(1) + destdir = args[1] if options.display_config: diff --git a/setup.py b/setup.py index 284795d..b168ee0 100644 --- a/setup.py +++ b/setup.py @@ -27,7 +27,7 @@ setup_kwargs['cmdclass'] = {} if py2exe is not None: setup_kwargs['console'] = ['overviewer.py'] - setup_kwargs['data_files'] = [('textures', ['textures/lava.png', 'textures/water.png', 'textures/fire.png']), + setup_kwargs['data_files'] = [('textures', ['textures/lava.png', 'textures/water.png', 'textures/fire.png', 'textures/portal.png']), ('', ['overviewerConfig.js', 'COPYING.txt', 'README.rst']), ('web_assets', glob.glob('web_assets/*'))] setup_kwargs['zipfile'] = None diff --git a/src/iterate.c b/src/iterate.c index 1828409..7fd3d3f 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -276,6 +276,8 @@ generate_pseudo_data(RenderState *state, unsigned char ancilData) { return final_data; + } else if (state->block == 90) { + return check_adjacent_blocks(state, x, y, z, state->block); } @@ -398,7 +400,7 @@ chunk_render(PyObject *self, PyObject *args) { PyObject *tmp; unsigned char ancilData = getArrayByte3D(blockdata_expanded, state.x, state.y, state.z); - if ((state.block == 85) || (state.block == 9) || (state.block == 55) || (state.block == 54) || (state.block == 2)) { + if ((state.block == 85) || (state.block == 9) || (state.block == 55) || (state.block == 54) || (state.block == 2) || (state.block == 90)) { ancilData = generate_pseudo_data(&state, ancilData); } diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c index be14a92..b4118e3 100644 --- a/src/rendermode-normal.c +++ b/src/rendermode-normal.c @@ -177,7 +177,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * int increment=0; if (state->block == 44) // half-step increment=6; - else if (state->block == 78) // snow + else if ((state->block == 78) || (state->block == 93) || (state->block == 94)) // snow, redstone repeaters (on and off) increment=9; if ((state->x == 15) && (state->up_right_blocks != Py_None)) { diff --git a/textures.py b/textures.py index 1b9e3a1..b5fb86a 100644 --- a/textures.py +++ b/textures.py @@ -121,14 +121,9 @@ def transform_image(img, blockID=None): """ - if blockID in (81,92): # cacti and cake - # Resize to 15x15, since the cactus and the cake textures are a little smaller than the other textures - img = img.resize((15, 15), Image.ANTIALIAS) - - else: - # Resize to 17x17, since the diagonal is approximately 24 pixels, a nice - # even number that can be split in half twice - img = img.resize((17, 17), Image.ANTIALIAS) + # Resize to 17x17, since the diagonal is approximately 24 pixels, a nice + # even number that can be split in half twice + img = img.resize((17, 17), Image.ANTIALIAS) # Build the Affine transformation matrix for this perspective transform = numpy.matrix(numpy.identity(3)) @@ -223,7 +218,7 @@ def _build_block(top, side, blockID=None): otherside.putalpha(othersidealpha) ## special case for non-block things - if blockID in (37,38,6,39,40,83): ## flowers, sapling, mushrooms, reeds + if blockID in (37,38,6,39,40,83,30): ## flowers, sapling, mushrooms, reeds, web # # instead of pasting these blocks at the cube edges, place them in the middle: # and omit the top @@ -233,9 +228,9 @@ def _build_block(top, side, blockID=None): if blockID in (81,): # cacti! - composite.alpha_over(img, side, (2,6), side) - composite.alpha_over(img, otherside, (10,6), otherside) - composite.alpha_over(img, top, (0,2), top) + composite.alpha_over(img, side, (1,6), side) + composite.alpha_over(img, otherside, (11,6), otherside) + composite.alpha_over(img, top, (0,0), top) elif blockID in (44,): # half step # shift each texture down 6 pixels composite.alpha_over(img, side, (0,12), side) @@ -273,9 +268,37 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None side3 is in the -x (bottom left, north) side4 is in the +y (bottom right, west) - A non transparent block uses top, side 3 and side 4 + A non transparent block uses top, side 3 and side 4. + + If top is a tuple then first member is the top image and the second + member is an increment (integer) from 0 to 12. This increment will + used to crop the side images to look like a block and to paste all + the images increment pixels lower. Using increment = 6 will create + a half-block. + + NOTE: this method uses the top of the texture image (as done in + minecraft with beds) """ + + increment = 0 + if isinstance(top, tuple): + increment = top[1] + crop_height = int(increment * 16./12.) + top = top[0] + if side1 != None: + side1 = side1.copy() + ImageDraw.Draw(side1).rectangle((0, 16 - crop_height,16,16),outline=(0,0,0,0),fill=(0,0,0,0)) + if side2 != None: + side2 = side2.copy() + ImageDraw.Draw(side2).rectangle((0, 16 - crop_height,16,16),outline=(0,0,0,0),fill=(0,0,0,0)) + if side3 != None: + side3 = side3.copy() + ImageDraw.Draw(side3).rectangle((0, 16 - crop_height,16,16),outline=(0,0,0,0),fill=(0,0,0,0)) + if side4 != None: + side4 = side4.copy() + ImageDraw.Draw(side4).rectangle((0, 16 - crop_height,16,16),outline=(0,0,0,0),fill=(0,0,0,0)) + img = Image.new("RGBA", (24,24), (38,92,255,0)) # first back sides @@ -288,7 +311,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None side1 = ImageEnhance.Brightness(side1).enhance(0.9) side1.putalpha(sidealpha) - composite.alpha_over(img, side1, (0,0), side1) + composite.alpha_over(img, side1, (0,0 + increment), side1) if side2 != None : @@ -299,7 +322,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None side2 = ImageEnhance.Brightness(side2).enhance(0.8) side2.putalpha(sidealpha2) - composite.alpha_over(img, side2, (12,0), side2) + composite.alpha_over(img, side2, (12,0 + increment), side2) if bottom != None : bottom = transform_image(bottom, blockID) @@ -314,7 +337,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None side3 = ImageEnhance.Brightness(side3).enhance(0.9) side3.putalpha(sidealpha) - composite.alpha_over(img, side3, (0,6), side3) + composite.alpha_over(img, side3, (0,6 + increment), side3) if side4 != None : side4 = transform_image_side(side4, blockID) @@ -325,11 +348,11 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None side4 = ImageEnhance.Brightness(side4).enhance(0.8) side4.putalpha(sidealpha) - composite.alpha_over(img, side4, (12,6), side4) + composite.alpha_over(img, side4, (12,6 + increment), side4) if top != None : top = transform_image(top, blockID) - composite.alpha_over(img, top, (0,0), top) + composite.alpha_over(img, top, (0, increment), top) return img @@ -346,13 +369,13 @@ def _build_blockimages(): # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 topids = [ -1, 1, 0, 2, 16, 4, -1, 17,205,205,237,237, 18, 19, 32, 33, # 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 - 34, -1, 52, 48, 49,160,144, -1,176, 74, -1, -1, -1, -1, -1, -1, # Cloths are left out, sandstone (it has top, side, and bottom wich is ignored here), note block + 34, -1, 52, 48, 49,160,144, -1,176, 74, -1, -1, -1, -1, 11, -1, # 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 - -1, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 7, 8, 4, # Gold/iron blocks? Doublestep? TNT from above? + -1, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 7, 9, 4, # 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 - 36, 37, -1, -1, 65, -1, -1, -1, 98, 24, -1, -1, 86, -1, -1, -1, # Torch from above? leaving out fire. Redstone wire? Crops/furnaces handled elsewhere. sign post + 36, 37, -1, -1, 65, -1, -1, -1, 50, 24, -1, -1, 86, -1, -1, -1, # 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 - -1, -1, -1, -1, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67, # door,ladder left out. Minecart rail orientation, redstone torches + -1, -1, -1, -1, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67, # 80 81 82 83 84 85 86 87 88 89 90 91 66, 69, 72, 73, 75, -1,102,103,104,105,-1, 102 # clay? ] @@ -363,15 +386,15 @@ def _build_blockimages(): # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 sideids = [ -1, 1, 3, 2, 16, 4, -1, 17,205,205,237,237, 18, 19, 32, 33, # 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 - 34, -1, 52, 48, 49,160,144, -1,192, 74, -1, -1,- 1, -1, -1, -1, + 34, -1, 52, 48, 49,160,144, -1,192, 74, -1, -1,- 1, -1, 11, -1, # 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 -1, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 7, 8, 35, # 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 - 36, 37, -1, -1, 65, -1, -1,101, 98, 24, -1, -1, 86, -1, -1, -1, + 36, 37, -1, -1, 65, -1, -1,101, 50, 24, -1, -1, 86, -1, -1, -1, # 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 -1, -1, -1, -1, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67, # 80 81 82 83 84 85 86 87 88 89 90 91 - 66, 69, 72, 73, 74,-1 ,118,103,104,105, -1, 118 + 66, 70, 72, 73, 74,-1 ,118,103,104,105, -1, 118 ] # This maps block id to the texture that goes on the side of the block @@ -516,6 +539,52 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"), img.split()[3]) + if blockID == 26: # bed + increment = 5 + left_face = None + right_face = None + if data & 0x8 == 0x8: # head of the bed + top = terrain_images[135] + if data & 0x00 == 0x00: # head pointing to West + top = top.copy().rotate(270) + left_face = terrain_images[151] + right_face = terrain_images[152] + if data & 0x01 == 0x01: # ... North + top = top.rotate(270) + left_face = terrain_images[152] + right_face = terrain_images[151] + if data & 0x02 == 0x02: # East + top = top.rotate(180) + left_face = terrain_images[151].transpose(Image.FLIP_LEFT_RIGHT) + right_face = None + if data & 0x03 == 0x03: # South + right_face = None + right_face = terrain_images[151].transpose(Image.FLIP_LEFT_RIGHT) + + else: # foot of the bed + top = terrain_images[134] + if data & 0x00 == 0x00: # head pointing to West + top = top.rotate(270) + left_face = terrain_images[150] + right_face = None + if data & 0x01 == 0x01: # ... North + top = top.rotate(270) + left_face = None + right_face = terrain_images[150] + if data & 0x02 == 0x02: # East + top = top.rotate(180) + left_face = terrain_images[150].transpose(Image.FLIP_LEFT_RIGHT) + right_face = terrain_images[149].transpose(Image.FLIP_LEFT_RIGHT) + if data & 0x03 == 0x03: # South + left_face = terrain_images[149] + right_face = terrain_images[150].transpose(Image.FLIP_LEFT_RIGHT) + + top = (top, increment) + img = _build_full_block(top, None, None, left_face, right_face) + + return (img.convert("RGB"), img.split()[3]) + + if blockID == 35: # wool if data == 0: # white top = side = terrain_images[64] @@ -1186,6 +1255,22 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"), img.split()[3]) + if blockID == 90: # portal + portaltexture = _load_image("portal.png") + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + side = transform_image_side(portaltexture) + otherside = side.transpose(Image.FLIP_TOP_BOTTOM) + + if data in (1,4): + composite.alpha_over(img, side, (5,4), side) + + if data in (2,8): + composite.alpha_over(img, otherside, (5,4), otherside) + + return (img.convert("RGB"), img.split()[3]) + + if blockID == 92: # cake! (without bites, at the moment) top = terrain_images[121] @@ -1203,13 +1288,129 @@ def generate_special_texture(blockID, data): img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, side, (2,12), side) - composite.alpha_over(img, otherside, (10,12), otherside) - composite.alpha_over(img, top, (0,8), top) + composite.alpha_over(img, side, (1,12), side) + composite.alpha_over(img, otherside, (11,13), otherside) # workaround, fixes a hole + composite.alpha_over(img, otherside, (12,12), otherside) + composite.alpha_over(img, top, (0,6), top) + + return (img.convert("RGB"), img.split()[3]) + + + if blockID in (93, 94): # redstone repeaters, ON and OFF + # NOTE: this function uses the redstone torches generated above, + # this must run after the function of the torches. + + top = terrain_images[131] if blockID == 93 else terrain_images[147] + side = terrain_images[5] + increment = 9 - #~ composite.alpha_over(img, side, (2,6), side) - #~ composite.alpha_over(img, otherside, (10,6), otherside) - #~ composite.alpha_over(img, top, (0,2), top) + if (data & 0x3) == 0: # pointing east + pass + + if (data & 0x3) == 1: # pointing south + top = top.rotate(270) + + if (data & 0x3) == 2: # pointing west + top = top.rotate(180) + + if (data & 0x3) == 3: # pointing north + top = top.rotate(90) + + img = _build_full_block( (top, increment), None, None, side, side) + + # paste redstone torches everywhere! + t = specialblockmap[(75,5)] if blockID == 93 else specialblockmap[(76,5)] + torch = t[0].copy() # textures are stored as tuples (RGB,A) + torch.putalpha(t[1]) + + # the torch is too tall for the repeater, crop the bottom. + ImageDraw.Draw(torch).rectangle((0,16,24,24),outline=(0,0,0,0),fill=(0,0,0,0)) + + # touch up the 3d effect with big rectangles, just in case, for other texture packs + ImageDraw.Draw(torch).rectangle((0,24,10,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(torch).rectangle((12,15,24,24),outline=(0,0,0,0),fill=(0,0,0,0)) + + # torch positions for every redstone torch orientation. + # + # This is a horrible list of torch orientations. I tried to + # obtain these orientations by rotating the positions for one + # orientation, but pixel rounding is horrible and messes the + # torches. + + if (data & 0x3) == 0: # pointing east + if (data & 0xC) == 0: # one tick delay + moving_torch = (1,1) + static_torch = (-3,-1) + + elif (data & 0xC) == 4: # two ticks delay + moving_torch = (2,2) + static_torch = (-3,-1) + + elif (data & 0xC) == 8: # three ticks delay + moving_torch = (3,2) + static_torch = (-3,-1) + + elif (data & 0xC) == 12: # four ticks delay + moving_torch = (4,3) + static_torch = (-3,-1) + + elif (data & 0x3) == 1: # pointing south + if (data & 0xC) == 0: # one tick delay + moving_torch = (1,1) + static_torch = (5,-1) + + elif (data & 0xC) == 4: # two ticks delay + moving_torch = (2,0) + static_torch = (5,-1) + + elif (data & 0xC) == 8: # three ticks delay + moving_torch = (3,0) + static_torch = (5,-1) + + elif (data & 0xC) == 12: # four ticks delay + moving_torch = (4,-1) + static_torch = (5,-1) + + elif (data & 0x3) == 2: # pointing west + if (data & 0xC) == 0: # one tick delay + moving_torch = (1,1) + static_torch = (5,3) + + elif (data & 0xC) == 4: # two ticks delay + moving_torch = (0,0) + static_torch = (5,3) + + elif (data & 0xC) == 8: # three ticks delay + moving_torch = (-1,0) + static_torch = (5,3) + + elif (data & 0xC) == 12: # four ticks delay + moving_torch = (-2,-1) + static_torch = (5,3) + + elif (data & 0x3) == 3: # pointing north + if (data & 0xC) == 0: # one tick delay + moving_torch = (1,1) + static_torch = (-3,3) + + elif (data & 0xC) == 4: # two ticks delay + moving_torch = (2,0) + static_torch = (-3,3) + + elif (data & 0xC) == 8: # three ticks delay + moving_torch = (3,0) + static_torch = (-3,3) + + elif (data & 0xC) == 12: # four ticks delay + moving_torch = (4,-1) + static_torch = (-3,3) + + # this paste order it's ok for east and south orientation + # but it's wrong for north and west orientations. But using the + # default texture pack the torches are small enough to no overlap. + composite.alpha_over(img, torch, static_torch, torch) + composite.alpha_over(img, torch, moving_torch, torch) + return (img.convert("RGB"), img.split()[3]) @@ -1290,9 +1491,9 @@ def getBiomeData(worlddir, chunkX, chunkY): # (when adding new blocks here and in generate_special_textures, # please, if possible, keep the ascending order of blockid value) -special_blocks = set([ 2, 6, 9, 17, 18, 23, 27, 28, 35, 43, 44, 50, 51, - 53, 54, 55, 58, 59, 61, 62, 64, 65, 66, 67, 71, 75, - 76, 85, 86, 91, 92]) +special_blocks = set([ 2, 6, 9, 17, 18, 26, 23, 27, 28, 35, 43, 44, 50, + 51, 53, 54, 55, 58, 59, 61, 62, 64, 65, 66, 67, 71, + 75, 76, 85, 86, 90, 91, 92, 93, 94]) # this is a map of special blockIDs to a list of all # possible values for ancillary data that it might have. @@ -1302,6 +1503,7 @@ special_map = {} special_map[6] = range(16) # saplings: usual, spruce, birch and future ones (rendered as usual saplings) special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values, uses pseudo data special_map[17] = range(4) # wood: normal, birch and pine +special_map[26] = range(12) # bed, orientation special_map[23] = range(6) # dispensers, orientation special_map[27] = range(14) # powered rail, orientation/slope and powered/unpowered special_map[28] = range(6) # detector rail, orientation/slope @@ -1326,8 +1528,11 @@ special_map[75] = (1,2,3,4,5) # off redstone torch, orientation special_map[76] = (1,2,3,4,5) # on redstone torch, orientation special_map[85] = range(17) # fences, all the possible combination, uses pseudo data special_map[86] = range(5) # pumpkin, orientation +special_map[90] = (1,2,4,8) # portal, in 2 orientations, 4 cases, uses pseudo data special_map[91] = range(5) # jack-o-lantern, orientation special_map[92] = range(6) # cake! +special_map[93] = range(16) # OFF redstone repeater, orientation and delay (delay not implemented) +special_map[94] = range(16) # ON redstone repeater, orientation and delay (delay not implemented) # grass and leaves are graysacle in terrain.png # we treat them as special so we can manually tint them diff --git a/textures/portal.png b/textures/portal.png new file mode 100644 index 0000000..1f75ca3 Binary files /dev/null and b/textures/portal.png differ diff --git a/web_assets/overviewer.js b/web_assets/overviewer.js index 6220a16..eea2f7d 100644 --- a/web_assets/overviewer.js +++ b/web_assets/overviewer.js @@ -368,6 +368,14 @@ var overviewer = { clickable = false; // if it doesn't have a name, we dont have to show it. } + if(region.opacity) { + var strokeOpacity = region.opacity; + var fillOpacity = region.opacity * 0.25; + } else { + var strokeOpacity = region.strokeOpacity; + var fillOpacity = region.fillOpacity; + } + if (region.closed) { var shape = new google.maps.Polygon({ 'name': name, @@ -375,10 +383,10 @@ var overviewer = { 'geodesic': false, 'map': null, 'strokeColor': region.color, - 'strokeOpacity': region.opacity, + 'strokeOpacity': strokeOpacity, 'strokeWeight': overviewerConfig.CONST.regionStrokeWeight, 'fillColor': region.color, - 'fillOpacity': region.opacity * 0.25, + 'fillOpacity': fillOpacity, 'zIndex': j, 'paths': converted }); @@ -389,7 +397,7 @@ var overviewer = { 'geodesic': false, 'map': null, 'strokeColor': region.color, - 'strokeOpacity': region.opacity, + 'strokeOpacity': strokeOpacity, 'strokeWeight': overviewerConfig.CONST.regionStrokeWeight, 'zIndex': j, 'path': converted