From 1e296e858a8a1c2eb075f8ca6f3c61595683fb2d Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Fri, 10 Sep 2010 00:04:02 -0400 Subject: [PATCH] readme updates, texture code updates. Re-factored the way the textures and blocks are being built. It should be easier to understand and add new exceptions (sorta). Also fixed the water and lava with other texture packs by putting a static water.png and lava.png in with the code. --- README.rst | 29 +++++- chunk.py | 3 +- textures.py | 245 +++++++++++++++++++++++++++------------------ textures/lava.png | Bin 0 -> 401 bytes textures/water.png | Bin 0 -> 374 bytes world.py | 2 + 6 files changed, 175 insertions(+), 104 deletions(-) create mode 100644 textures/lava.png create mode 100644 textures/water.png diff --git a/README.rst b/README.rst index 64dabf8..a652933 100644 --- a/README.rst +++ b/README.rst @@ -96,8 +96,33 @@ render for my world from 85M to 67M. find /path/to/destination -name "*.png" -exec pngcrush {} {}.crush \; -exec mv {}.crush {} \; -Windows users, you're on your own, but there's probably a way to do this. (If -someone figures it out, let me know I'll update this README) +If you're on Windows, I've gotten word that this command line snippet works +provided pngout is installed and on your path. Note that the % symbols will +need to be doubled up if this is in a batch file. + +:: + + FOR /R c:\path\to\tiles\folder %v IN (*.png) DO pngout %v /y + +Viewing the Results +------------------- +The output is two things: an index.html file, and a directory hierarchy full of +images. To view your world, simply open index.html in a web browser. Internet +access is required to load the Google Maps API files, but you otherwise don't +need anything else. + +You can throw these files up to a web server to let others view your map. You +do not need a Google Maps API key (as was the case with older versions of the +API), so just copying the directory to your web server should suffice. + +Tip: Since Minecraft worlds rarely produce perfectly square worlds, there will +be blank and non-existent tiles around the borders of your world. The Google +Maps API has no way of knowing this until it requests them and the web server +returns a 404 Not Found. If this doesn't bother you, then fine, stop reading. +Otherwise: you can avoid a lot of 404s to your logs by configuring your web +server to redirect all 404 requests in that directory to a single 1px +"blank.png". This may or may not save on bandwidth, but it will probably save +on log noise. Using the Large Image Renderer ============================== diff --git a/chunk.py b/chunk.py index ffdbbb9..d32bc2c 100644 --- a/chunk.py +++ b/chunk.py @@ -6,7 +6,6 @@ import hashlib import nbt import textures -from textures import texturemap as txtarray # General note about pasting transparent image objects onto an image with an # alpha channel: @@ -244,7 +243,7 @@ class ChunkRenderer(object): if blockid not in transparent_blocks: draw = ImageDraw.Draw(img) if x != 15 and blocks[x+1,y,z] == 0: - draw.line(((imgx+12,imgy), (imgx+24,imgy+6)), fill=(0,0,0), width=1) + draw.line(((imgx+12,imgy), (imgx+22,imgy+5)), fill=(0,0,0), width=1) if y != 0 and blocks[x,y-1,z] == 0: draw.line(((imgx,imgy+6), (imgx+12,imgy)), fill=(0,0,0), width=1) diff --git a/textures.py b/textures.py index b857781..499b1a4 100644 --- a/textures.py +++ b/textures.py @@ -8,36 +8,70 @@ import math import numpy from PIL import Image, ImageEnhance -def _get_terrain_image(): - # Check the current directory for terrain.png first: - if os.path.isfile("terrain.png"): - return Image.open("terrain.png") +def _find_file(filename, mode="rb"): + """Searches for the given file and returns an open handle to it. + This searches the following locations in this order: + + * The program dir (same dir as this file) + * On Darwin, in /Applications/Minecraft + * Inside minecraft.jar, which is looked for at these locations - if "darwin" in sys.platform: - # On Macs, terrain.png could lie at - # "/Applications/minecraft/terrain.png" for custom terrain. Try this - # first. - png = "/Applications/Minecraft/terrain.png" - if os.access(png, os.F_OK): - return Image.open(png) + * On Windows, at %APPDATA%/.minecraft/bin/minecraft.jar + * On Darwin, at $HOME/Library/Application Support/minecraft/bin/minecraft.jar + * at $HOME/.minecraft/bin/minecraft.jar - # Paths on a Mac are a bit different - minecraftdir = os.path.join(os.environ['HOME'], "Library", - "Application Support", "minecraft") - minecraftjar = zipfile.ZipFile(os.path.join(minecraftdir, "bin", "minecraft.jar")) - textures = minecraftjar.open("terrain.png") + * The current working directory + * The program dir / textures - else: - if "win" in sys.platform: - minecraftdir = os.environ['APPDATA'] - else: - minecraftdir = os.environ['HOME'] - minecraftjar = zipfile.ZipFile(os.path.join(minecraftdir, ".minecraft", + """ + programdir = os.path.dirname(__file__) + path = os.path.join(programdir, filename) + if os.path.exists(path): + return open(path, mode) + + if sys.platform == "darwin": + path = os.path.join("/Applications/Minecraft", filename) + if os.path.exists(path): + return open(path, mode) + + # Find minecraft.jar. + jarpaths = [] + if "APPDATA" in os.environ: + jarpaths.append( os.path.join(os.environ['APPDATA'], ".minecraft", "bin", "minecraft.jar")) - textures = minecraftjar.open("terrain.png") - buffer = StringIO(textures.read()) + if "HOME" in os.environ: + jarpaths.append(os.path.join(os.environ['HOME'], "Library", + "Application Support", "minecraft")) + jarpaths.append(os.path.join(os.environ['HOME'], ".minecraft", "bin", + "minecraft.jar")) + + for jarpath in jarpaths: + if os.path.exists(jarpath): + jar = zipfile.ZipFile(jarpath) + try: + return jar.open(filename) + except KeyError: + pass + + path = filename + if os.path.exists(path): + return open(path, mode) + + path = os.path.join(programdir, "textures", filename) + if os.path.exists(path): + return open(path, mode) + + raise IOError("Could not find the file {0}".format(filename)) + +def _load_image(filename): + """Returns an image object""" + fileobj = _find_file(filename) + buffer = StringIO(fileobj.read()) return Image.open(buffer) +def _get_terrain_image(): + return _load_image("terrain.png") + def _split_terrain(terrain): """Builds and returns a length 256 array of each 16x16 chunk of texture""" textures = [] @@ -100,102 +134,113 @@ def _transform_image_side(img): return newimg -def _build_texturemap(): - """""" - t = terrain_images +def _build_block(top, side): + """From a top texture and a side texture, build a block image. + top and side should be 16x16 image objects. Returns a 24x24 image - # Notes are for things I've left out or will probably have to make special - # exception for - top = [-1,1,0,2,16,4,15,17,205,205,237,237,18,19,32,33, + """ + img = Image.new("RGBA", (24,24)) + + top = _transform_image(top) + + if not side: + img.paste(top, (0,0), top) + return img + + side = _transform_image_side(side) + + otherside = side.transpose(Image.FLIP_LEFT_RIGHT) + + # Darken the sides slightly. These methods also affect the alpha layer, + # so save them first (we don't want to "darken" the alpha layer making + # the block transparent) + sidealpha = side.split()[3] + side = ImageEnhance.Brightness(side).enhance(0.9) + side.putalpha(sidealpha) + othersidealpha = otherside.split()[3] + otherside = ImageEnhance.Brightness(otherside).enhance(0.8) + otherside.putalpha(othersidealpha) + + img.paste(side, (0,6), side) + img.paste(otherside, (12,6), otherside) + img.paste(top, (0,0), top) + + # Manually touch up 6 pixels that leave a gap because of how the + # shearing works out. This makes the blocks perfectly tessellate-able + for x,y in [(13,23), (17,21), (21,19)]: + # Copy a pixel to x,y from x-1,y + img.putpixel((x,y), img.getpixel((x-1,y))) + for x,y in [(3,4), (7,2), (11,0)]: + # Copy a pixel to x,y from x+1,y + img.putpixel((x,y), img.getpixel((x+1,y))) + + return img + + +def _build_blockimages(): + """Returns a mapping from blockid to an image of that block in perspective + The values of the mapping are actually (image in RGB mode, alpha channel). + This is not appropriate for all block types, only block types that are + proper cubes""" + + # Top textures of all block types. The number here is the index in the + # texture array (terrain_images), which comes from terrain.png's cells, left to right top to + # bottom. + topids = [-1,1,0,2,16,4,15,17,205,205,237,237,18,19,32,33, 34,21,52,48,49,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, # Cloths are left out -1,-1,-1,64,64,13,12,29,28,23,22,6,6,7,8,35, # Gold/iron blocks? Doublestep? TNT from above? 36,37,-1,-1,65,4,25,101,98,24,43,-1,86,1,1,-1, # Torch from above? leaving out fire. Redstone wire? Crops left out. sign post -1,-1,-1,16,-1,-1,-1,-1,-1,51,51,-1,-1,1,66,67, # door,ladder left out. Minecart rail orientation 66,69,72,-1,74 # clay? ] - side = [-1,1,3,2,16,4,15,17,205,205,237,237,18,19,32,33, - 34,21,52,48,49,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, + + # And side textures of all block types + sideids = [-1,1,3,2,16,4,15,17,205,205,237,237,18,19,32,33, + 34,20,52,48,49,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,64,64,13,12,29,28,23,22,6,6,7,8,35, 36,37,-1,-1,65,4,25,101,98,24,43,-1,86,1,1,-1, -1,-1,-1,16,-1,-1,-1,-1,-1,51,51,-1,-1,1,66,67, 66,69,72,-1,74 ] - side[2] = 2 - return ( - [(t[x] if x != -1 else None) for x in top], - [(_transform_image(t[x]) if x != -1 else None) for x in top], - [(_transform_image_side(t[x]) if x != -1 else None) for x in side], - ) -# texturemap maps block ids to a 16x16 image that goes on the top face -# perspective_texturemap does the same, except the texture is rotated and shrunk -# shear_texturemap maps block ids to the image that goes on the side of the -# block, sheared appropriately -texturemap, perspective_texturemap, shear_texturemap = _build_texturemap() - -def _render_sprite(img): - """Takes a 16x16 sprite image, and returns a 22x22 image to go in the - blockmap - This is for rendering things that are sticking out of the ground, like - flowers and such - torches are drawn the same way, but torches that attach to walls are - handled differently - """ - pass - -def _render_ground_image(img): - """Takes a 16x16 sprite image and skews it to look like it's on the ground. - This is for things like mine track and such - - """ - pass - -def _build_blockimages(): - """Returns a mapping from blockid to an image of that block in perspective - The values of the mapping are actually (image in RGB mode, alpha channel)""" # This maps block id to the texture that goes on the side of the block allimages = [] - for top, side in zip(perspective_texturemap, shear_texturemap): - if not top or not side: + for toptextureid, sidetextureid in zip(topids, sideids): + if toptextureid == -1 or sidetextureid == -1: allimages.append(None) continue - img = Image.new("RGBA", (24,24)) - - otherside = side.transpose(Image.FLIP_LEFT_RIGHT) - # Darken the sides slightly. These methods also affect the alpha layer, - # so save them first (we don't want to "darken" the alpha layer making - # the block transparent) - if 1: - sidealpha = side.split()[3] - side = ImageEnhance.Brightness(side).enhance(0.9) - side.putalpha(sidealpha) - othersidealpha = otherside.split()[3] - otherside = ImageEnhance.Brightness(otherside).enhance(0.8) - otherside.putalpha(othersidealpha) + toptexture = terrain_images[toptextureid] + sidetexture = terrain_images[sidetextureid] - # Copy on the left side - img.paste(side, (0,6), side) - # Copy on the other side - img.paste(otherside, (12,6), otherside) - # Copy on the top piece (last so it's on top) - img.paste(top, (0,0), top) - - # Manually touch up 6 pixels that leave a gap because of how the - # shearing works out. This makes the blocks perfectly tessellate-able - for x,y in [(13,23), (17,21), (21,19)]: - # Copy a pixel to x,y from x-1,y - img.putpixel((x,y), img.getpixel((x-1,y))) - for x,y in [(3,4), (7,2), (11,0)]: - # Copy a pixel to x,y from x+1,y - img.putpixel((x,y), img.getpixel((x+1,y))) + img = _build_block(toptexture, sidetexture) allimages.append((img.convert("RGB"), img.split()[3])) - return allimages -# Maps block images to the appropriate texture on each side. This map is not -# appropriate for all block types + # Future block types: + while len(allimages) < 256: + allimages.append(None) + return allimages blockmap = _build_blockimages() -# Future block types: -while len(blockmap) < 256: - blockmap.append(None) + +def load_water(): + """Evidentially, the water and lava textures are not loaded from any files + in the jar (that I can tell). They must be generated on the fly. While + terrain.png does have some water and lava cells, not all texture packs + include them. So I load them here from a couple pngs included. + + This mutates the blockmap global list with the new water and lava blocks. + Block 9, standing water, is given a block with only the top face showing. + Block 8, flowing water, is given a full 3 sided cube.""" + + watertexture = _load_image("water.png") + w1 = _build_block(watertexture, None) + blockmap[9] = w1.convert("RGB"), w1 + w2 = _build_block(watertexture, watertexture) + blockmap[8] = w2.convert("RGB"), w2 + + lavatexture = _load_image("lava.png") + lavablock = _build_block(lavatexture, lavatexture) + blockmap[10] = lavablock.convert("RGB"), lavablock + blockmap[11] = blockmap[10] +load_water() diff --git a/textures/lava.png b/textures/lava.png new file mode 100644 index 0000000000000000000000000000000000000000..b70c6c152088e2799e5fa5bbe81205c67f7c29ef GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s77>k44ofy`glX=O&z`&N| z?e4&kwMlZ#P+Ya3@<3j+g# zWQl7;iF1B#Zfaf$gL6@8Vo7R>LV0FMhJw4NZ$Nk>pEv^p!y8W*#}JFtrIXL{9#-IK z-S2j@-F6p4yU+23>|!s(WH#{&*|HqBH<_lR$n&lG{I}HyJz7uib7&Rbs?uI_^uY0# zGcW%Ba^^+My$hfJsvVK;;ce(wv&nthvweHN`h%;979ElD#XkSjLp36Hh^?wHEO*+f zA)7bvXnmQK$HXK%Z>I3Mp-beBZedkz^bQhnJRF`an49?KhTZbP0l+XkKPPU!{ literal 0 HcmV?d00001 diff --git a/textures/water.png b/textures/water.png new file mode 100644 index 0000000000000000000000000000000000000000..2c0e69f3471246184a3cb2b3e8f7d98919a4086d GIT binary patch literal 374 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s77>k44ofy`glX=O&z`&N| z?e4&kwMlZ%nx^sMPpbp{3o z$r9Iy66gHf+|;}h2Ir#G#FEq$h4Rdj37L3lj- zGu&C4&mGFH@m_6b z#pP$O+g90>DyDw8Gd=F$!n9ANuJi6WxyeMHzvr=w)qjro6>Z*(fA@7)oL^}Cjdfns Xobs%bt;-k~7#KWV{an^LB{Ts5N|28W literal 0 HcmV?d00001 diff --git a/world.py b/world.py index 4d36efe..c7d084c 100644 --- a/world.py +++ b/world.py @@ -74,6 +74,8 @@ def render_chunks_async(chunks, caves, processes): kwds=dict(cave=caves)) resultsmap[(chunkx, chunky)] = result + pool.close() + # Stick the pool object in the dict under the key "pool" so it isn't # garbage collected (which kills the subprocesses) resultsmap['pool'] = pool