diff --git a/chunk.py b/chunk.py index 98173df..dd06ffe 100644 --- a/chunk.py +++ b/chunk.py @@ -129,38 +129,6 @@ fluid_blocks = set([8,9,10,11]) # (glass, half blocks) nospawn_blocks = set([20,44]) -# chunkcoords should be the coordinates of a possible chunk. it may not exist -def render_to_image(chunkcoords, img, imgcoords, quadtreeobj, cave=False, queue=None): - """Used to render a chunk to a tile in quadtree.py. - - chunkcoords is a tuple: (chunkX, chunkY) - - imgcoords is as well: (imgX, imgY), which represents the "origin" - to use for drawing. - - If the chunk doesn't exist, return False. - Else, returns True.""" - a = ChunkRenderer(chunkcoords, quadtreeobj.world, quadtreeobj.rendermode, queue) - try: - a.chunk_render(img, imgcoords[0], imgcoords[1], cave) - return True - except ChunkCorrupt: - # This should be non-fatal, but should print a warning - pass - except Exception, e: - import traceback - traceback.print_exc() - raise - except KeyboardInterrupt: - print - print "You pressed Ctrl-C. Exiting..." - # Raise an exception that is an instance of Exception. Unlike - # KeyboardInterrupt, this will re-raise in the parent, killing the - # entire program, instead of this process dying and the parent waiting - # forever for it to finish. - raise Exception() - return False - class ChunkCorrupt(Exception): pass diff --git a/contrib/testRender.py b/contrib/testRender.py index 2ea0e87..c4de264 100644 --- a/contrib/testRender.py +++ b/contrib/testRender.py @@ -4,6 +4,8 @@ import os, shutil, tempfile, time, sys, math, re from subprocess import Popen, PIPE, STDOUT, CalledProcessError from optparse import OptionParser +overviewer_scripts = ['./overviewer.py', './gmap.py'] + def check_call(*args, **kwargs): quiet = False if "quiet" in kwargs.keys(): @@ -35,12 +37,21 @@ def check_output(*args, **kwargs): def clean_render(overviewerargs, quiet): tempdir = tempfile.mkdtemp('mc-overviewer-test') + overviewer_script = None + for script in overviewer_scripts: + if os.path.exists(script): + overviewer_script = script + break + if overviewer_script is None: + sys.stderr.write("could not find main overviewer script\n") + sys.exit(1) + try: # check_call raises CalledProcessError when overviewer.py exits badly check_call(['python', 'setup.py', 'clean', 'build'], quiet=quiet) - check_call(['./overviewer.py', '-d'] + overviewerargs, quiet=quiet) + check_call([overviewer_script, '-d'] + overviewerargs, quiet=quiet) starttime = time.time() - check_call(['./overviewer.py',] + overviewerargs + [tempdir,], quiet=quiet) + check_call([overviewer_script,] + overviewerargs + [tempdir,], quiet=quiet) endtime = time.time() return endtime - starttime diff --git a/googlemap.py b/googlemap.py index a27d175..dde8ad7 100644 --- a/googlemap.py +++ b/googlemap.py @@ -148,7 +148,13 @@ class MapGen(object): # write out the default marker table with open(os.path.join(self.destdir, "markers.js"), 'w') as output: - output.write("var markerData=%s" % json.dumps(self.world.POI)) + output.write("var markerData=[\n") + for marker in self.world.POI: + output.write(json.dumps(marker)) + if marker != self.world.POI[-1]: + output.write(",") + output.write("\n") + output.write("]\n") # save persistent data self.world.persistentData['POI'] = self.world.POI diff --git a/optimizeimages.py b/optimizeimages.py index 6fe8b78..e1699b0 100644 --- a/optimizeimages.py +++ b/optimizeimages.py @@ -23,11 +23,14 @@ advdef = "advdef" def check_programs(level): path = os.environ.get("PATH").split(os.pathsep) - + + def exists_in_path(prog): + result = filter(lambda x: os.path.exists(os.path.join(x, prog)), path) + return len(result) != 0 + for prog,l in [(pngcrush,1), (optipng,2), (advdef,2)]: if l <= level: - result = filter(lambda x: os.path.exists(os.path.join(x, prog)), path) - if len(result) == 0: + if (not exists_in_path(prog)) and (not exists_in_path(prog + ".exe")): raise Exception("Optimization prog %s for level %d not found!" % (prog, l)) def optimize_image(imgpath, imgformat, optimizeimg): diff --git a/quadtree.py b/quadtree.py index ac1c26c..d4018f6 100644 --- a/quadtree.py +++ b/quadtree.py @@ -453,11 +453,13 @@ class QuadtreeGen(object): ypos = -96 + (row-rowstart)*96 # draw the chunk! - a = chunk.ChunkRenderer((chunkx, chunky), world, rendermode, poi_queue) - a.chunk_render(tileimg, xpos, ypos, None) -# chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), self, False, None) - - + try: + a = chunk.ChunkRenderer((chunkx, chunky), world, rendermode, poi_queue) + a.chunk_render(tileimg, xpos, ypos, None) + except chunk.ChunkCorrupt: + # an error was already printed + pass + # Save them tileimg.save(imgpath) diff --git a/sample.settings.py b/sample.settings.py index 7ee5011..ee0d050 100644 --- a/sample.settings.py +++ b/sample.settings.py @@ -1 +1,149 @@ -# TODO: put something useful in this file! +################################################################################ +# Please see the README or https://github.com/brownan/Minecraft-Overviewer/wiki/DTT-Upgrade-Guide +# for more details. + +# To use this file, simply copy it to settings.py and make any necessary changes +# to suite your needs. + +# This file is a python script, so you can import and python module you wish or +# use any built-in python function, though this is not normally necessary + +# Lines that start with a hash mark are comments + +# Some variables come with defaults (like procs or rendermode) +# If you specify a configuration option in both a settings.py file and on the +# command line, the value from the command line will take precedence + +################################################################################ +### procs +## Specify the number of processors to use for rendering +## Default: The number of CPU cores on your machine +## Type: integer +## Example: set the number of processors to use to be 1 less than the number of +## CPU cpus in your machine + +import multiprocessing +procs = multiprocessing.cpu_count() - 1 +if procs < 1: procs = 1 + + +################################################################################ +### zoom +## Sets the zoom level manually instead of calculating it. This can be useful +## if you have outlier chunks that make your world too big. This value will +## make the highest zoom level contain (2**ZOOM)^2 tiles +## Normally you should not need to set this variable. +## Default: Automatically calculated from your world +## Type: integer +## Example: + +zoom = 9 + +################################################################################ +### regionlist +## A file containing, on each line, a path to a chunkfile to update. Instead +## of scanning the world directory for chunks, it will just use this list. +## Normal caching rules still apply. +## Default: not yet +## Type: string +## Example: Dynamically create regionlist of only regions older than 2 days + +import os, time +regionDir = os.path.join(args[0], "region") +regionFiles = filter(lambda x: x.endswith(".mcr"), os.listdir(regionDir)) +def olderThanTwoDays(f): + return time.time() - os.stat(f).st_mtime > (60*60*24*2) +oldRegionFiles = filter(olderThanTwoDays, regionFiles) +with open("regionlist.txt", "w") as f: + f.write("\n".join(oldRegionFiles)) + + +################################################################################ +### rendermode +## Specifies the render types +## Default: "normal" +## Type: Either a list of strings, or a single string containing modes separated +## by commas +## Example: Render the using the 'lighting' mode, but if today is Sunday, then +## also render the 'night' mode + +import time +rendermode=["lighting"] +if time.localtime().tm_wday == 6: + rendermode.append("night") + + +################################################################################ +### imgformat +## The image output format to use. Currently supported: png(default), jpg. +## NOTE: png will always be used as the intermediate image format. +## Default: not yet +## Type: string +## Example: + +imgformat = "jpg" + + + +################################################################################ +### optimizeimg +## If using png, perform image file size optimizations on the output. Specify 1 +## for pngcrush, 2 for pngcrush+optipng+advdef. This may double (or more) +## render times, but will produce up to 30% smaller images. NOTE: requires +## corresponding programs in $PATH or %PATH% +## Default: not set +## Type: integer +## Example: + +if imgformat != "jpg": + optimizeimg = 2 + + + +################################################################################ +### web_assets_hook +## If provided, run this function after the web assets have been copied, but +## before actual tile rendering beings. It should accept a QuadtreeGen +## object as its only argument. Note: this is only called if skipjs is True +## Default: not yet +## Type: function +## Example: Call an external program to generate something useful + +def web_assets_hook(o): + import subprocess + p = subprocess.Popen(["/path/to/my/script.pl", "--output_dir", args[1]]) + p.wait() + if p.returncode != 0: + raise Exception("web_assets_hook failed") + + + +################################################################################ +### quiet +## Print less output. You can specify higher values to suppress additional output +## Default: 0 +## Type: integer +## Example: +quiet = 1 + + +################################################################################ +### verbose +## Print more output. You can specify higher values to print additional output +## Default: 0 +## Type: integer +## Example: +verbose = 1 + + +################################################################################ +### skipjs +## Don't output marker.js or region.js +## Default: False +## Type: boolean +## Example: Set skipjs if web_assets_hook is defined + +if "web_assets_hook" in locals(): + skipjs = True + + diff --git a/textures.py b/textures.py index a94030c..e3e714c 100644 --- a/textures.py +++ b/textures.py @@ -272,10 +272,10 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None build a full block with four differnts faces. All images should be 16x16 image objects. Returns a 24x24 image. Can be used to render any block. - side1 is in the -y face of the cube (top left) - side2 is in the +x (top right) - side3 is in the -x (bottom left) - side4 is in the +y (bottom right) + side1 is in the -y face of the cube (top left, east) + side2 is in the +x (top right, south) + 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 @@ -354,9 +354,9 @@ def _build_blockimages(): # 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, # Gold/iron blocks? Doublestep? TNT from above? # 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 - 36, 37, -1, -1, 65, 4, 25, -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, 25, -1, 98, 24, -1, -1, 86, -1, -1, -1, # Torch from above? leaving out fire. Redstone wire? Crops/furnaces handled elsewhere. sign post # 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 - -1, -1, -1, 16, -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, # door,ladder left out. Minecart rail orientation, redstone torches # 80 81 82 83 84 85 86 87 88 89 90 91 66, 69, 72, 73, 74, -1,102,103,104,105,-1, 102 # clay? ] @@ -371,9 +371,9 @@ def _build_blockimages(): # 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, 4, 25,101, 98, 24, -1, -1, 86, -1, -1, -1, + 36, 37, -1, -1, 65, -1, 25,101, 98, 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, 16, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67, + -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 ] @@ -663,6 +663,75 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"), img.split()[3]) + if blockID in (53,67): # wooden and cobblestone stairs. + + if blockID == 53: # wooden + texture = terrain_images[4] + + elif blockID == 67: # cobblestone + texture = terrain_images[16] + + side = texture.copy() + half_block_u = texture.copy() # up, down, left, right + half_block_d = texture.copy() + half_block_l = texture.copy() + half_block_r = texture.copy() + + # generate needed geometries + ImageDraw.Draw(side).rectangle((0,0,7,6),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(half_block_u).rectangle((0,8,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(half_block_d).rectangle((0,0,15,6),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(half_block_l).rectangle((8,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(half_block_r).rectangle((0,0,7,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + if data == 0: # ascending south + img = _build_full_block(half_block_r, None, None, half_block_d, side.transpose(Image.FLIP_LEFT_RIGHT)) + tmp1 = transform_image_side(half_block_u) + + # Darken the vertical part of the second step + sidealpha = tmp1.split()[3] + # darken it a bit more than usual, looks better + tmp1 = ImageEnhance.Brightness(tmp1).enhance(0.8) + tmp1.putalpha(sidealpha) + + composite.alpha_over(img, tmp1, (6,3)) + tmp2 = transform_image(half_block_l) + composite.alpha_over(img, tmp2, (0,6)) + + elif data == 1: # ascending north + img = Image.new("RGBA", (24,24), (38,92,255,0)) # first paste the texture in the back + tmp1 = transform_image(half_block_r) + composite.alpha_over(img, tmp1, (0,6)) + tmp2 = _build_full_block(half_block_l, None, None, texture, side) + composite.alpha_over(img, tmp2) + + elif data == 2: # ascending west + img = Image.new("RGBA", (24,24), (38,92,255,0)) # first paste the texture in the back + tmp1 = transform_image(half_block_u) + composite.alpha_over(img, tmp1, (0,6)) + tmp2 = _build_full_block(half_block_d, None, None, side, texture) + composite.alpha_over(img, tmp2) + + elif data == 3: # ascending east + img = _build_full_block(half_block_u, None, None, side.transpose(Image.FLIP_LEFT_RIGHT), half_block_d) + tmp1 = transform_image_side(half_block_u).transpose(Image.FLIP_LEFT_RIGHT) + + # Darken the vertical part of the second step + sidealpha = tmp1.split()[3] + # darken it a bit more than usual, looks better + tmp1 = ImageEnhance.Brightness(tmp1).enhance(0.7) + tmp1.putalpha(sidealpha) + + composite.alpha_over(img, tmp1, (6,3)) + tmp2 = transform_image(half_block_d) + composite.alpha_over(img, tmp2, (0,6)) + + # touch up a (horrible) pixel + img.putpixel((18,3),(0,0,0,0)) + + return (img.convert("RGB"), img.split()[3]) + + if blockID == 55: # redstone wire if data & 0b1000000 == 64: # powered redstone wire @@ -1146,8 +1215,8 @@ 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, 9, 17, 18, 23, 35, 43, 44, 50, 51, 55, 58, 59, \ - 61, 62, 64, 65, 66, 71, 75, 76, 85, 86, 91, 92]) +special_blocks = set([2, 9, 17, 18, 23, 35, 43, 44, 50, 51, 53, 55, 58, 59, \ + 61, 62, 64, 65, 66, 67, 71, 75, 76, 85, 86, 91, 92]) # this is a map of special blockIDs to a list of all # possible values for ancillary data that it might have. @@ -1162,14 +1231,16 @@ special_map[43] = range(4) # stone, sandstone, wooden and cobblestone double-sl special_map[44] = range(4) # stone, sandstone, wooden and cobblestone slab special_map[50] = (1,2,3,4,5) # torch, position in the block special_map[51] = range(16) # fire, position in the block (not implemented) +special_map[53] = range(4) # wooden stairs, orientation special_map[55] = range(128) # redstone wire, all the possible combinations special_map[58] = (0,) # crafting table special_map[59] = range(8) # crops, grow from 0 to 7 special_map[61] = range(6) # furnace, orientation (not implemented) special_map[62] = range(6) # burning furnace, orientation (not implemented) special_map[64] = range(16) # wooden door, open/close and orientation -special_map[65] = (2,3,4,5) # ladder, orientation (not implemented) +special_map[65] = (2,3,4,5) # ladder, orientation special_map[66] = range(10) # minecrart tracks, orientation, slope +special_map[67] = range(4) # cobblestone stairs, orientation special_map[71] = range(16) # iron door, open/close and orientation special_map[75] = (1,2,3,4,5) # off redstone torch, orientation special_map[76] = (1,2,3,4,5) # on redstone torch, orientation diff --git a/web_assets/functions.js b/web_assets/functions.js index b59c79e..7bb170f 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -186,7 +186,12 @@ function initMarkers() { if (markersInit) { return; } markersInit = true; - + + // first, give all collections an empty array to work with + for (i in signGroups) { + markerCollection[signGroups[i].label] = []; + } + for (i in markerData) { var item = markerData[i]; @@ -228,11 +233,8 @@ function initMarkers() { icon: iconURL, visible: false }); - if (markerCollection[label]) { - markerCollection[label].push(marker); - } else { - markerCollection[label] = [marker]; - } + + markerCollection[label].push(marker); if (item.type == 'sign') { prepareSignMarker(marker, item);