From e989e97c5eff7613f2799801f1c4f9f5ec2dbd7e Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Fri, 31 Dec 2010 00:53:57 -0500 Subject: [PATCH 001/213] Added a new config file parser. The new config file parser has an interface that's nearly identical to the OptionParser of optparse. Below is a sample settings.py config file: $ cat settings.py import multiprocessing if 'rendermode' not in locals(): rendermode="lighting" cachedir = "cache.%s.cachedir" % rendermode procs = multiprocessing.cpu_count() - 1 --- configParser.py | 134 ++++++++++++++++++++++++++++++++++++++++++++++++ gmap.py | 19 ++++--- world.py | 8 +-- 3 files changed, 150 insertions(+), 11 deletions(-) create mode 100644 configParser.py diff --git a/configParser.py b/configParser.py new file mode 100644 index 0000000..f7bdb65 --- /dev/null +++ b/configParser.py @@ -0,0 +1,134 @@ +from optparse import OptionParser +import sys + +class OptionsResults(object): + pass + +class ConfigOptionParser(object): + def __init__(self, **kwargs): + self.cmdParser = OptionParser(usage=kwargs.get("usage","")) + self.configFile = kwargs.get("config","settings.py") + self.configVars = [] + + # these are arguments not understood by OptionParser, so they must be removed + # in add_option before being passed to the OptionParser + # note that default is a valid OptionParser argument, but we remove it + # because we want to do our default value handling + self.customArgs = ["required", "commandLineOnly", "default"] + + self.requiredArgs = [] + + def display_config(self): + for x in self.configVars: + n = x['dest'] + print "%s: %r" % (n, self.configResults.__dict__[n]) + + def add_option(self, *args, **kwargs): + self.configVars.append(kwargs.copy()) + + if not kwargs.get("configFileOnly", False): + for arg in self.customArgs: + if arg in kwargs.keys(): del kwargs[arg] + + self.cmdParser.add_option(*args, **kwargs) + + def print_help(self): + self.cmdParser.print_help() + + def parse_args(self): + + # first, load the results from the command line: + options, args = self.cmdParser.parse_args() + + + # second, use these values to seed the locals dict + l = dict() + g = dict() + for a in self.configVars: + n = a['dest'] + if a.get('configFileOnly', False): continue + if a.get('commandLineOnly', False): continue + if getattr(options, n) != None: + l[n] = getattr(options, n) + g['args'] = args + try: + execfile(self.configFile, g, l) + except NameError, ex: + pass + except SyntaxError, ex: + print "Error parsing %s. Please check the trackback below:" % self.configFile + import traceback + traceback.print_exc() + tb = sys.exc_info()[2] + #print tb.tb_frame.f_code.co_filename + sys.exit(1) + + #print l.keys() + + configResults = OptionsResults() + # first, load the results from the config file: + for a in self.configVars: + n = a['dest'] + if a.get('commandLineOnly', False): + if n in l.keys(): + print "Error: %s can only be specified on the command line. It is not valid in the config file" % n + sys.exit(1) + + configResults.__dict__[n] = l.get(n) + + + + # third, merge options into configReslts (with options overwriting anything in configResults) + for a in self.configVars: + n = a['dest'] + if a.get('configFileOnly', False): continue + if getattr(options, n) != None: + configResults.__dict__[n] = getattr(options, n) + + # forth, set defaults for any empty values + for a in self.configVars: + n = a['dest'] + if (n not in configResults.__dict__.keys() or configResults.__dict__[n] == None) and 'default' in a.keys(): + configResults.__dict__[n] = a['default'] + + # fifth, check required args: + for a in self.configVars: + n = a['dest'] + if configResults.__dict__[n] == None and a.get('required',False): + raise Exception("%s is required" % n) + + # sixth, check types + for a in self.configVars: + n = a['dest'] + if 'type' in a.keys() and configResults.__dict__[n] != None: + try: + # switch on type. there are only 6 types that can be used with optparse + if a['type'] == "int": + configResults.__dict__[n] = int(configResults.__dict__[n]) + elif a['type'] == "string": + configResults.__dict__[n] = str(configResults.__dict__[n]) + elif a['type'] == "long": + configResults.__dict__[n] = long(configResults.__dict__[n]) + elif a['type'] == "choice": + if configResults.__dict__[n] not in a['choices']: + print "The value '%s' is not valid for config parameter '%s'" % (configResults.__dict__[n], n) + sys.exit(1) + elif a['type'] == "float": + configResults.__dict__[n] = long(configResults.__dict__[n]) + elif a['type'] == "complex": + configResults.__dict__[n] = complex(configResults.__dict__[n]) + else: + print "Unknown type!" + sys.exit(1) + except ValueError, ex: + print "There was a problem converting the value '%s' to type %s for config parameter '%s'" % (configResults.__dict__[n], a['type'], n) + import traceback + #traceback.print_exc() + sys.exit(1) + + + + self.configResults = configResults + + return configResults, args + diff --git a/gmap.py b/gmap.py index 8a41c40..728ef77 100755 --- a/gmap.py +++ b/gmap.py @@ -22,7 +22,7 @@ if not (sys.version_info[0] == 2 and sys.version_info[1] >= 6): import os import os.path -from optparse import OptionParser +from configParser import ConfigOptionParser import re import multiprocessing import time @@ -43,20 +43,20 @@ def main(): cpus = multiprocessing.cpu_count() except NotImplementedError: cpus = 1 - parser = OptionParser(usage=helptext) + parser = ConfigOptionParser(usage=helptext, config="settings.py") parser.add_option("-p", "--processes", dest="procs", help="How many worker processes to start. Default %s" % cpus, default=cpus, action="store", type="int") parser.add_option("-z", "--zoom", dest="zoom", help="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", action="store", type="int") - parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true") + parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true", commandLineOnly=True) parser.add_option("--cachedir", dest="cachedir", help="Sets the directory where the Overviewer will save chunk images, which is an intermediate step before the tiles are generated. You must use the same directory each time to gain any benefit from the cache. If not set, this defaults to your world directory.") parser.add_option("--chunklist", dest="chunklist", help="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.") - parser.add_option("--lighting", dest="lighting", help="Renders shadows using light data from each chunk.", action="store_true") - parser.add_option("--night", dest="night", help="Renders shadows using light data from each chunk, as if it were night. Implies --lighting.", action="store_true") - parser.add_option("--spawn", dest="spawn", help="Renders shadows using light data from each chunk, as if it were night, while also highlighting areas that are dark enough to spawn mobs. Implies --lighting and --night.", action="store_true") + parser.add_option("--rendermode", dest="rendermode", help="Specifies the render type: normal (default), lighting, night, or spawn.", type="choice", choices=["normal", "lighting", "night", "spawn"], required=True, default="normal") parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.") parser.add_option("--optimize-img", dest="optimizeimg", help="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%") parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.") parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, help="Print more output. You can specify this option multiple times.") parser.add_option("--skip-js", dest="skipjs", action="store_true", help="Don't output marker.js or regions.js") + parser.add_option("--display-config", dest="display_config", action="store_true", help="Display the configuration parameters, but don't render the map", commandLineOnly=True) + #parser.add_option("--write-config", dest="write_config", action="store_true", help="Writes out a sample config file", commandLineOnly=True) options, args = parser.parse_args() @@ -87,6 +87,11 @@ def main(): parser.error("Where do you want to save the tiles?") destdir = args[1] + if options.display_config: + # just display the config file and exit + parser.display_config() + sys.exit(0) + if options.delete: return delete_all(cachedir, destdir) @@ -124,7 +129,7 @@ def main(): logging.info("Notice: Not using biome data for tinting") # First generate the world's chunk images - w = world.WorldRenderer(worlddir, cachedir, chunklist=chunklist, lighting=options.lighting, night=options.night, spawn=options.spawn, useBiomeData=useBiomeData) + w = world.WorldRenderer(worlddir, cachedir, chunklist=chunklist, rendermode=options.rendermode, useBiomeData=useBiomeData) w.go(options.procs) diff --git a/world.py b/world.py index 5064baf..d1ea20e 100644 --- a/world.py +++ b/world.py @@ -103,12 +103,12 @@ class WorldRenderer(object): files to update. If it includes a trailing newline, it is stripped, so you can pass in file handles just fine. """ - def __init__(self, worlddir, cachedir, chunklist=None, lighting=False, night=False, spawn=False, useBiomeData=False): + def __init__(self, worlddir, cachedir, chunklist=None, rendermode="normal", useBiomeData=False): self.worlddir = worlddir self.caves = False - self.lighting = lighting or night or spawn - self.night = night or spawn - self.spawn = spawn + self.lighting = rendermode in ("lighting","night","spawn") + self.night = rendermode in ("night","spawn") + self.spawn = rendermode in ("spawn",) self.cachedir = cachedir self.useBiomeData = useBiomeData From feeb3283e0b27d23b27ff922c1427cc69e6e36bc Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 8 Jan 2011 01:21:41 -0500 Subject: [PATCH 002/213] New benchmarking script Useful for examining how a code change affects performance --- chunk.py | 3 ++- contrib/benchmark.py | 51 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 contrib/benchmark.py diff --git a/chunk.py b/chunk.py index 13a387b..7a5fc15 100644 --- a/chunk.py +++ b/chunk.py @@ -924,7 +924,8 @@ class ChunkRenderer(object): # check to see if there are any signs in the persistentData list that are from this chunk. # if so, remove them from the persistentData list (since they're have been added to the world.POI # list above. - self.queue.put(['removePOI', (self.chunkX, self.chunkY)]) + if self.queue: + self.queue.put(['removePOI', (self.chunkX, self.chunkY)]) return img diff --git a/contrib/benchmark.py b/contrib/benchmark.py new file mode 100644 index 0000000..145654e --- /dev/null +++ b/contrib/benchmark.py @@ -0,0 +1,51 @@ +import chunk +import world +import tempfile +import glob +import time +import cProfile +import os +import sys +import shutil + +# Simple Benchmarking script. Usage and example: + +# $ python contrib/benchmark.py World4/ +# Rendering 50 chunks... +# Took 20.290062 seconds or 0.405801 seconds per chunk, or 2.464261 chunks per second + + +# create a new, empty, cache dir +cachedir = tempfile.mkdtemp(prefix="benchmark_cache", dir=".") +if os.path.exists("benchmark.prof"): os.unlink("benchmark.prof") + +w = world.WorldRenderer("World4", cachedir) + +numchunks = 50 +chunklist = w._find_chunkfiles()[:numchunks] + +print "Rendering %d chunks..." % (numchunks) +def go(): + for f in chunklist: + chunk.render_and_save(f[2], w.cachedir, w, (None,None), None) +start = time.time() +if "-profile" in sys.argv: + cProfile.run("go()", 'benchmark.prof') +else: + go() +stop = time.time() + +delta = stop - start + +print "Took %f seconds or %f seconds per chunk, or %f chunks per second" % (delta, delta/numchunks, numchunks/delta) + +if "-profile" in sys.argv: + print "Profile is below:\n----\n" + + import pstats + p = pstats.Stats('benchmark.prof') + + p.strip_dirs().sort_stats("cumulative").print_stats(20) + + +shutil.rmtree(cachedir) From f46d0ce4addb71c2944a034c8f3ffad645b274c8 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 22 Jan 2011 14:23:20 -0500 Subject: [PATCH 003/213] options with default args are now seeded in the globals dict --- configParser.py | 24 ++++++++++++++++++------ gmap.py | 8 ++++---- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/configParser.py b/configParser.py index f7bdb65..0f43a34 100644 --- a/configParser.py +++ b/configParser.py @@ -24,6 +24,10 @@ class ConfigOptionParser(object): print "%s: %r" % (n, self.configResults.__dict__[n]) def add_option(self, *args, **kwargs): + + if kwargs.get("configFileOnly", False) and kwargs.get("commandLineOnly", False): + raise Exception(args, "configFileOnly and commandLineOnly are mututally exclusive") + self.configVars.append(kwargs.copy()) if not kwargs.get("configFileOnly", False): @@ -40,7 +44,6 @@ class ConfigOptionParser(object): # first, load the results from the command line: options, args = self.cmdParser.parse_args() - # second, use these values to seed the locals dict l = dict() g = dict() @@ -48,25 +51,34 @@ class ConfigOptionParser(object): n = a['dest'] if a.get('configFileOnly', False): continue if a.get('commandLineOnly', False): continue - if getattr(options, n) != None: - l[n] = getattr(options, n) + v = getattr(options, n) + if v != None: + #print "seeding %s with %s" % (n, v) + l[n] = v + else: + # if this has a default, use that to seed the globals dict + if a.get("default", None): g[n] = a['default'] g['args'] = args + try: execfile(self.configFile, g, l) except NameError, ex: - pass + import traceback + traceback.print_exc() + print "\nError parsing %s. Please check the trackback above" % self.configFile + sys.exit(1) except SyntaxError, ex: - print "Error parsing %s. Please check the trackback below:" % self.configFile import traceback traceback.print_exc() tb = sys.exc_info()[2] #print tb.tb_frame.f_code.co_filename + print "\nError parsing %s. Please check the trackback above" % self.configFile sys.exit(1) #print l.keys() configResults = OptionsResults() - # first, load the results from the config file: + # third, load the results from the config file: for a in self.configVars: n = a['dest'] if a.get('commandLineOnly', False): diff --git a/gmap.py b/gmap.py index 5ab81d4..749a41b 100755 --- a/gmap.py +++ b/gmap.py @@ -46,17 +46,17 @@ def main(): cpus = 1 parser = ConfigOptionParser(usage=helptext, config="settings.py") parser.add_option("-p", "--processes", dest="procs", help="How many worker processes to start. Default %s" % cpus, default=cpus, action="store", type="int") - parser.add_option("-z", "--zoom", dest="zoom", help="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", action="store", type="int") + parser.add_option("-z", "--zoom", dest="zoom", help="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", action="store", type="int", configFileOnly=True) parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true", commandLineOnly=True) parser.add_option("--cachedir", dest="cachedir", help="Sets the directory where the Overviewer will save chunk images, which is an intermediate step before the tiles are generated. You must use the same directory each time to gain any benefit from the cache. If not set, this defaults to your world directory.") parser.add_option("--chunklist", dest="chunklist", help="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.") parser.add_option("--rendermode", dest="rendermode", help="Specifies the render type: normal (default), lighting, night, or spawn.", type="choice", choices=["normal", "lighting", "night", "spawn"], required=True, default="normal") - parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.") - parser.add_option("--optimize-img", dest="optimizeimg", help="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%") + parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.", configFileOnly=True ) + parser.add_option("--optimize-img", dest="optimizeimg", help="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%", configFileOnly=True) parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.") parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, help="Print more output. You can specify this option multiple times.") parser.add_option("--skip-js", dest="skipjs", action="store_true", help="Don't output marker.js or regions.js") - parser.add_option("--display-config", dest="display_config", action="store_true", help="Display the configuration parameters, but don't render the map", commandLineOnly=True) + parser.add_option("--display-config", dest="display_config", action="store_true", help="Display the configuration parameters, but don't render the map. Requires all required options to be specified", commandLineOnly=True) #parser.add_option("--write-config", dest="write_config", action="store_true", help="Writes out a sample config file", commandLineOnly=True) options, args = parser.parse_args() From 5485e684b5c4db7994ae1e675fc78443cd540b32 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 1 Mar 2011 10:34:34 -0500 Subject: [PATCH 004/213] gutted gmap.py in preperation for direct-to-tile rendering --- gmap.py | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/gmap.py b/gmap.py index acfacb1..14ffd43 100755 --- a/gmap.py +++ b/gmap.py @@ -48,7 +48,6 @@ def main(): parser.add_option("-p", "--processes", dest="procs", help="How many worker processes to start. Default %s" % cpus, default=cpus, action="store", type="int") parser.add_option("-z", "--zoom", dest="zoom", help="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", action="store", type="int") parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true") - parser.add_option("--cachedir", dest="cachedir", help="Sets the directory where the Overviewer will save chunk images, which is an intermediate step before the tiles are generated. You must use the same directory each time to gain any benefit from the cache. If not set, this defaults to your world directory.") parser.add_option("--chunklist", dest="chunklist", help="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.") parser.add_option("--lighting", dest="lighting", help="Renders shadows using light data from each chunk.", action="store_true") parser.add_option("--night", dest="night", help="Renders shadows using light data from each chunk, as if it were night. Implies --lighting.", action="store_true") @@ -89,11 +88,6 @@ def main(): parser.print_help() sys.exit(1) - if not options.cachedir: - cachedir = worlddir - else: - cachedir = options.cachedir - if len(args) != 2: if options.delete: return delete_all(cachedir, None) @@ -102,7 +96,7 @@ def main(): destdir = args[1] if options.delete: - return delete_all(cachedir, destdir) + return delete_all(destdir) if options.chunklist: chunklist = open(options.chunklist, 'r') @@ -139,28 +133,17 @@ def main(): logging.info("Notice: Not using biome data for tinting") # First generate the world's chunk images - w = world.WorldRenderer(worlddir, cachedir, chunklist=chunklist, lighting=options.lighting, night=options.night, spawn=options.spawn, useBiomeData=useBiomeData) + #w = world.WorldRenderer(worlddir, cachedir, chunklist=chunklist, lighting=options.lighting, night=options.night, spawn=options.spawn, useBiomeData=useBiomeData) - w.go(options.procs) + #w.go(options.procs) # Now generate the tiles - q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg) - q.write_html(options.skipjs) - q.go(options.procs) + #q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg) + #q.write_html(options.skipjs) + #q.go(options.procs) -def delete_all(worlddir, tiledir): - # First delete all images in the world dir - imgre = r"img\.[^.]+\.[^.]+\.nocave\.\w+\.png$" - matcher = re.compile(imgre) - - for dirpath, dirnames, filenames in os.walk(worlddir): - for f in filenames: - if matcher.match(f): - filepath = os.path.join(dirpath, f) - logging.info("Deleting {0}".format(filepath)) - os.unlink(filepath) - - # Now delete all /hash/ files in the tile dir. +def delete_all(tiledir): + # Delete all /hash/ files in the tile dir. if tiledir: for dirpath, dirnames, filenames in os.walk(tiledir): for f in filenames: From f34d9739e95f2940f6c8d08cb663d4d3423941c0 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 1 Mar 2011 11:16:20 -0500 Subject: [PATCH 005/213] removed rendering code from WorldRenderer (now just World) --- gmap.py | 7 +- world.py | 294 ++++++++++--------------------------------------------- 2 files changed, 53 insertions(+), 248 deletions(-) diff --git a/gmap.py b/gmap.py index 14ffd43..b104e78 100755 --- a/gmap.py +++ b/gmap.py @@ -132,10 +132,9 @@ def main(): if not useBiomeData: logging.info("Notice: Not using biome data for tinting") - # First generate the world's chunk images - #w = world.WorldRenderer(worlddir, cachedir, chunklist=chunklist, lighting=options.lighting, night=options.night, spawn=options.spawn, useBiomeData=useBiomeData) - - #w.go(options.procs) + # First do world-level preprocessing + w = world.World(worlddir, useBiomeData=useBiomeData) + w.go(options.procs) # Now generate the tiles #q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg) diff --git a/world.py b/world.py index 1af190a..9c47799 100644 --- a/world.py +++ b/world.py @@ -31,8 +31,7 @@ import nbt import textures """ -This module has routines related to generating all the chunks for a world -and for extracting information about available worlds +This module has routines for extracting information about available worlds """ @@ -40,34 +39,14 @@ base36decode = functools.partial(int, base=36) cached = collections.defaultdict(dict) -def _convert_coords(chunks): - """Takes the list of (chunkx, chunky, chunkfile) where chunkx and chunky - are in the chunk coordinate system, and figures out the row and column in - the image each one should be. +def _convert_coords(chunkx, chunky): + """Takes a coordinate (chunkx, chunky) where chunkx and chunky are + in the chunk coordinate system, and figures out the row and column + in the image each one should be. Returns (col, row).""" - returns mincol, maxcol, minrow, maxrow, chunks_translated - chunks_translated is a list of (col, row, (chunkX, chunkY)) - - The (chunkX, chunkY) tuple is the chunkCoords, used to identify the - chunk file - """ - chunks_translated = [] # columns are determined by the sum of the chunk coords, rows are the - # difference - item = chunks[0] - mincol = maxcol = item[0] + item[1] - minrow = maxrow = item[1] - item[0] - for c in chunks: - col = c[0] + c[1] - mincol = min(mincol, col) - maxcol = max(maxcol, col) - row = c[1] - c[0] - minrow = min(minrow, row) - maxrow = max(maxrow, row) - chunks_translated.append((col, row, (c[0],c[1]))) - - return mincol, maxcol, minrow, maxrow, chunks_translated - + # difference (TODO: be able to change direction of north) + return (chunkx + chunky, chunky - chunkx) def base36encode(number, alphabet='0123456789abcdefghijklmnopqrstuvwxyz'): ''' @@ -91,33 +70,19 @@ def base36encode(number, alphabet='0123456789abcdefghijklmnopqrstuvwxyz'): return "-" + base36 return base36 -class FakeAsyncResult: - def __init__(self, string): - self.string = string - def get(self): - return self.string - -class WorldRenderer(object): - """Renders a world's worth of chunks. +class World(object): + """Does world-level preprocessing to prepare for QuadtreeGen worlddir is the path to the minecraft world - cachedir is the path to a directory that should hold the resulting images. - It may be the same as worlddir (which used to be the default). - - If chunklist is given, it is assumed to be an iterator over paths to chunk - files to update. If it includes a trailing newline, it is stripped, so you - can pass in file handles just fine. """ - def __init__(self, worlddir, cachedir, chunklist=None, lighting=False, night=False, spawn=False, useBiomeData=False): + + mincol = maxcol = minrow = maxrow = 0 + + def __init__(self, worlddir, useBiomeData=False): self.worlddir = worlddir - self.caves = False - self.lighting = lighting or night or spawn - self.night = night or spawn - self.spawn = spawn - self.cachedir = cachedir self.useBiomeData = useBiomeData # figure out chunk format is in use - # if mcregion, error out early until we can add support + # if not mcregion, error out early data = nbt.load(os.path.join(self.worlddir, "level.dat"))[1]['Data'] #print data if not ('version' in data and data['version'] == 19132): @@ -127,22 +92,6 @@ class WorldRenderer(object): if self.useBiomeData: textures.prepareBiomeData(worlddir) - self.chunklist = chunklist - - # In order to avoid having to look up the cache file names in - # ChunkRenderer, get them all and store them here - # TODO change how caching works - for root, dirnames, filenames in os.walk(cachedir): - for filename in filenames: - if not filename.endswith('.png') or not filename.startswith("img."): - continue - dirname, dir_b = os.path.split(root) - _, dir_a = os.path.split(dirname) - _, x, z, cave, _ = filename.split('.', 4) - dir = '/'.join((dir_a, dir_b)) - bits = '.'.join((x, z, cave)) - cached[dir][bits] = os.path.join(root, filename) - # stores Points Of Interest to be mapped with markers # a list of dictionaries, see below for an example self.POI = [] @@ -151,7 +100,8 @@ class WorldRenderer(object): # info self.persistentData. This dictionary can hold any information # that may be needed between runs. # Currently only holds into about POIs (more more details, see quadtree) - self.pickleFile = os.path.join(self.cachedir,"overviewer.dat") + # TODO maybe store this with the tiles, not with the world? + self.pickleFile = os.path.join(self.worlddir, "overviewer.dat") if os.path.exists(self.pickleFile): with open(self.pickleFile,"rb") as p: self.persistentData = cPickle.load(p) @@ -159,42 +109,6 @@ class WorldRenderer(object): # some defaults self.persistentData = dict(POI=[]) - - - def _get_chunk_renderset(self): - """Returns a set of (col, row) chunks that should be rendered. Returns - None if all chunks should be rendered""" - if not self.chunklist: - return None - - raise Exception("not yet working") ## TODO correctly reimplement this for mcregion - # Get a list of the (chunks, chunky, filename) from the passed in list - # of filenames - chunklist = [] - for path in self.chunklist: - if path.endswith("\n"): - path = path[:-1] - f = os.path.basename(path) - if f and f.startswith("c.") and f.endswith(".dat"): - p = f.split(".") - chunklist.append((base36decode(p[1]), base36decode(p[2]), - path)) - - if not chunklist: - logging.error("No valid chunks specified in your chunklist!") - logging.error("HINT: chunks are in your world directory and have names of the form 'c.*.*.dat'") - sys.exit(1) - - # Translate to col, row coordinates - _, _, _, _, chunklist = _convert_coords(chunklist) - - # Build a set from the col, row pairs - inclusion_set = set() - for col, row, filename in chunklist: - inclusion_set.add((col, row)) - - return inclusion_set - def get_region_path(self, chunkX, chunkY): """Returns the path to the region that contains chunk (chunkX, chunkY) """ @@ -239,20 +153,41 @@ class WorldRenderer(object): msg="Spawn", type="spawn", chunk=(inChunkX,inChunkZ))) def go(self, procs): - """Starts the render. This returns when it is finished""" + """Scan the world directory, to fill in + self.{min,max}{col,row} for use later in quadtree.py. This + also does other world-level processing.""" logging.info("Scanning chunks") - raw_chunks = self._get_chunklist() + # find the dimensions of the map, in region files + minx = maxx = miny = maxy = 0 + found_regions = False + for x, y, regionfile in self._iterate_regionfiles(): + found_regions = True + minx = min(minx, x) + maxx = max(maxx, x) + miny = min(miny, y) + maxy = max(maxy, y) + if not found_regions: + logging.error("Error: No chunks found!") + sys.exit(1) logging.debug("Done scanning chunks") - + + # turn our region coordinates into chunk coordinates + minx = minx * 32 + miny = miny * 32 + maxx = maxx * 32 + 32 + maxy = maxy * 32 + 32 + # Translate chunks to our diagonal coordinate system - # TODO - mincol, maxcol, minrow, maxrow, chunks = _convert_coords(raw_chunks) - del raw_chunks # Free some memory - - self.chunkmap = self._render_chunks_async(chunks, procs) - logging.debug("world chunkmap has len %d", len(self.chunkmap)) - + mincol = maxcol = minrow = maxrow = 0 + for chunkx, chunky in [(minx, miny), (minx, maxy), (maxx, miny), (maxx, maxy)]: + col, row = _convert_coords(chunkx, chunky) + mincol = min(mincol, col) + maxcol = max(maxcol, col) + minrow = min(minrow, row) + maxrow = max(maxrow, row) + + #logging.debug("map size: (%i, %i) to (%i, %i)" % (mincol, minrow, maxcol, maxrow)) self.mincol = mincol self.maxcol = maxcol @@ -261,147 +196,18 @@ class WorldRenderer(object): self.findTrueSpawn() - def _find_regionfiles(self): - """Returns a list of all of the region files, along with their + def _iterate_regionfiles(self): + """Returns an iterator of all of the region files, along with their coordinates Returns (regionx, regiony, filename)""" - all_chunks = [] for dirpath, dirnames, filenames in os.walk(os.path.join(self.worlddir, 'region')): if not dirnames and filenames and "DIM-1" not in dirpath: for f in filenames: if f.startswith("r.") and f.endswith(".mcr"): p = f.split(".") - all_chunks.append((int(p[1]), int(p[2]), - os.path.join(dirpath, f))) - return all_chunks - - def _get_chunklist(self): - """Returns a list of all possible chunk coordinates, based on the - available regions files. Note that not all chunk coordinates will - exists. The chunkrender will know how to ignore non-existant chunks - - returns a list of (chunkx, chunky, regionfile) where regionfile is - the region file that contains this chunk - - TODO, a --cachedir implemetation should involved thie method - - """ - - all_chunks = [] - - regions = self._find_regionfiles() - logging.debug("Found %d regions",len(regions)) - for region in regions: - these_chunks = list(itertools.product( - range(region[0]*32,region[0]*32 + 32), - range(region[1]*32,region[1]*32 + 32) - )) - these_chunks = map(lambda x: (x[0], x[1], region[2]), these_chunks) - assert(len(these_chunks) == 1024) - all_chunks += these_chunks - - if not all_chunks: - logging.error("Error: No chunks found!") - sys.exit(1) - - logging.debug("Total possible chunks: %d", len(all_chunks)) - return all_chunks - - def _render_chunks_async(self, chunks, processes): - """Starts up a process pool and renders all the chunks asynchronously. - - chunks is a list of (col, row, (chunkX, chunkY)). Use chunkX,chunkY - to find the chunk data in a region file - - Returns a dictionary mapping (col, row) to the file where that - chunk is rendered as an image - """ - # The set of chunks to render, or None for all of them. The logic is - # slightly more compliated than it should seem, since we still need to - # build the results dict out of all chunks, even if they're not being - # rendered. - inclusion_set = self._get_chunk_renderset() - - results = {} - manager = multiprocessing.Manager() - q = manager.Queue() - - if processes == 1: - # Skip the multiprocessing stuff - logging.debug("Rendering chunks synchronously since you requested 1 process") - for i, (col, row, chunkXY) in enumerate(chunks): - ##TODO##/if inclusion_set and (col, row) not in inclusion_set: - ##TODO##/ # Skip rendering, just find where the existing image is - ##TODO##/ _, imgpath = chunk.find_oldimage(chunkfile, cached, self.caves) - ##TODO##/ if imgpath: - ##TODO##/ results[(col, row)] = imgpath - ##TODO##/ continue - - oldimg = chunk.find_oldimage(chunkXY, cached, self.caves) - # TODO remove this shortcircuit - if chunk.check_cache(self, chunkXY, oldimg): - result = oldimg[1] - else: - #logging.debug("check cache failed, need to render (could be ghost chunk)") - result = chunk.render_and_save(chunkXY, self.cachedir, self, oldimg, queue=q) - - if result: - results[(col, row)] = result - if i > 0: - try: - item = q.get(block=False) - if item[0] == "newpoi": - self.POI.append(item[1]) - elif item[0] == "removePOI": - self.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], self.persistentData['POI']) - except Queue.Empty: - pass - if 1000 % i == 0 or i % 1000 == 0: - logging.info("{0}/{1} chunks rendered".format(i, len(chunks))) - else: - logging.debug("Rendering chunks in {0} processes".format(processes)) - pool = multiprocessing.Pool(processes=processes) - asyncresults = [] - for col, row, chunkXY in chunks: - ##TODO/if inclusion_set and (col, row) not in inclusion_set: - ##TODO/ # Skip rendering, just find where the existing image is - ##TODO/ _, imgpath = chunk.find_oldimage(chunkfile, cached, self.caves) - ##TODO/ if imgpath: - ##TODO/ results[(col, row)] = imgpath - ##TODO/ continue - - oldimg = chunk.find_oldimage(chunkXY, cached, self.caves) - if chunk.check_cache(self, chunkXY, oldimg): - result = FakeAsyncResult(oldimg[1]) - else: - result = pool.apply_async(chunk.render_and_save, - args=(chunkXY,self.cachedir,self, oldimg), - kwds=dict(cave=self.caves, queue=q)) - asyncresults.append((col, row, result)) - - pool.close() - - for i, (col, row, result) in enumerate(asyncresults): - results[(col, row)] = result.get() - try: - item = q.get(block=False) - if item[0] == "newpoi": - self.POI.append(item[1]) - elif item[0] == "removePOI": - self.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], self.persistentData['POI']) - - except Queue.Empty: - pass - if i > 0: - if 1000 % i == 0 or i % 1000 == 0: - logging.info("{0}/{1} chunks rendered".format(i, len(asyncresults))) - - pool.join() - logging.info("Done!") - - return results + yield (int(p[1]), int(p[2]), os.path.join(dirpath, f)) def get_save_dir(): """Returns the path to the local saves directory From 83d7a36ef4e350d8d3025229689d9ff8861b5851 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 1 Mar 2011 13:18:25 -0500 Subject: [PATCH 006/213] moved quadtree.py to mtime-based update checking, and added a stub direct-to-tile renderer --- gmap.py | 7 ++- quadtree.py | 169 ++++++++++++++++++++++------------------------------ world.py | 29 +++++---- 3 files changed, 93 insertions(+), 112 deletions(-) diff --git a/gmap.py b/gmap.py index b104e78..da18aa0 100755 --- a/gmap.py +++ b/gmap.py @@ -137,9 +137,10 @@ def main(): w.go(options.procs) # Now generate the tiles - #q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg) - #q.write_html(options.skipjs) - #q.go(options.procs) + # TODO chunklist, render type (night, lighting, spawn) + q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg) + q.write_html(options.skipjs) + q.go(options.procs) def delete_all(tiledir): # Delete all /hash/ files in the tile dir. diff --git a/quadtree.py b/quadtree.py index 15c428b..aa0a46f 100644 --- a/quadtree.py +++ b/quadtree.py @@ -31,6 +31,7 @@ from time import gmtime, strftime, sleep from PIL import Image +import nbt from optimizeimages import optimize_image import composite @@ -424,9 +425,11 @@ class QuadtreeGen(object): chunklist = [] for row in xrange(rowstart-16, rowend+1): for col in xrange(colstart, colend+1): - c = self.world.chunkmap.get((col, row), None) - if c: - chunklist.append((col, row, c)) + # return (col, row, chunkx, chunky, regionpath) + chunkx, chunky = self.world.unconvert_coords(col, row) + c = self.world.get_region_path(chunkx, chunky) + if os.path.exists(c): + chunklist.append((col, row, chunkx, chunky, c)) return chunklist @catch_keyboardinterrupt @@ -436,68 +439,56 @@ def render_innertile(dest, name, imgformat, optimizeimg): os.path.join(dest, name, "{0,1,2,3}.png") """ imgpath = os.path.join(dest, name) + "." + imgformat - hashpath = os.path.join(dest, name) + ".hash" if name == "base": q0path = os.path.join(dest, "0." + imgformat) q1path = os.path.join(dest, "1." + imgformat) q2path = os.path.join(dest, "2." + imgformat) q3path = os.path.join(dest, "3." + imgformat) - q0hash = os.path.join(dest, "0.hash") - q1hash = os.path.join(dest, "1.hash") - q2hash = os.path.join(dest, "2.hash") - q3hash = os.path.join(dest, "3.hash") else: q0path = os.path.join(dest, name, "0." + imgformat) q1path = os.path.join(dest, name, "1." + imgformat) q2path = os.path.join(dest, name, "2." + imgformat) q3path = os.path.join(dest, name, "3." + imgformat) - q0hash = os.path.join(dest, name, "0.hash") - q1hash = os.path.join(dest, name, "1.hash") - q2hash = os.path.join(dest, name, "2.hash") - q3hash = os.path.join(dest, name, "3.hash") # Check which ones exist - if not os.path.exists(q0hash): + if not os.path.exists(q0path): q0path = None - q0hash = None - if not os.path.exists(q1hash): + if not os.path.exists(q1path): q1path = None - q1hash = None - if not os.path.exists(q2hash): + if not os.path.exists(q2path): q2path = None - q2hash = None - if not os.path.exists(q3hash): + if not os.path.exists(q3path): q3path = None - q3hash = None # do they all not exist? if not (q0path or q1path or q2path or q3path): if os.path.exists(imgpath): os.unlink(imgpath) - if os.path.exists(hashpath): - os.unlink(hashpath) return - # Now check the hashes - hasher = hashlib.md5() - if q0hash: - hasher.update(open(q0hash, "rb").read()) - if q1hash: - hasher.update(open(q1hash, "rb").read()) - if q2hash: - hasher.update(open(q2hash, "rb").read()) - if q3hash: - hasher.update(open(q3hash, "rb").read()) - if os.path.exists(hashpath): - oldhash = open(hashpath, "rb").read() - else: - oldhash = None - newhash = hasher.digest() - - if newhash == oldhash: - # Nothing to do - return + # check the mtimes + try: + tile_mtime = os.path.getmtime(imgpath) + needs_rerender = False + + # remove non-existant paths + components = [q0path, q1path, q2path, q3path] + components = filter(lambda p: p != None, components) + + for mtime in [os.path.getmtime(path) for path in components]: + if mtime > tile_mtime: + needs_rerender = True + break + + # quit now if we don't need rerender + if not needs_rerender: + return + except OSError: + # one of our mtime calls failed, so we'll continue + pass + + #logging.debug("writing out innertile {0}".format(imgpath)) # Create the actual image now img = Image.new("RGBA", (384, 384), (38,92,255,0)) @@ -538,10 +529,6 @@ def render_innertile(dest, name, imgformat, optimizeimg): if optimizeimg: optimize_image(imgpath, imgformat, optimizeimg) - with open(hashpath, "wb") as hashout: - hashout.write(newhash) - - @catch_keyboardinterrupt def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat, optimizeimg): """Renders just the specified chunks into a tile and save it. Unlike usual @@ -549,8 +536,8 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat chunks around the edges are half-way cut off (so that neighboring tiles will render the other half) - chunks is a list of (col, row, filename) of chunk images that are relevant - to this call + chunks is a list of (col, row, chunkx, chunky, filename) of chunk + images that are relevant to this call (with their associated regions) The image is saved to path+".ext" and a hash is saved to path+".hash" @@ -592,15 +579,20 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat # Before we render any tiles, check the hash of each image in this tile to # see if it's changed. - hashpath = path + ".hash" + # TODO remove hash files? imgpath = path + "." + imgformat + + # first, remove chunks from `chunks` that don't actually exist in + # their region files + def chunk_exists(chunk): + _, _, chunkx, chunky, region = chunk + return nbt.load_from_region(region, chunkx, chunky) != None + chunks = filter(chunk_exists, chunks) if not chunks: # No chunks were found in this tile if os.path.exists(imgpath): os.unlink(imgpath) - if os.path.exists(hashpath): - os.unlink(hashpath) return None # Create the directory if not exists @@ -615,60 +607,44 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat import errno if e.errno != errno.EEXIST: raise - - imghash = hashlib.md5() - for col, row, chunkfile in chunks: - # Get the hash of this image and add it to our hash for this tile - imghash.update( - os.path.basename(chunkfile).split(".")[4] - ) - digest = imghash.digest() - - if os.path.exists(hashpath): - oldhash = open(hashpath, 'rb').read() - else: - oldhash = None - - if digest == oldhash: - # All the chunks for this tile have not changed according to the hash - return + + # check chunk mtimes to see if they are newer + try: + tile_mtime = os.path.getmtime(imgpath) + needs_rerender = False + for col, row, chunkx, chunky, regionfile in chunks: + # check region file mtime first + if os.path.getmtime(regionfile) <= tile_mtime: + continue + + # checking chunk mtime + with open(regionfile, 'rb') as regionfile: + region = nbt.MCRFileReader(regionfile) + if region.get_chunk_timestamp(chunkx, chunky) > time_mtime: + needs_rerender = True + if needs_rerender: + break + + # if after all that, we don't need a rerender, return + if not needs_rerender: + return None + except OSError: + # couldn't get tile mtime, skip check + pass + + #logging.debug("writing out worldtile {0}".format(imgpath)) # Compile this image tileimg = Image.new("RGBA", (width, height), (38,92,255,0)) # col colstart will get drawn on the image starting at x coordinates -(384/2) # row rowstart will get drawn on the image starting at y coordinates -(192/2) - for col, row, chunkfile in chunks: - try: - chunkimg = Image.open(chunkfile) - chunkimg.load() - except Exception, e: - # If for some reason the chunk failed to load (perhaps a previous - # run was canceled and the file was only written half way, - # corrupting it), then this could error. - # Since we have no easy way of determining how this chunk was - # generated, we need to just ignore it. - logging.warning("Could not open chunk '{0}' ({1})".format(chunkfile,e)) - try: - # Remove the file so that the next run will re-generate it. - os.unlink(chunkfile) - except OSError, e: - import errno - # Ignore if file doesn't exist, another task could have already - # removed it. - if e.errno != errno.ENOENT: - logging.warning("Could not remove chunk '{0}'!".format(chunkfile)) - raise - else: - logging.warning("Removed the corrupt file") - - logging.warning("You will need to re-run the Overviewer to fix this chunk") - continue - + for col, row, chunkx, chunky, regionfile in chunks: xpos = -192 + (col-colstart)*192 ypos = -96 + (row-rowstart)*96 - composite.alpha_over(tileimg, chunkimg.convert("RGB"), (xpos, ypos), chunkimg) + # TODO draw chunks! + #composite.alpha_over(tileimg, chunkimg.convert("RGB"), (xpos, ypos), chunkimg) # Save them tileimg.save(imgpath) @@ -676,9 +652,6 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat if optimizeimg: optimize_image(imgpath, imgformat, optimizeimg) - with open(hashpath, "wb") as hashout: - hashout.write(digest) - class FakeResult(object): def __init__(self, res): self.res = res diff --git a/world.py b/world.py index 9c47799..826a651 100644 --- a/world.py +++ b/world.py @@ -38,16 +38,6 @@ This module has routines for extracting information about available worlds base36decode = functools.partial(int, base=36) cached = collections.defaultdict(dict) - -def _convert_coords(chunkx, chunky): - """Takes a coordinate (chunkx, chunky) where chunkx and chunky are - in the chunk coordinate system, and figures out the row and column - in the image each one should be. Returns (col, row).""" - - # columns are determined by the sum of the chunk coords, rows are the - # difference (TODO: be able to change direction of north) - return (chunkx + chunky, chunky - chunkx) - def base36encode(number, alphabet='0123456789abcdefghijklmnopqrstuvwxyz'): ''' Convert an integer to a base36 string. @@ -117,6 +107,23 @@ class World(object): return os.path.join(self.worlddir, chunkFile) + def convert_coords(self, chunkx, chunky): + """Takes a coordinate (chunkx, chunky) where chunkx and chunky are + in the chunk coordinate system, and figures out the row and column + in the image each one should be. Returns (col, row).""" + + # columns are determined by the sum of the chunk coords, rows are the + # difference (TODO: be able to change direction of north) + # change this function, and you MUST change unconvert_coords + return (chunkx + chunky, chunky - chunkx) + + def unconvert_coords(self, col, row): + """Undoes what convert_coords does. Returns (chunkx, chunky).""" + + # col + row = chunky + chunky => (col + row)/2 = chunky + # col - row = chunkx + chunkx => (col - row)/2 = chunkx + return ((col - row) / 2, (col + row) / 2) + def findTrueSpawn(self): """Adds the true spawn location to self.POI. The spawn Y coordinate is almost always the default of 64. Find the first air block above @@ -181,7 +188,7 @@ class World(object): # Translate chunks to our diagonal coordinate system mincol = maxcol = minrow = maxrow = 0 for chunkx, chunky in [(minx, miny), (minx, maxy), (maxx, miny), (maxx, maxy)]: - col, row = _convert_coords(chunkx, chunky) + col, row = self.convert_coords(chunkx, chunky) mincol = min(mincol, col) maxcol = max(maxcol, col) minrow = min(minrow, row) From 33b10f7065417067ec33a409d75fdc034874086c Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 1 Mar 2011 16:45:04 -0500 Subject: [PATCH 007/213] initial (and incorrect) drawing code --- chunk.py | 168 ++++++++++------------------------------------------ quadtree.py | 30 ++++++---- 2 files changed, 47 insertions(+), 151 deletions(-) diff --git a/chunk.py b/chunk.py index 8e6245e..01db090 100644 --- a/chunk.py +++ b/chunk.py @@ -128,54 +128,21 @@ fluid_blocks = set([8,9,10,11]) # (glass, half blocks) nospawn_blocks = set([20,44]) -def find_oldimage(chunkXY, cached, cave): - blockid = "%d.%d" % chunkXY - - # Get the name of the existing image. - dir1 = world.base36encode(chunkXY[0]%64) - dir2 = world.base36encode(chunkXY[1]%64) - cachename = '/'.join((dir1, dir2)) - - oldimg = oldimg_path = None - key = ".".join((blockid, "cave" if cave else "nocave")) - if key in cached[cachename]: - oldimg_path = cached[cachename][key] - _, oldimg = os.path.split(oldimg_path) - #logging.debug("Found cached image {0}".format(oldimg)) - return oldimg, oldimg_path - -def check_cache(world, chunkXY, oldimg): - """Returns True is oldimg is OK to use (i.e. not stale)""" -# TODO read to the region file and get the timestamp?? -# TODO currently, just use the mtime on the region file -# TODO (which will cause a single chunk update to invalidate everything in the region - - if not oldimg[1]: return False - chunkfile = os.path.join(world.worlddir, "region", "r.%d.%d.mcr" % (chunkXY[0]//32, chunkXY[1]//32)) - - with open(chunkfile, "rb") as f: - region = nbt.MCRFileReader(f) - mtime = region.get_chunk_timestamp(chunkXY[0], chunkXY[1]) - #logging.debug("checking cache %s against %s %d", chunkfile, oldimg[1], mtime) - try: - if mtime <= os.path.getmtime(oldimg[1]): - return True - return False - except OSError: - return False - # chunkcoords should be the coordinates of a possible chunk. it may not exist -def render_and_save(chunkcoords, cachedir, worldobj, oldimg, cave=False, queue=None): - """Used as the entry point for the multiprocessing workers (since processes - can't target bound methods) or to easily render and save one chunk +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 None. - Else, returns the image file location""" - a = ChunkRenderer(chunkcoords, cachedir, worldobj, oldimg, queue) + If the chunk doesn't exist, return False. + Else, returns True.""" + a = ChunkRenderer(chunkcoords, quadtreeobj.world, quadtreeobj, queue) try: - return a.render_and_save(cave) + a.chunk_render(img, imgcoords[0], imgcoords[1], cave) + return True except ChunkCorrupt: # This should be non-fatal, but should print a warning pass @@ -191,6 +158,7 @@ def render_and_save(chunkcoords, cachedir, worldobj, oldimg, cave=False, queue=N # entire program, instead of this process dying and the parent waiting # forever for it to finish. raise Exception() + return False class ChunkCorrupt(Exception): pass @@ -199,17 +167,15 @@ class NoSuchChunk(Exception): pass class ChunkRenderer(object): - def __init__(self, chunkcoords, cachedir, worldobj, oldimg, queue): + def __init__(self, chunkcoords, worldobj, quadtreeobj, queue): """Make a new chunk renderer for the given chunk coordinates. chunkcoors should be a tuple: (chunkX, chunkY) cachedir is a directory to save the resulting chunk images to """ self.queue = queue - # derive based on worlddir and chunkcoords - self.regionfile = os.path.join(worldobj.worlddir, "region", - "r.%d.%d.mcr" % (chunkcoords[0] // 32, chunkcoords[1]//32)) - + + self.regionfile = worldobj.get_region_path(*chunkcoords) if not os.path.exists(self.regionfile): raise ValueError("Could not find regionfile: %s" % self.regionfile) @@ -227,23 +193,8 @@ class ChunkRenderer(object): self.chunkX = chunkcoords[0] self.chunkY = chunkcoords[1] - - self.world = worldobj - - - # Cachedir here is the base directory of the caches. We need to go 2 - # levels deeper according to the chunk file. Get the last 2 components - # of destdir and use that - ##moredirs, dir2 = os.path.split(destdir) - ##_, dir1 = os.path.split(moredirs) - self.cachedir = os.path.join(cachedir, - world.base36encode(self.chunkX%64), - world.base36encode(self.chunkY%64)) - - #logging.debug("cache location for this chunk: %s", self.cachedir) - self.oldimg, self.oldimg_path = oldimg - + self.quadtree = quadtreeobj if self.world.useBiomeData: # make sure we've at least *tried* to load the color arrays in this process... @@ -251,15 +202,6 @@ class ChunkRenderer(object): if not textures.grasscolor or not textures.foliagecolor: raise Exception("Can't find grasscolor.png or foliagecolor.png") - - if not os.path.exists(self.cachedir): - try: - os.makedirs(self.cachedir) - except OSError, e: - import errno - if e.errno != errno.EEXIST: - raise - def _load_level(self): """Loads and returns the level structure""" if not hasattr(self, "_level"): @@ -480,69 +422,12 @@ class ChunkRenderer(object): self._digest = digest[:6] return self._digest - def render_and_save(self, cave=False): - """Render the chunk using chunk_render, and then save it to a file in - the same directory as the source image. If the file already exists and - is up to date, this method doesn't render anything. - """ - blockid = self.blockid - - - # Reasons for the code to get to this point: - # 1) An old image doesn't exist - # 2) An old image exists, but the chunk was more recently modified (the - # image was NOT checked if it was valid) - # 3) An old image exists, the chunk was not modified more recently, but - # the image was invalid and deleted (sort of the same as (1)) - - # What /should/ the image be named, go ahead and hash the block array - try: - dest_filename = "img.{0}.{1}.{2}.png".format( - blockid, - "cave" if cave else "nocave", - self._hash_blockarray(), - ) - except NoSuchChunk, e: - return None - - dest_path = os.path.join(self.cachedir, dest_filename) - #logging.debug("cache filename: %s", dest_path) - - if self.oldimg: - if dest_filename == self.oldimg: - # There is an existing file, the chunk has a newer mtime, but the - # hashes match. - # Before we return it, update its mtime so the next round - # doesn't have to check the hash - # TODO confirm hash checking is correct (it should be) - os.utime(dest_path, None) - logging.debug("Using cached image, and updating utime") - return dest_path - else: - # Remove old image for this chunk. Anything already existing is - # either corrupt or out of date - os.unlink(self.oldimg_path) - - - logging.debug("doing a real real render") - # Render the chunk - img = self.chunk_render(cave=cave) - # Save it - try: - img.save(dest_path) - except: - os.unlink(dest_path) - raise - # Return its location - #raise Exception("early exit") - return dest_path - def calculate_darkness(self, skylight, blocklight): """Takes a raw blocklight and skylight, and returns a value between 0.0 (fully lit) and 1.0 (fully black) that can be used as an alpha value for a blend with a black source image. It mimics Minecraft lighting calculations.""" - if not self.world.night: + if not self.quadtree.night: # Daytime return 1.0 - pow(0.8, 15 - max(blocklight, skylight)) else: @@ -662,7 +547,7 @@ class ChunkRenderer(object): # won't get counted as "transparent". blocks = blocks.copy() blocks[self.skylight != 0] = 21 - + blockData = get_blockdata_array(self.level) blockData_expanded = numpy.empty((16,16,128), dtype=numpy.uint8) # Even elements get the lower 4 bits @@ -691,6 +576,11 @@ class ChunkRenderer(object): for x,y,z,imgx,imgy in iterate_chunkblocks(xoff,yoff): blockid = blocks[x,y,z] + + if imgx > img.size[0] + 12 or imgx < -12: + continue + if imgy > img.size[1] + 12 or imgy < -12: + continue # the following blocks don't have textures that can be pre-computed from the blockid # alone. additional data is required. @@ -817,7 +707,7 @@ class ChunkRenderer(object): # no lighting for cave -- depth is probably more useful composite.alpha_over(img, Image.blend(t[0],depth_colors[z],0.3), (imgx, imgy), t[1]) else: - if not self.world.lighting: + if not self.quadtree.lighting: # no lighting at all composite.alpha_over(img, t[0], (imgx, imgy), t[1]) elif blockid in transparent_blocks: @@ -825,7 +715,7 @@ class ChunkRenderer(object): # block shaded with the current # block's light black_coeff, _ = self.get_lighting_coefficient(x, y, z) - if self.world.spawn and black_coeff > 0.8 and blockid in solid_blocks and not ( + if self.quadtree.spawn and black_coeff > 0.8 and blockid in solid_blocks and not ( blockid in nospawn_blocks or ( z != 127 and (blocks[x,y,z+1] in solid_blocks or blocks[x,y,z+1] in fluid_blocks) ) @@ -841,7 +731,7 @@ class ChunkRenderer(object): # top face black_coeff, face_occlude = self.get_lighting_coefficient(x, y, z + 1) # Use red instead of black for spawnable blocks - if self.world.spawn and black_coeff > 0.8 and blockid in solid_blocks and not ( + if self.quadtree.spawn and black_coeff > 0.8 and blockid in solid_blocks and not ( blockid in nospawn_blocks or ( z != 127 and (blocks[x,y,z+1] in solid_blocks or blocks[x,y,z+1] in fluid_blocks) ) @@ -892,13 +782,15 @@ class ChunkRenderer(object): msg=msg, chunk= (self.chunkX, self.chunkY), ) - self.queue.put(["newpoi", newPOI]) + if self.queue: + self.queue.put(["newpoi", newPOI]) # check to see if there are any signs in the persistentData list that are from this chunk. # if so, remove them from the persistentData list (since they're have been added to the world.POI - # list above. - self.queue.put(['removePOI', (self.chunkX, self.chunkY)]) + # list above + if self.queue: + self.queue.put(['removePOI', (self.chunkX, self.chunkY)]) return img diff --git a/quadtree.py b/quadtree.py index aa0a46f..87a52a2 100644 --- a/quadtree.py +++ b/quadtree.py @@ -32,6 +32,7 @@ from time import gmtime, strftime, sleep from PIL import Image import nbt +import chunk from optimizeimages import optimize_image import composite @@ -95,6 +96,11 @@ class QuadtreeGen(object): assert(imgformat) self.imgformat = imgformat self.optimizeimg = optimizeimg + + # TODO placeholders (use config!) + self.lighting = False + self.night = False + self.spawn = False # Make the destination dir if not os.path.exists(destdir): @@ -308,9 +314,8 @@ class QuadtreeGen(object): # Put this in the pool # (even if tilechunks is empty, render_worldtile will delete # existing images if appropriate) - yield pool.apply_async(func=render_worldtile, args= (tilechunks, - colstart, colend, rowstart, rowend, dest, self.imgformat, - self.optimizeimg)) + yield pool.apply_async(func=render_worldtile, args= (self, + tilechunks, colstart, colend, rowstart, rowend, dest)) def _apply_render_inntertile(self, pool, zoom): """Same as _apply_render_worltiles but for the inntertile routine. @@ -530,7 +535,7 @@ def render_innertile(dest, name, imgformat, optimizeimg): optimize_image(imgpath, imgformat, optimizeimg) @catch_keyboardinterrupt -def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat, optimizeimg): +def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path): """Renders just the specified chunks into a tile and save it. Unlike usual python conventions, rowend and colend are inclusive. Additionally, the chunks around the edges are half-way cut off (so that neighboring tiles @@ -539,7 +544,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat chunks is a list of (col, row, chunkx, chunky, filename) of chunk images that are relevant to this call (with their associated regions) - The image is saved to path+".ext" and a hash is saved to path+".hash" + The image is saved to path+"."+quadtree.imgformat If there are no chunks, this tile is not saved (if it already exists, it is deleted) @@ -550,6 +555,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat There is no return value """ + # width of one chunk is 384. Each column is half a chunk wide. The total # width is (384 + 192*(numcols-1)) since the first column contributes full # width, and each additional one contributes half since they're staggered. @@ -577,10 +583,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat # way above this one are possibly visible in this tile). Render them # anyways just in case). "chunks" should include up to rowstart-16 - # Before we render any tiles, check the hash of each image in this tile to - # see if it's changed. - # TODO remove hash files? - imgpath = path + "." + imgformat + imgpath = path + "." + quadtree.imgformat # first, remove chunks from `chunks` that don't actually exist in # their region files @@ -643,14 +646,15 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat xpos = -192 + (col-colstart)*192 ypos = -96 + (row-rowstart)*96 - # TODO draw chunks! - #composite.alpha_over(tileimg, chunkimg.convert("RGB"), (xpos, ypos), chunkimg) + # draw the chunk! + # TODO cave, queue arguments + chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), quadtree, False, None) # Save them tileimg.save(imgpath) - if optimizeimg: - optimize_image(imgpath, imgformat, optimizeimg) + if quadtree.optimizeimg: + optimize_image(imgpath, quadtree.imgformat, quadtree.optimizeimg) class FakeResult(object): def __init__(self, res): From 16bab9be3b544348d173e8b5a59f165fd1dec864 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 1 Mar 2011 18:40:06 -0500 Subject: [PATCH 008/213] less time between console messages for -p1 --- quadtree.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quadtree.py b/quadtree.py index 87a52a2..bd4e6eb 100644 --- a/quadtree.py +++ b/quadtree.py @@ -361,10 +361,10 @@ class QuadtreeGen(object): logging.info("The others will go faster") for result in self._apply_render_worldtiles(pool): results.append(result) - if len(results) > 10000: + if len(results) > 10 + 2*procs: # Empty the queue before adding any more, so that memory # required has an upper bound - while len(results) > 500: + while len(results) > 10: results.popleft().get() complete += 1 self.print_statusline(complete, total, 1) @@ -386,8 +386,8 @@ class QuadtreeGen(object): logging.info("Starting level {0}".format(level)) for result in self._apply_render_inntertile(pool, zoom): results.append(result) - if len(results) > 10000: - while len(results) > 500: + if len(results) > 10 + 2*procs: + while len(results) > 10: results.popleft().get() complete += 1 self.print_statusline(complete, total, level) From 4293851fcc476496636913c112a34619f14d634c Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 1 Mar 2011 20:22:22 -0500 Subject: [PATCH 009/213] fixed chunk double-render bug and artifacts at the top of each tile --- chunk.py | 10 ++++++---- quadtree.py | 10 +++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/chunk.py b/chunk.py index 01db090..fc3d5fa 100644 --- a/chunk.py +++ b/chunk.py @@ -575,13 +575,15 @@ class ChunkRenderer(object): img = Image.new("RGBA", (384, 1728), (38,92,255,0)) for x,y,z,imgx,imgy in iterate_chunkblocks(xoff,yoff): - blockid = blocks[x,y,z] - - if imgx > img.size[0] + 12 or imgx < -12: + # make sure we're drawing within the image boundaries + # 24 == approximate width, height of one block + if imgx >= img.size[0] + 24 or imgx <= -24: continue - if imgy > img.size[1] + 12 or imgy < -12: + if imgy >= img.size[1] + 24 or imgy <= -24: continue + blockid = blocks[x,y,z] + # the following blocks don't have textures that can be pre-computed from the blockid # alone. additional data is required. # TODO torches, redstone torches, crops, ladders, stairs, diff --git a/quadtree.py b/quadtree.py index bd4e6eb..a25d84f 100644 --- a/quadtree.py +++ b/quadtree.py @@ -430,6 +430,12 @@ class QuadtreeGen(object): chunklist = [] for row in xrange(rowstart-16, rowend+1): for col in xrange(colstart, colend+1): + # due to how chunks are arranged, we can only allow + # even row, even column or odd row, odd column + # otherwise, you end up with duplicates! + if row % 2 != col % 2: + continue + # return (col, row, chunkx, chunky, regionpath) chunkx, chunky = self.world.unconvert_coords(col, row) c = self.world.get_region_path(chunkx, chunky) @@ -589,7 +595,9 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) # their region files def chunk_exists(chunk): _, _, chunkx, chunky, region = chunk - return nbt.load_from_region(region, chunkx, chunky) != None + with open(region, 'rb') as region: + r = nbt.MCRFileReader(region) + return r.load_chunk(chunkx, chunky) != None chunks = filter(chunk_exists, chunks) if not chunks: From d8130533293712354dcf4d7368ebfb2239c104cb Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 1 Mar 2011 20:28:31 -0500 Subject: [PATCH 010/213] removed lingering traces of the hash system --- chunk.py | 17 ----------------- quadtree.py | 7 ++----- 2 files changed, 2 insertions(+), 22 deletions(-) diff --git a/chunk.py b/chunk.py index fc3d5fa..4bd5b75 100644 --- a/chunk.py +++ b/chunk.py @@ -16,7 +16,6 @@ import numpy from PIL import Image, ImageDraw, ImageEnhance, ImageOps import os.path -import hashlib import logging import time import math @@ -406,22 +405,6 @@ class ChunkRenderer(object): return pseudo_data - def _hash_blockarray(self): - """Finds a hash of the block array""" - if hasattr(self, "_digest"): - return self._digest - h = hashlib.md5() - h.update(self.level['Blocks']) - - # If the render algorithm changes, change this line to re-generate all - # the chunks automatically: - h.update("1") - - digest = h.hexdigest() - # 6 digits ought to be plenty - self._digest = digest[:6] - return self._digest - def calculate_darkness(self, skylight, blocklight): """Takes a raw blocklight and skylight, and returns a value between 0.0 (fully lit) and 1.0 (fully black) that can be used as diff --git a/quadtree.py b/quadtree.py index a25d84f..caef4f8 100644 --- a/quadtree.py +++ b/quadtree.py @@ -17,7 +17,6 @@ import multiprocessing import itertools import os import os.path -import hashlib import functools import re import shutil @@ -251,8 +250,8 @@ class QuadtreeGen(object): newdir = "new" + str(dirnum) newdirpath = getpath(newdir) - files = [str(dirnum)+"."+self.imgformat, str(dirnum)+".hash", str(dirnum)] - newfiles = [str(newnum)+"."+self.imgformat, str(newnum)+".hash", str(newnum)] + files = [str(dirnum)+"."+self.imgformat, str(dirnum)] + newfiles = [str(newnum)+"."+self.imgformat, str(newnum)] os.mkdir(newdirpath) for f, newf in zip(files, newfiles): @@ -555,8 +554,6 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) If there are no chunks, this tile is not saved (if it already exists, it is deleted) - If the hash file already exists, it is checked against the hash of each chunk. - Standard tile size has colend-colstart=2 and rowend-rowstart=4 There is no return value From 94c1fbfaa61d03cdd024e1013dc2cb2fd9500cbc Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 1 Mar 2011 20:42:58 -0500 Subject: [PATCH 011/213] fixed typo in quadtree.py --- quadtree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quadtree.py b/quadtree.py index caef4f8..34d3081 100644 --- a/quadtree.py +++ b/quadtree.py @@ -628,7 +628,7 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) # checking chunk mtime with open(regionfile, 'rb') as regionfile: region = nbt.MCRFileReader(regionfile) - if region.get_chunk_timestamp(chunkx, chunky) > time_mtime: + if region.get_chunk_timestamp(chunkx, chunky) > tile_mtime: needs_rerender = True if needs_rerender: break From 300d4ac53e7489089125f1691827f37a93e7ba50 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 2 Mar 2011 08:21:41 -0500 Subject: [PATCH 012/213] added back lighting, night, and spawn support --- gmap.py | 6 +++--- quadtree.py | 11 +++++------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/gmap.py b/gmap.py index da18aa0..8510732 100755 --- a/gmap.py +++ b/gmap.py @@ -52,7 +52,7 @@ def main(): parser.add_option("--lighting", dest="lighting", help="Renders shadows using light data from each chunk.", action="store_true") parser.add_option("--night", dest="night", help="Renders shadows using light data from each chunk, as if it were night. Implies --lighting.", action="store_true") parser.add_option("--spawn", dest="spawn", help="Renders shadows using light data from each chunk, as if it were night, while also highlighting areas that are dark enough to spawn mobs. Implies --lighting and --night.", action="store_true") - parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.") + parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg.") parser.add_option("--optimize-img", dest="optimizeimg", help="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%") parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.") parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, help="Print more output. You can specify this option multiple times.") @@ -137,8 +137,8 @@ def main(): w.go(options.procs) # Now generate the tiles - # TODO chunklist, render type (night, lighting, spawn) - q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg) + # TODO chunklist + q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, lighting=options.lighting, night=options.night, spawn=options.spawn) q.write_html(options.skipjs) q.go(options.procs) diff --git a/quadtree.py b/quadtree.py index 34d3081..78dcffb 100644 --- a/quadtree.py +++ b/quadtree.py @@ -82,7 +82,7 @@ def catch_keyboardinterrupt(func): return newfunc class QuadtreeGen(object): - def __init__(self, worldobj, destdir, depth=None, imgformat=None, optimizeimg=None): + def __init__(self, worldobj, destdir, depth=None, imgformat=None, optimizeimg=None, lighting=False, night=False, spawn=False): """Generates a quadtree from the world given into the given dest directory @@ -96,10 +96,9 @@ class QuadtreeGen(object): self.imgformat = imgformat self.optimizeimg = optimizeimg - # TODO placeholders (use config!) - self.lighting = False - self.night = False - self.spawn = False + self.lighting = lighting + self.night = night + self.spawn = spawn # Make the destination dir if not os.path.exists(destdir): @@ -652,7 +651,7 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) ypos = -96 + (row-rowstart)*96 # draw the chunk! - # TODO cave, queue arguments + # TODO POI queue chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), quadtree, False, None) # Save them From 5a043ab3d6a3a3d1b760dbbc40792dc978e84bbf Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 2 Mar 2011 08:28:55 -0500 Subject: [PATCH 013/213] fixed -d flag, which now deletes just overviewer.dat --- gmap.py | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/gmap.py b/gmap.py index 8510732..64b08bc 100755 --- a/gmap.py +++ b/gmap.py @@ -90,13 +90,13 @@ def main(): if len(args) != 2: if options.delete: - return delete_all(cachedir, None) + return delete_all(worlddir, None) parser.error("Where do you want to save the tiles?") destdir = args[1] if options.delete: - return delete_all(destdir) + return delete_all(worlddir, destdir) if options.chunklist: chunklist = open(options.chunklist, 'r') @@ -142,16 +142,9 @@ def main(): q.write_html(options.skipjs) q.go(options.procs) -def delete_all(tiledir): - # Delete all /hash/ files in the tile dir. - if tiledir: - for dirpath, dirnames, filenames in os.walk(tiledir): - for f in filenames: - if f.endswith(".hash"): - filepath = os.path.join(dirpath, f) - logging.info("Deleting {0}".format(filepath)) - os.unlink(filepath) - +def delete_all(worlddir, tiledir): + # TODO should we delete tiledir here too? + # delete the overviewer.dat persistant data file datfile = os.path.join(worlddir,"overviewer.dat") if os.path.exists(datfile): From be26dc702bf0ec17d3f40886ead2962910a95aca Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 2 Mar 2011 08:29:33 -0500 Subject: [PATCH 014/213] added option to QuadtreeGen to specify tile output directory --- quadtree.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/quadtree.py b/quadtree.py index 78dcffb..8c1a593 100644 --- a/quadtree.py +++ b/quadtree.py @@ -82,7 +82,7 @@ def catch_keyboardinterrupt(func): return newfunc class QuadtreeGen(object): - def __init__(self, worldobj, destdir, depth=None, imgformat=None, optimizeimg=None, lighting=False, night=False, spawn=False): + def __init__(self, worldobj, destdir, depth=None, tiledir="tiles", imgformat=None, optimizeimg=None, lighting=False, night=False, spawn=False): """Generates a quadtree from the world given into the given dest directory @@ -103,6 +103,7 @@ class QuadtreeGen(object): # Make the destination dir if not os.path.exists(destdir): os.mkdir(destdir) + self.tiledir = tiledir if depth is None: # Determine quadtree depth (midpoint is always 0,0) @@ -169,7 +170,7 @@ class QuadtreeGen(object): # Write a blank image blank = Image.new("RGBA", (1,1)) - tileDir = os.path.join(self.destdir, "tiles") + tileDir = os.path.join(self.destdir, self.tiledir) if not os.path.exists(tileDir): os.mkdir(tileDir) blank.save(os.path.join(tileDir, "blank."+self.imgformat)) @@ -235,7 +236,7 @@ class QuadtreeGen(object): def _increase_depth(self): """Moves existing tiles into place for a larger tree""" - getpath = functools.partial(os.path.join, self.destdir, "tiles") + getpath = functools.partial(os.path.join, self.destdir, self.tiledir) # At top level of the tree: # quadrant 0 is now 0/3 @@ -262,7 +263,7 @@ class QuadtreeGen(object): def _decrease_depth(self): """If the map size decreases, or perhaps the user has a depth override in effect, re-arrange existing tiles for a smaller tree""" - getpath = functools.partial(os.path.join, self.destdir, "tiles") + getpath = functools.partial(os.path.join, self.destdir, self.tiledir) # quadrant 0/3 goes to 0 # 1/2 goes to 1 @@ -301,7 +302,7 @@ class QuadtreeGen(object): rowend = rowstart + 4 # This image is rendered at: - dest = os.path.join(self.destdir, "tiles", *(str(x) for x in path)) + dest = os.path.join(self.destdir, self.tiledir, *(str(x) for x in path)) #logging.debug("this is rendered at %s", dest) # And uses these chunks @@ -322,7 +323,7 @@ class QuadtreeGen(object): """ for path in iterate_base4(zoom): # This image is rendered at: - dest = os.path.join(self.destdir, "tiles", *(str(x) for x in path[:-1])) + dest = os.path.join(self.destdir, self.tiledir, *(str(x) for x in path[:-1])) name = str(path[-1]) yield pool.apply_async(func=render_innertile, args= (dest, name, self.imgformat, self.optimizeimg)) @@ -403,7 +404,7 @@ class QuadtreeGen(object): pool.join() # Do the final one right here: - render_innertile(os.path.join(self.destdir, "tiles"), "base", self.imgformat, self.optimizeimg) + render_innertile(os.path.join(self.destdir, self.tiledir), "base", self.imgformat, self.optimizeimg) def _get_range_by_path(self, path): """Returns the x, y chunk coordinates of this tile""" From 66a9306a29e2c6a3030cd1c2dadbc3ba157dbecf Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 6 Mar 2011 00:30:40 -0500 Subject: [PATCH 015/213] Experimental code to provide a C implementation of chunk_render Please see the TODO comments in iterate.c None of the lighting, spawning, night, or cave modes work with this version. --- chunk.py | 197 +--------------------------- iterate.c | 373 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ setup.py | 1 + 3 files changed, 376 insertions(+), 195 deletions(-) create mode 100644 iterate.c diff --git a/chunk.py b/chunk.py index 4bd5b75..47ab960 100644 --- a/chunk.py +++ b/chunk.py @@ -25,6 +25,7 @@ import nbt import textures import world import composite +import _iterate """ This module has routines related to rendering one particular chunk into an @@ -557,201 +558,7 @@ class ChunkRenderer(object): if not img: img = Image.new("RGBA", (384, 1728), (38,92,255,0)) - for x,y,z,imgx,imgy in iterate_chunkblocks(xoff,yoff): - # make sure we're drawing within the image boundaries - # 24 == approximate width, height of one block - if imgx >= img.size[0] + 24 or imgx <= -24: - continue - if imgy >= img.size[1] + 24 or imgy <= -24: - continue - - blockid = blocks[x,y,z] - - # the following blocks don't have textures that can be pre-computed from the blockid - # alone. additional data is required. - # TODO torches, redstone torches, crops, ladders, stairs, - # levers, doors, buttons, and signs all need to be handled here (and in textures.py) - - ## minecart track, crops, ladder, doors, etc. - if blockid in textures.special_blocks: - # also handle furnaces here, since one side has a different texture than the other - ancilData = blockData_expanded[x,y,z] - try: - if blockid in pseudo_ancildata_blocks: - - pseudo_ancilData = self.generate_pseudo_ancildata(x,y,z,blockid) - ancilData = pseudo_ancilData - t = textures.specialblockmap[(blockid, ancilData)] - except KeyError: - t = None - - else: - t = textures.blockmap[blockid] - - if not t: - continue - - if self.world.useBiomeData: - if blockid == 2: #grass - index = biomeColorData[ ((startY*16)+y) * 128 + (startX*16) + x] - c = textures.grasscolor[index] - - # only tint the top texture - t = textures.prepareGrassTexture(c) - elif blockid == 18: # leaves - index = biomeColorData[ ((startY*16)+y) * 128 + (startX*16) + x] - c = textures.foliagecolor[index] - - t = textures.prepareLeafTexture(c) - - - - - # Check if this block is occluded - if cave and ( - x == 0 and y != 15 and z != 127 - ): - # If it's on the x face, only render if there's a - # transparent block in the y+1 direction OR the z-1 - # direction - if ( - blocks[x,y+1,z] not in transparent_blocks and - blocks[x,y,z+1] not in transparent_blocks - ): - continue - elif cave and ( - y == 15 and x != 0 and z != 127 - ): - # If it's on the facing y face, only render if there's - # a transparent block in the x-1 direction OR the z-1 - # direction - if ( - blocks[x-1,y,z] not in transparent_blocks and - blocks[x,y,z+1] not in transparent_blocks - ): - continue - elif cave and ( - y == 15 and x == 0 and z != 127 - ): - # If it's on the facing edge, only render if what's - # above it is transparent - if ( - blocks[x,y,z+1] not in transparent_blocks - ): - continue - elif (left_blocks == None and right_blocks == None): - # Normal block or not cave mode, check sides for - # transparentcy or render if it's a border chunk. - - if ( - x != 0 and y != 15 and z != 127 and - blocks[x-1,y,z] not in transparent_blocks and - blocks[x,y+1,z] not in transparent_blocks and - blocks[x,y,z+1] not in transparent_blocks - ): - continue - - elif (left_blocks != None and right_blocks == None): - - if ( - # If it has the left face covered check for - # transparent blocks in left face - y != 15 and z != 127 and - (left_blocks[15,y,z] if x == 0 else blocks[x - 1,y,z]) not in transparent_blocks and - blocks[x,y+1,z] not in transparent_blocks and - blocks[x,y,z+1] not in transparent_blocks - ): - continue - - elif (left_blocks == None and right_blocks != None): - - if ( - # If it has the right face covered check for - # transparent blocks in right face - x != 0 and z != 127 and - blocks[x-1,y,z] not in transparent_blocks and - (right_blocks[x,0,z] if y == 15 else blocks[x,y + 1,z]) not in transparent_blocks and - blocks[x,y,z+1] not in transparent_blocks - ): - continue - - elif ( - # If it's a interior chunk check for transparent blocks - # in the adjacent chunks. - z != 127 and - (left_blocks[15,y,z] if x == 0 else blocks[x - 1,y,z]) not in transparent_blocks and - (right_blocks[x,0,z] if y == 15 else blocks[x,y + 1,z]) not in transparent_blocks and - blocks[x,y,z+1] not in transparent_blocks - # Don't render if all sides aren't transparent - ): - continue - - # Draw the actual block on the image. For cave images, - # tint the block with a color proportional to its depth - if cave: - # no lighting for cave -- depth is probably more useful - composite.alpha_over(img, Image.blend(t[0],depth_colors[z],0.3), (imgx, imgy), t[1]) - else: - if not self.quadtree.lighting: - # no lighting at all - composite.alpha_over(img, t[0], (imgx, imgy), t[1]) - elif blockid in transparent_blocks: - # transparent means draw the whole - # block shaded with the current - # block's light - black_coeff, _ = self.get_lighting_coefficient(x, y, z) - if self.quadtree.spawn and black_coeff > 0.8 and blockid in solid_blocks and not ( - blockid in nospawn_blocks or ( - z != 127 and (blocks[x,y,z+1] in solid_blocks or blocks[x,y,z+1] in fluid_blocks) - ) - ): - composite.alpha_over(img, Image.blend(t[0], red_color, black_coeff), (imgx, imgy), t[1]) - else: - composite.alpha_over(img, Image.blend(t[0], black_color, black_coeff), (imgx, imgy), t[1]) - else: - # draw each face lit appropriately, - # but first just draw the block - composite.alpha_over(img, t[0], (imgx, imgy), t[1]) - - # top face - black_coeff, face_occlude = self.get_lighting_coefficient(x, y, z + 1) - # Use red instead of black for spawnable blocks - if self.quadtree.spawn and black_coeff > 0.8 and blockid in solid_blocks and not ( - blockid in nospawn_blocks or ( - z != 127 and (blocks[x,y,z+1] in solid_blocks or blocks[x,y,z+1] in fluid_blocks) - ) - ): - over_color = red_color - else: - over_color = black_color - - if not face_occlude: - composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) - - # left face - black_coeff, face_occlude = self.get_lighting_coefficient(x - 1, y, z) - if not face_occlude: - composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[1]).enhance(black_coeff)) - - # right face - black_coeff, face_occlude = self.get_lighting_coefficient(x, y + 1, z) - if not face_occlude: - composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[2]).enhance(black_coeff)) - - # Draw edge lines - if blockid in (44,): # step block - increment = 6 - elif blockid in (78,): # snow - increment = 9 - else: - increment = 0 - - if blockid not in transparent_blocks or blockid in (78,): #special case snow so the outline is still drawn - draw = ImageDraw.Draw(img) - if x != 15 and blocks[x+1,y,z] == 0: - draw.line(((imgx+12,imgy+increment), (imgx+22,imgy+5+increment)), fill=(0,0,0), width=1) - if y != 0 and blocks[x,y-1,z] == 0: - draw.line(((imgx,imgy+6+increment), (imgx+12,imgy+increment)), fill=(0,0,0), width=1) + _iterate.render_loop(self, img, xoff, yoff, blockData_expanded) for entity in tileEntities: diff --git a/iterate.c b/iterate.c new file mode 100644 index 0000000..9405bf5 --- /dev/null +++ b/iterate.c @@ -0,0 +1,373 @@ +#include + +#include +#include + + +/* like (a * b + 127) / 255), but much faster on most platforms + from PIL's _imaging.c */ +#define MULDIV255(a, b, tmp) \ + (tmp = (a) * (b) + 128, ((((tmp) >> 8) + (tmp)) >> 8)) + +typedef struct +{ + PyObject_HEAD + Imaging image; +} ImagingObject; + +// macro for getting blockID from a chunk of memory +#define getBlock(blockThing, x,y,z) blockThing[ y + ( z * 128 + ( x * 128 * 16) ) ] + +static inline int isTransparent(unsigned char b) { + // TODO expand this to include all transparent blocks + return b == 0; + +} + + +static Imaging imaging_python_to_c(PyObject* obj) +{ + PyObject* im; + Imaging image; + + /* first, get the 'im' attribute */ + im = PyObject_GetAttrString(obj, "im"); + if (!im) + return NULL; + + /* make sure 'im' is the right type */ + if (strcmp(im->ob_type->tp_name, "ImagingCore") != 0) + { + /* it's not -- raise an error and exit */ + PyErr_SetString(PyExc_TypeError, "image attribute 'im' is not a core Imaging type"); + return NULL; + } + + image = ((ImagingObject*)im)->image; + Py_DECREF(im); + return image; +} + + + +// TODO refact iterate.c and _composite.c so share implementations +static PyObject* alpha_over(PyObject* dest, PyObject* t, int imgx, int imgy) +{ + /* raw input python variables */ + PyObject * src, * mask; + /* libImaging handles */ + Imaging imDest, imSrc, imMask; + /* cached blend properties */ + int src_has_alpha, mask_offset, mask_stride; + /* destination position and size */ + int dx, dy, xsize, ysize; + /* source position */ + int sx, sy; + /* iteration variables */ + unsigned int x, y, i; + /* temporary calculation variables */ + int tmp1, tmp2, tmp3; + + src = PyTuple_GET_ITEM(t, 0); + mask = PyTuple_GET_ITEM(t, 1); + if (mask == Py_None) { + printf("mask is none\n"); + Py_INCREF(mask); + mask = src; + } + //if (!PyArg_ParseTuple(args, "OOOO", &dest, &src, &pos, &mask)) + // return NULL; + + imDest = imaging_python_to_c(dest); + imSrc = imaging_python_to_c(src); + imMask = imaging_python_to_c(mask); + + //printf("alpha1\n"); + if (!imDest || !imSrc || !imMask) { + PyErr_SetString(PyExc_ValueError, "dest, src, or mask is missing"); + return NULL; + } + //printf("alpha2\n"); + + /* check the various image modes, make sure they make sense */ + if (strcmp(imDest->mode, "RGBA") != 0) + { + PyErr_SetString(PyExc_ValueError, "given destination image does not have mode \"RGBA\""); + return NULL; + } + + if (strcmp(imSrc->mode, "RGBA") != 0 && strcmp(imSrc->mode, "RGB") != 0) + { + PyErr_SetString(PyExc_ValueError, "given source image does not have mode \"RGBA\" or \"RGB\""); + return NULL; + } + + if (strcmp(imMask->mode, "RGBA") != 0 && strcmp(imMask->mode, "L") != 0) + { + PyErr_SetString(PyExc_ValueError, "given mask image does not have mode \"RGBA\" or \"L\""); + return NULL; + } + + /* make sure mask size matches src size */ + if (imSrc->xsize != imMask->xsize || imSrc->ysize != imMask->ysize) + { + PyErr_SetString(PyExc_ValueError, "mask and source image sizes do not match"); + return NULL; + } + + //printf("alpha3\n"); + /* set up flags for the src/mask type */ + src_has_alpha = (imSrc->pixelsize == 4 ? 1 : 0); + /* how far into image the first alpha byte resides */ + mask_offset = (imMask->pixelsize == 4 ? 3 : 0); + /* how many bytes to skip to get to the next alpha byte */ + mask_stride = imMask->pixelsize; + + //printf("alpha4\n"); + /* destination position read */ + //if (!PyArg_ParseTuple(pos, "iiii", &dx, &dy, &xsize, &ysize)) + //{ + // PyErr_SetString(PyExc_TypeError, "given blend destination rect is not valid"); + // return NULL; + //} + dx = imgx; + dy = imgy; + + xsize = imSrc->xsize; + ysize = imSrc->ysize; + //printf("xsize/ysize %d/%d\n", xsize, ysize); + + //printf("alpha5\n"); + + /* set up the source position, size and destination position */ + /* handle negative dest pos */ + if (dx < 0) + { + sx = -dx; + dx = 0; + } else { + sx = 0; + } + + if (dy < 0) + { + sy = -dy; + dy = 0; + } else { + sy = 0; + } + + /* set up source dimensions */ + xsize -= sx; + ysize -= sy; + + //printf("imDest->xsize=%d imDest->yize=%d\n", imDest->xsize, imDest->ysize); + + /* clip dimensions, if needed */ + if (dx + xsize > imDest->xsize) + xsize = imDest->xsize - dx; + if (dy + ysize > imDest->ysize) + ysize = imDest->ysize - dy; + + /* check that there remains any blending to be done */ + if (xsize <= 0 || ysize <= 0) + { + /* nothing to do, return */ + Py_INCREF(dest); + return dest; + } + + for (y = 0; y < ysize; y++) + { + UINT8* out = (UINT8*) imDest->image[dy + y] + dx*4; + UINT8* outmask = (UINT8*) imDest->image[dy + y] + dx*4 + 3; + UINT8* in = (UINT8*) imSrc->image[sy + y] + sx*(imSrc->pixelsize); + UINT8* inmask = (UINT8*) imMask->image[sy + y] + sx*mask_stride + mask_offset; + + for (x = 0; x < xsize; x++) + { + /* special cases */ + if (*inmask == 255 || *outmask == 0) + { + *outmask = *inmask; + + *out = *in; + out++, in++; + *out = *in; + out++, in++; + *out = *in; + out++, in++; + } else if (*inmask == 0) { + /* do nothing -- source is fully transparent */ + out += 3; + in += 3; + } else { + /* general case */ + int alpha = *inmask + MULDIV255(*outmask, 255 - *inmask, tmp1); + for (i = 0; i < 3; i++) + { + /* general case */ + *out = MULDIV255(*in, *inmask, tmp1) + MULDIV255(MULDIV255(*out, *outmask, tmp2), 255 - *inmask, tmp3); + *out = (*out * 255) / alpha; + out++, in++; + } + + *outmask = alpha; + } + + out++; + if (src_has_alpha) + in++; + outmask += 4; + inmask += mask_stride; + } + } + + Py_INCREF(dest); + return dest; +} + + + +// TODO triple check this to make sure reference counting is correct +static PyObject* +chunk_render(PyObject *self, PyObject *args) { + + PyObject *chunk; + PyObject *blockdata_expanded; + int xoff, yoff; + PyObject *img; + + if (!PyArg_ParseTuple(args, "OOiiO", &chunk, &img, &xoff, &yoff, &blockdata_expanded)) + return Py_BuildValue("i", "-1"); + + // tuple + PyObject *imgsize = PyObject_GetAttrString(img, "size"); + + PyObject *imgsize0_py = PySequence_GetItem(imgsize, 0); + PyObject *imgsize1_py = PySequence_GetItem(imgsize, 1); + Py_DECREF(imgsize); + + int imgsize0 = PyInt_AsLong(imgsize0_py); + int imgsize1 = PyInt_AsLong(imgsize1_py); + Py_DECREF(imgsize0_py); + Py_DECREF(imgsize1_py); + + + // get the block data directly from numpy: + PyObject *blocks_py = PyObject_GetAttrString(chunk, "blocks"); + char *blocks = PyArray_BYTES(blocks_py); + Py_DECREF(blocks_py); + + //PyObject *left_blocks = PyObject_GetAttrString(chunk, "left_blocks"); + //PyObject *right_blocks = PyObject_GetAttrString(chunk, "right_blocks"); + //PyObject *transparent_blocks = PyObject_GetAttrString(chunk, "transparent_blocks"); + + PyObject *textures = PyImport_ImportModule("textures"); + + // TODO can these be global static? these don't change during program execution + PyObject *blockmap = PyObject_GetAttrString(textures, "blockmap"); + PyObject *special_blocks = PyObject_GetAttrString(textures, "special_blocks"); + PyObject *specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); + + Py_DECREF(textures); + + //printf("render_loop\n"); + + int imgx, imgy; + int x, y, z; + for (x = 15; x > -1; x--) { + for (y = 0; y < 16; y++) { + imgx = xoff + x*12 + y*12; + imgy = yoff - x*6 + y*6 + 1632; // 1632 == 128*12 + 16*12//2 + for (z = 0; z < 128; z++) { + //printf("c/imgx/%d\n", imgx); + //printf("c/imgy/%d\n", imgy); + if ((imgx >= imgsize0 + 24) || (imgx <= -24)) { + imgy -= 12; // NOTE: we need to do this at every continue + continue; + } + if ((imgy >= imgsize1 + 24) || (imgy <= -24)) { + imgy -= 12; + continue; + } + + // get blockid + unsigned char block = getBlock(blocks, x, z, y); // Note the order: x,z,y + if (block == 0) { + imgy -= 12; + continue; + } + //printf("checking blockid %hhu\n", block); + PyObject *blockid = PyInt_FromLong(block); // TODO figure out how to DECREF this easily, instead at every 'continue'. + + + if ( (x != 0) && (y != 15) && (z != 127) && + !isTransparent(getBlock(blocks, x-1, z, y)) && + !isTransparent(getBlock(blocks, x, z+1, y)) && + !isTransparent(getBlock(blocks, x, z, y+1)) ) { + imgy -= 12; + continue; + } + + + if (!PySequence_Contains(special_blocks, blockid)) { + //t = textures.blockmap[blockid] + PyObject *t = PyList_GetItem(blockmap, block); + // PyList_GetItem returns borrowed ref + if (t == Py_None) { + printf("t == Py_None. blockid=%d\n", block); + imgy -= 12; + continue; + + } + + // note that this version of alpha_over has a different signature than the + // version in _composite.c + alpha_over(img, t, imgx, imgy ); + + } else { + // this should be a pointer to a unsigned char + void* ancilData_p = PyArray_GETPTR3(blockdata_expanded, x, y, z); + unsigned char ancilData = *((unsigned char*)ancilData_p); + if (block == 85) { // fence. skip the generate_pseudo_ancildata for now + imgy -= 12; + continue; + } + PyObject *tmp = PyTuple_New(2); + + Py_INCREF(blockid); // because SetItem steals + PyTuple_SetItem(tmp, 0, blockid); + PyTuple_SetItem(tmp, 1, PyInt_FromLong(ancilData)); + PyObject *t = PyDict_GetItem(specialblockmap, tmp); // this is a borrowed reference. no need to decref + Py_DECREF(tmp); + if (t != NULL) + alpha_over(img, t, imgx, imgy ); + imgy -= 12; + continue; + } + imgy -= 12; + } + } + } + + Py_DECREF(blockmap); + Py_DECREF(special_blocks); + Py_DECREF(specialblockmap); + + return Py_BuildValue("i",2); +} + +static PyMethodDef IterateMethods[] = { + {"render_loop", chunk_render, METH_VARARGS, + "Renders stuffs"}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + + +PyMODINIT_FUNC +init_iterate(void) +{ + (void) Py_InitModule("_iterate", IterateMethods); + import_array(); // for numpy +} diff --git a/setup.py b/setup.py index 55ee8bb..44c19af 100644 --- a/setup.py +++ b/setup.py @@ -36,6 +36,7 @@ if py2exe != None: # setup_kwargs['ext_modules'].append(Extension('_composite', ['_composite.c'], include_dirs=['.'], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) +setup_kwargs['ext_modules'].append(Extension('_iterate', ['iterate.c'], include_dirs=['.'], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) # tell build_ext to build the extension in-place # (NOT in build/) setup_kwargs['options']['build_ext'] = {'inplace' : 1} From 7555bcf1aff448bdc5362520db69954d5a03f09c Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 8 Mar 2011 16:54:20 -0500 Subject: [PATCH 016/213] moved c extensions into src/ --- setup.py | 4 ++-- _composite.c => src/composite.c | 0 iterate.c => src/iterate.c | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename _composite.c => src/composite.c (100%) rename iterate.c => src/iterate.c (100%) diff --git a/setup.py b/setup.py index 44c19af..597ee98 100644 --- a/setup.py +++ b/setup.py @@ -35,8 +35,8 @@ if py2exe != None: # _composite.c extension # -setup_kwargs['ext_modules'].append(Extension('_composite', ['_composite.c'], include_dirs=['.'], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) -setup_kwargs['ext_modules'].append(Extension('_iterate', ['iterate.c'], include_dirs=['.'], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) +setup_kwargs['ext_modules'].append(Extension('_composite', ['src/composite.c'], include_dirs=['.'], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) +setup_kwargs['ext_modules'].append(Extension('_iterate', ['src/iterate.c'], include_dirs=['.'], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) # tell build_ext to build the extension in-place # (NOT in build/) setup_kwargs['options']['build_ext'] = {'inplace' : 1} diff --git a/_composite.c b/src/composite.c similarity index 100% rename from _composite.c rename to src/composite.c diff --git a/iterate.c b/src/iterate.c similarity index 100% rename from iterate.c rename to src/iterate.c From 07dd219d20671347ae86f49b6e4da32ba98d4f47 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 8 Mar 2011 17:43:50 -0500 Subject: [PATCH 017/213] refactored c extensions into one --- .gitignore | 10 +- chunk.py | 4 +- composite.py | 2 +- setup.py | 8 +- src/composite.c | 68 ++++++++----- src/iterate.c | 242 +++-------------------------------------------- src/main.c | 18 ++++ src/overviewer.h | 39 ++++++++ 8 files changed, 123 insertions(+), 268 deletions(-) create mode 100644 src/main.c create mode 100644 src/overviewer.h diff --git a/.gitignore b/.gitignore index 28a4053..86d813a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,11 +7,11 @@ cachedir* ImPlatform.h Imaging.h -# various forms of compiled _composite extensions -_composite.so -_composite.pyd -_composite_d.pyd -_composite.dylib +# various forms of compiled c_overviewer extensions +c_overviewer.so +c_overviewer.pyd +c_overviewer_d.pyd +c_overviewer.dylib # Mac OS X noise .DS_Store diff --git a/chunk.py b/chunk.py index 37dd530..8454c2b 100644 --- a/chunk.py +++ b/chunk.py @@ -25,7 +25,7 @@ import nbt import textures import world import composite -import _iterate +import c_overviewer """ This module has routines related to rendering one particular chunk into an @@ -545,7 +545,7 @@ class ChunkRenderer(object): if not img: img = Image.new("RGBA", (384, 1728), (38,92,255,0)) - _iterate.render_loop(self, img, xoff, yoff, blockData_expanded) + c_overviewer.render_loop(self, img, xoff, yoff, blockData_expanded) for entity in tileEntities: if entity['id'] == 'Sign': diff --git a/composite.py b/composite.py index abe5562..f3edf6b 100644 --- a/composite.py +++ b/composite.py @@ -25,7 +25,7 @@ alpha-over extension cannot be found. extension_alpha_over = None try: - from _composite import alpha_over as _extension_alpha_over + from c_overviewer import alpha_over as _extension_alpha_over extension_alpha_over = _extension_alpha_over except ImportError: pass diff --git a/setup.py b/setup.py index 597ee98..1f5c93a 100644 --- a/setup.py +++ b/setup.py @@ -32,11 +32,11 @@ if py2exe != None: setup_kwargs['options']['py2exe'] = {'bundle_files' : 1, 'excludes': 'Tkinter'} # -# _composite.c extension +# c_overviewer extension # -setup_kwargs['ext_modules'].append(Extension('_composite', ['src/composite.c'], include_dirs=['.'], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) -setup_kwargs['ext_modules'].append(Extension('_iterate', ['src/iterate.c'], include_dirs=['.'], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) +c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c'] +setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.'], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) # tell build_ext to build the extension in-place # (NOT in build/) setup_kwargs['options']['build_ext'] = {'inplace' : 1} @@ -52,7 +52,7 @@ class CustomClean(clean): # try to remove '_composite.{so,pyd,...}' extension, # regardless of the current system's extension name convention build_ext = self.get_finalized_command('build_ext') - pretty_fname = build_ext.get_ext_filename('_composite') + pretty_fname = build_ext.get_ext_filename('c_overviewer') fname = pretty_fname if os.path.exists(fname): try: diff --git a/src/composite.c b/src/composite.c index 7ed9f71..b7f2cf3 100644 --- a/src/composite.c +++ b/src/composite.c @@ -22,8 +22,7 @@ * PIL paste if this extension is not found. */ -#include -#include +#include "overviewer.h" /* like (a * b + 127) / 255), but much faster on most platforms from PIL's _imaging.c */ @@ -36,7 +35,7 @@ typedef struct Imaging image; } ImagingObject; -static Imaging imaging_python_to_c(PyObject* obj) +Imaging imaging_python_to_c(PyObject* obj) { PyObject* im; Imaging image; @@ -59,26 +58,22 @@ static Imaging imaging_python_to_c(PyObject* obj) return image; } -static PyObject* _composite_alpha_over(PyObject* self, PyObject* args) +/* the alpha_over function, in a form that can be called from C */ +/* if xsize, ysize are negative, they are instead set to the size of the image in src */ +/* returns NULL on error, dest on success. You do NOT need to decref the return! */ +PyObject* alpha_over(PyObject* dest, PyObject* src, PyObject* mask, int dx, int dy, int xsize, int ysize) { - /* raw input python variables */ - PyObject* dest, * src, * pos, * mask; /* libImaging handles */ Imaging imDest, imSrc, imMask; /* cached blend properties */ int src_has_alpha, mask_offset, mask_stride; - /* destination position and size */ - int dx, dy, xsize, ysize; /* source position */ int sx, sy; /* iteration variables */ unsigned int x, y, i; /* temporary calculation variables */ int tmp1, tmp2, tmp3; - - if (!PyArg_ParseTuple(args, "OOOO", &dest, &src, &pos, &mask)) - return NULL; - + imDest = imaging_python_to_c(dest); imSrc = imaging_python_to_c(src); imMask = imaging_python_to_c(mask); @@ -119,11 +114,11 @@ static PyObject* _composite_alpha_over(PyObject* self, PyObject* args) /* how many bytes to skip to get to the next alpha byte */ mask_stride = imMask->pixelsize; - /* destination position read */ - if (!PyArg_ParseTuple(pos, "iiii", &dx, &dy, &xsize, &ysize)) + /* handle negative/zero sizes appropriately */ + if (xsize <= 0 || ysize <= 0) { - PyErr_SetString(PyExc_TypeError, "given blend destination rect is not valid"); - return NULL; + xsize = imSrc->xsize; + ysize = imSrc->ysize; } /* set up the source position, size and destination position */ @@ -158,7 +153,6 @@ static PyObject* _composite_alpha_over(PyObject* self, PyObject* args) if (xsize <= 0 || ysize <= 0) { /* nothing to do, return */ - Py_INCREF(dest); return dest; } @@ -208,17 +202,39 @@ static PyObject* _composite_alpha_over(PyObject* self, PyObject* args) } } - Py_INCREF(dest); return dest; } -static PyMethodDef _CompositeMethods[] = +/* wraps alpha_over so it can be called directly from python */ +/* properly refs the return value when needed: you DO need to decref the return */ +PyObject* alpha_over_wrap(PyObject* self, PyObject* args) { - {"alpha_over", _composite_alpha_over, METH_VARARGS, "alpha over composite function"}, - {NULL, NULL, 0, NULL} -}; - -PyMODINIT_FUNC init_composite(void) -{ - (void) Py_InitModule("_composite", _CompositeMethods); + /* raw input python variables */ + PyObject* dest, * src, * pos, * mask; + /* destination position and size */ + int dx, dy, xsize, ysize; + + if (!PyArg_ParseTuple(args, "OOOO", &dest, &src, &pos, &mask)) + return NULL; + + /* destination position read */ + if (!PyArg_ParseTuple(pos, "iiii", &dx, &dy, &xsize, &ysize)) + { + /* try again, but this time try to read a point */ + xsize = 0; + ysize = 0; + if (!PyArg_ParseTuple(pos, "ii", &dx, &dy)) + { + PyErr_SetString(PyExc_TypeError, "given blend destination rect is not valid"); + return NULL; + } + } + + PyObject* ret = alpha_over(dest, src, mask, dx, dy, xsize, ysize); + if (ret == dest) + { + /* Python needs us to own our return value */ + Py_INCREF(dest); + } + return ret; } diff --git a/src/iterate.c b/src/iterate.c index 9405bf5..7a5d1ef 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -1,19 +1,6 @@ -#include +#include "overviewer.h" #include -#include - - -/* like (a * b + 127) / 255), but much faster on most platforms - from PIL's _imaging.c */ -#define MULDIV255(a, b, tmp) \ - (tmp = (a) * (b) + 128, ((((tmp) >> 8) + (tmp)) >> 8)) - -typedef struct -{ - PyObject_HEAD - Imaging image; -} ImagingObject; // macro for getting blockID from a chunk of memory #define getBlock(blockThing, x,y,z) blockThing[ y + ( z * 128 + ( x * 128 * 16) ) ] @@ -24,213 +11,22 @@ static inline int isTransparent(unsigned char b) { } - -static Imaging imaging_python_to_c(PyObject* obj) +// helper to handle alpha_over calls involving a texture tuple +static inline PyObject* texture_alpha_over(PyObject* dest, PyObject* t, int imgx, int imgy) { - PyObject* im; - Imaging image; - - /* first, get the 'im' attribute */ - im = PyObject_GetAttrString(obj, "im"); - if (!im) - return NULL; + PyObject* src, * mask; - /* make sure 'im' is the right type */ - if (strcmp(im->ob_type->tp_name, "ImagingCore") != 0) - { - /* it's not -- raise an error and exit */ - PyErr_SetString(PyExc_TypeError, "image attribute 'im' is not a core Imaging type"); - return NULL; + src = PyTuple_GET_ITEM(t, 0); + mask = PyTuple_GET_ITEM(t, 1); + if (mask == Py_None) { + mask = src; } - image = ((ImagingObject*)im)->image; - Py_DECREF(im); - return image; + return alpha_over(dest, src, mask, imgx, imgy, 0, 0); } - - -// TODO refact iterate.c and _composite.c so share implementations -static PyObject* alpha_over(PyObject* dest, PyObject* t, int imgx, int imgy) -{ - /* raw input python variables */ - PyObject * src, * mask; - /* libImaging handles */ - Imaging imDest, imSrc, imMask; - /* cached blend properties */ - int src_has_alpha, mask_offset, mask_stride; - /* destination position and size */ - int dx, dy, xsize, ysize; - /* source position */ - int sx, sy; - /* iteration variables */ - unsigned int x, y, i; - /* temporary calculation variables */ - int tmp1, tmp2, tmp3; - - src = PyTuple_GET_ITEM(t, 0); - mask = PyTuple_GET_ITEM(t, 1); - if (mask == Py_None) { - printf("mask is none\n"); - Py_INCREF(mask); - mask = src; - } - //if (!PyArg_ParseTuple(args, "OOOO", &dest, &src, &pos, &mask)) - // return NULL; - - imDest = imaging_python_to_c(dest); - imSrc = imaging_python_to_c(src); - imMask = imaging_python_to_c(mask); - - //printf("alpha1\n"); - if (!imDest || !imSrc || !imMask) { - PyErr_SetString(PyExc_ValueError, "dest, src, or mask is missing"); - return NULL; - } - //printf("alpha2\n"); - - /* check the various image modes, make sure they make sense */ - if (strcmp(imDest->mode, "RGBA") != 0) - { - PyErr_SetString(PyExc_ValueError, "given destination image does not have mode \"RGBA\""); - return NULL; - } - - if (strcmp(imSrc->mode, "RGBA") != 0 && strcmp(imSrc->mode, "RGB") != 0) - { - PyErr_SetString(PyExc_ValueError, "given source image does not have mode \"RGBA\" or \"RGB\""); - return NULL; - } - - if (strcmp(imMask->mode, "RGBA") != 0 && strcmp(imMask->mode, "L") != 0) - { - PyErr_SetString(PyExc_ValueError, "given mask image does not have mode \"RGBA\" or \"L\""); - return NULL; - } - - /* make sure mask size matches src size */ - if (imSrc->xsize != imMask->xsize || imSrc->ysize != imMask->ysize) - { - PyErr_SetString(PyExc_ValueError, "mask and source image sizes do not match"); - return NULL; - } - - //printf("alpha3\n"); - /* set up flags for the src/mask type */ - src_has_alpha = (imSrc->pixelsize == 4 ? 1 : 0); - /* how far into image the first alpha byte resides */ - mask_offset = (imMask->pixelsize == 4 ? 3 : 0); - /* how many bytes to skip to get to the next alpha byte */ - mask_stride = imMask->pixelsize; - - //printf("alpha4\n"); - /* destination position read */ - //if (!PyArg_ParseTuple(pos, "iiii", &dx, &dy, &xsize, &ysize)) - //{ - // PyErr_SetString(PyExc_TypeError, "given blend destination rect is not valid"); - // return NULL; - //} - dx = imgx; - dy = imgy; - - xsize = imSrc->xsize; - ysize = imSrc->ysize; - //printf("xsize/ysize %d/%d\n", xsize, ysize); - - //printf("alpha5\n"); - - /* set up the source position, size and destination position */ - /* handle negative dest pos */ - if (dx < 0) - { - sx = -dx; - dx = 0; - } else { - sx = 0; - } - - if (dy < 0) - { - sy = -dy; - dy = 0; - } else { - sy = 0; - } - - /* set up source dimensions */ - xsize -= sx; - ysize -= sy; - - //printf("imDest->xsize=%d imDest->yize=%d\n", imDest->xsize, imDest->ysize); - - /* clip dimensions, if needed */ - if (dx + xsize > imDest->xsize) - xsize = imDest->xsize - dx; - if (dy + ysize > imDest->ysize) - ysize = imDest->ysize - dy; - - /* check that there remains any blending to be done */ - if (xsize <= 0 || ysize <= 0) - { - /* nothing to do, return */ - Py_INCREF(dest); - return dest; - } - - for (y = 0; y < ysize; y++) - { - UINT8* out = (UINT8*) imDest->image[dy + y] + dx*4; - UINT8* outmask = (UINT8*) imDest->image[dy + y] + dx*4 + 3; - UINT8* in = (UINT8*) imSrc->image[sy + y] + sx*(imSrc->pixelsize); - UINT8* inmask = (UINT8*) imMask->image[sy + y] + sx*mask_stride + mask_offset; - - for (x = 0; x < xsize; x++) - { - /* special cases */ - if (*inmask == 255 || *outmask == 0) - { - *outmask = *inmask; - - *out = *in; - out++, in++; - *out = *in; - out++, in++; - *out = *in; - out++, in++; - } else if (*inmask == 0) { - /* do nothing -- source is fully transparent */ - out += 3; - in += 3; - } else { - /* general case */ - int alpha = *inmask + MULDIV255(*outmask, 255 - *inmask, tmp1); - for (i = 0; i < 3; i++) - { - /* general case */ - *out = MULDIV255(*in, *inmask, tmp1) + MULDIV255(MULDIV255(*out, *outmask, tmp2), 255 - *inmask, tmp3); - *out = (*out * 255) / alpha; - out++, in++; - } - - *outmask = alpha; - } - - out++; - if (src_has_alpha) - in++; - outmask += 4; - inmask += mask_stride; - } - } - - Py_INCREF(dest); - return dest; -} - - - // TODO triple check this to make sure reference counting is correct -static PyObject* +PyObject* chunk_render(PyObject *self, PyObject *args) { PyObject *chunk; @@ -324,7 +120,7 @@ chunk_render(PyObject *self, PyObject *args) { // note that this version of alpha_over has a different signature than the // version in _composite.c - alpha_over(img, t, imgx, imgy ); + texture_alpha_over(img, t, imgx, imgy ); } else { // this should be a pointer to a unsigned char @@ -342,7 +138,7 @@ chunk_render(PyObject *self, PyObject *args) { PyObject *t = PyDict_GetItem(specialblockmap, tmp); // this is a borrowed reference. no need to decref Py_DECREF(tmp); if (t != NULL) - alpha_over(img, t, imgx, imgy ); + texture_alpha_over(img, t, imgx, imgy ); imgy -= 12; continue; } @@ -357,17 +153,3 @@ chunk_render(PyObject *self, PyObject *args) { return Py_BuildValue("i",2); } - -static PyMethodDef IterateMethods[] = { - {"render_loop", chunk_render, METH_VARARGS, - "Renders stuffs"}, - {NULL, NULL, 0, NULL} /* Sentinel */ -}; - - -PyMODINIT_FUNC -init_iterate(void) -{ - (void) Py_InitModule("_iterate", IterateMethods); - import_array(); // for numpy -} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..29fa4e6 --- /dev/null +++ b/src/main.c @@ -0,0 +1,18 @@ +#include "overviewer.h" + +#include + +static PyMethodDef COverviewerMethods[] = { + {"alpha_over", alpha_over_wrap, METH_VARARGS, + "alpha over composite function"}, + {"render_loop", chunk_render, METH_VARARGS, + "Renders stuffs"}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +PyMODINIT_FUNC +initc_overviewer(void) +{ + (void) Py_InitModule("c_overviewer", COverviewerMethods); + import_array(); // for numpy +} diff --git a/src/overviewer.h b/src/overviewer.h new file mode 100644 index 0000000..692f5d4 --- /dev/null +++ b/src/overviewer.h @@ -0,0 +1,39 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + +/* + * This is a general include file for the Overviewer C extension. It + * lists useful, defined functions as well as those that are exported + * to python, so all files can use them. + */ + +#ifndef __OVERVIEWER_H_INCLUDED__ +#define __OVERVIEWER_H_INCLUDED__ + +/* Python and PIL headers */ +#include +#include + +/* in composite.c */ +Imaging imaging_python_to_c(PyObject* obj); +PyObject* alpha_over(PyObject* dest, PyObject* src, PyObject* mask, int dx, int dy, int xsize, int ysize); +PyObject* alpha_over_wrap(PyObject* self, PyObject* args); + +/* in iterate.c */ +PyObject* chunk_render(PyObject *self, PyObject *args); + +#endif /* __OVERVIEWER_H_INCLUDED__ */ From 5852c17ec846bc7cf2553447eec0c8896a851ca5 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 8 Mar 2011 19:58:09 -0500 Subject: [PATCH 018/213] changed my code style to be more in line with eminence's (and PEP 7), added GPL notice --- src/composite.c | 383 +++++++++++++++++++++++------------------------ src/iterate.c | 38 +++-- src/main.c | 29 +++- src/overviewer.h | 9 +- 4 files changed, 247 insertions(+), 212 deletions(-) diff --git a/src/composite.c b/src/composite.c index b7f2cf3..6904e75 100644 --- a/src/composite.c +++ b/src/composite.c @@ -29,212 +29,211 @@ #define MULDIV255(a, b, tmp) \ (tmp = (a) * (b) + 128, ((((tmp) >> 8) + (tmp)) >> 8)) -typedef struct -{ - PyObject_HEAD - Imaging image; +typedef struct { + PyObject_HEAD; + Imaging image; } ImagingObject; -Imaging imaging_python_to_c(PyObject* obj) +Imaging +imaging_python_to_c(PyObject *obj) { - PyObject* im; - Imaging image; + PyObject *im; + Imaging image; - /* first, get the 'im' attribute */ - im = PyObject_GetAttrString(obj, "im"); - if (!im) - return NULL; - - /* make sure 'im' is the right type */ - if (strcmp(im->ob_type->tp_name, "ImagingCore") != 0) - { - /* it's not -- raise an error and exit */ - PyErr_SetString(PyExc_TypeError, "image attribute 'im' is not a core Imaging type"); - return NULL; - } - - image = ((ImagingObject*)im)->image; - Py_DECREF(im); - return image; + /* first, get the 'im' attribute */ + im = PyObject_GetAttrString(obj, "im"); + if (!im) + return NULL; + + /* make sure 'im' is the right type */ + if (strcmp(im->ob_type->tp_name, "ImagingCore") != 0) { + /* it's not -- raise an error and exit */ + PyErr_SetString(PyExc_TypeError, + "image attribute 'im' is not a core Imaging type"); + return NULL; + } + + image = ((ImagingObject *)im)->image; + Py_DECREF(im); + return image; } /* the alpha_over function, in a form that can be called from C */ /* if xsize, ysize are negative, they are instead set to the size of the image in src */ /* returns NULL on error, dest on success. You do NOT need to decref the return! */ -PyObject* alpha_over(PyObject* dest, PyObject* src, PyObject* mask, int dx, int dy, int xsize, int ysize) +PyObject * +alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, int dy, + int xsize, int ysize) { - /* libImaging handles */ - Imaging imDest, imSrc, imMask; - /* cached blend properties */ - int src_has_alpha, mask_offset, mask_stride; - /* source position */ - int sx, sy; - /* iteration variables */ - unsigned int x, y, i; - /* temporary calculation variables */ - int tmp1, tmp2, tmp3; - - imDest = imaging_python_to_c(dest); - imSrc = imaging_python_to_c(src); - imMask = imaging_python_to_c(mask); - - if (!imDest || !imSrc || !imMask) - return NULL; - - /* check the various image modes, make sure they make sense */ - if (strcmp(imDest->mode, "RGBA") != 0) - { - PyErr_SetString(PyExc_ValueError, "given destination image does not have mode \"RGBA\""); - return NULL; - } - - if (strcmp(imSrc->mode, "RGBA") != 0 && strcmp(imSrc->mode, "RGB") != 0) - { - PyErr_SetString(PyExc_ValueError, "given source image does not have mode \"RGBA\" or \"RGB\""); - return NULL; - } - - if (strcmp(imMask->mode, "RGBA") != 0 && strcmp(imMask->mode, "L") != 0) - { - PyErr_SetString(PyExc_ValueError, "given mask image does not have mode \"RGBA\" or \"L\""); - return NULL; - } - - /* make sure mask size matches src size */ - if (imSrc->xsize != imMask->xsize || imSrc->ysize != imMask->ysize) - { - PyErr_SetString(PyExc_ValueError, "mask and source image sizes do not match"); - return NULL; - } - - /* set up flags for the src/mask type */ + /* libImaging handles */ + Imaging imDest, imSrc, imMask; + /* cached blend properties */ + int src_has_alpha, mask_offset, mask_stride; + /* source position */ + int sx, sy; + /* iteration variables */ + unsigned int x, y, i; + /* temporary calculation variables */ + int tmp1, tmp2, tmp3; + + imDest = imaging_python_to_c(dest); + imSrc = imaging_python_to_c(src); + imMask = imaging_python_to_c(mask); + + if (!imDest || !imSrc || !imMask) + return NULL; + + /* check the various image modes, make sure they make sense */ + if (strcmp(imDest->mode, "RGBA") != 0) { + PyErr_SetString(PyExc_ValueError, + "given destination image does not have mode \"RGBA\""); + return NULL; + } + + if (strcmp(imSrc->mode, "RGBA") != 0 && strcmp(imSrc->mode, "RGB") != 0) { + PyErr_SetString(PyExc_ValueError, + "given source image does not have mode \"RGBA\" or \"RGB\""); + return NULL; + } + + if (strcmp(imMask->mode, "RGBA") != 0 && strcmp(imMask->mode, "L") != 0) { + PyErr_SetString(PyExc_ValueError, + "given mask image does not have mode \"RGBA\" or \"L\""); + return NULL; + } + + /* make sure mask size matches src size */ + if (imSrc->xsize != imMask->xsize || imSrc->ysize != imMask->ysize) { + PyErr_SetString(PyExc_ValueError, + "mask and source image sizes do not match"); + return NULL; + } + + /* set up flags for the src/mask type */ src_has_alpha = (imSrc->pixelsize == 4 ? 1 : 0); - /* how far into image the first alpha byte resides */ - mask_offset = (imMask->pixelsize == 4 ? 3 : 0); - /* how many bytes to skip to get to the next alpha byte */ - mask_stride = imMask->pixelsize; - - /* handle negative/zero sizes appropriately */ - if (xsize <= 0 || ysize <= 0) - { - xsize = imSrc->xsize; - ysize = imSrc->ysize; - } - - /* set up the source position, size and destination position */ - /* handle negative dest pos */ - if (dx < 0) - { - sx = -dx; - dx = 0; - } else { - sx = 0; - } - - if (dy < 0) - { - sy = -dy; - dy = 0; - } else { - sy = 0; - } - - /* set up source dimensions */ - xsize -= sx; - ysize -= sy; - - /* clip dimensions, if needed */ - if (dx + xsize > imDest->xsize) - xsize = imDest->xsize - dx; - if (dy + ysize > imDest->ysize) - ysize = imDest->ysize - dy; - - /* check that there remains any blending to be done */ - if (xsize <= 0 || ysize <= 0) - { - /* nothing to do, return */ - return dest; - } - - for (y = 0; y < ysize; y++) - { - UINT8* out = (UINT8*) imDest->image[dy + y] + dx*4; - UINT8* outmask = (UINT8*) imDest->image[dy + y] + dx*4 + 3; - UINT8* in = (UINT8*) imSrc->image[sy + y] + sx*(imSrc->pixelsize); - UINT8* inmask = (UINT8*) imMask->image[sy + y] + sx*mask_stride + mask_offset; - - for (x = 0; x < xsize; x++) - { - /* special cases */ - if (*inmask == 255 || *outmask == 0) - { - *outmask = *inmask; - - *out = *in; - out++, in++; - *out = *in; - out++, in++; - *out = *in; - out++, in++; - } else if (*inmask == 0) { - /* do nothing -- source is fully transparent */ - out += 3; - in += 3; - } else { - /* general case */ - int alpha = *inmask + MULDIV255(*outmask, 255 - *inmask, tmp1); - for (i = 0; i < 3; i++) - { - /* general case */ - *out = MULDIV255(*in, *inmask, tmp1) + MULDIV255(MULDIV255(*out, *outmask, tmp2), 255 - *inmask, tmp3); - *out = (*out * 255) / alpha; - out++, in++; - } - - *outmask = alpha; - } - - out++; - if (src_has_alpha) - in++; - outmask += 4; - inmask += mask_stride; - } - } - - return dest; + /* how far into image the first alpha byte resides */ + mask_offset = (imMask->pixelsize == 4 ? 3 : 0); + /* how many bytes to skip to get to the next alpha byte */ + mask_stride = imMask->pixelsize; + + /* handle negative/zero sizes appropriately */ + if (xsize <= 0 || ysize <= 0) { + xsize = imSrc->xsize; + ysize = imSrc->ysize; + } + + /* set up the source position, size and destination position */ + /* handle negative dest pos */ + if (dx < 0) { + sx = -dx; + dx = 0; + } + else { + sx = 0; + } + + if (dy < 0) { + sy = -dy; + dy = 0; + } + else { + sy = 0; + } + + /* set up source dimensions */ + xsize -= sx; + ysize -= sy; + + /* clip dimensions, if needed */ + if (dx + xsize > imDest->xsize) + xsize = imDest->xsize - dx; + if (dy + ysize > imDest->ysize) + ysize = imDest->ysize - dy; + + /* check that there remains any blending to be done */ + if (xsize <= 0 || ysize <= 0) { + /* nothing to do, return */ + return dest; + } + + for (y = 0; y < ysize; y++) { + UINT8 *out = (UINT8 *)imDest->image[dy + y] + dx * 4; + UINT8 *outmask = (UINT8 *)imDest->image[dy + y] + dx * 4 + 3; + UINT8 *in = (UINT8 *)imSrc->image[sy + y] + sx * (imSrc->pixelsize); + UINT8 *inmask = (UINT8 *)imMask->image[sy + y] + sx * mask_stride + mask_offset; + + for (x = 0; x < xsize; x++) { + /* special cases */ + if (*inmask == 255 || *outmask == 0) { + *outmask = *inmask; + + *out = *in; + out++, in++; + *out = *in; + out++, in++; + *out = *in; + out++, in++; + } + else if (*inmask == 0) { + /* do nothing -- source is fully transparent */ + out += 3; + in += 3; + } + else { + /* general case */ + int alpha = *inmask + MULDIV255(*outmask, 255 - *inmask, tmp1); + for (i = 0; i < 3; i++) { + /* general case */ + *out = MULDIV255(*in, *inmask, tmp1) + + MULDIV255(MULDIV255(*out, *outmask, tmp2), 255 - *inmask, tmp3); + + *out = (*out * 255) / alpha; + out++, in++; + } + + *outmask = alpha; + } + + out++; + if (src_has_alpha) + in++; + outmask += 4; + inmask += mask_stride; + } + } + + return dest; } /* wraps alpha_over so it can be called directly from python */ /* properly refs the return value when needed: you DO need to decref the return */ -PyObject* alpha_over_wrap(PyObject* self, PyObject* args) +PyObject * +alpha_over_wrap(PyObject *self, PyObject *args) { - /* raw input python variables */ - PyObject* dest, * src, * pos, * mask; - /* destination position and size */ - int dx, dy, xsize, ysize; - - if (!PyArg_ParseTuple(args, "OOOO", &dest, &src, &pos, &mask)) - return NULL; - - /* destination position read */ - if (!PyArg_ParseTuple(pos, "iiii", &dx, &dy, &xsize, &ysize)) - { - /* try again, but this time try to read a point */ - xsize = 0; - ysize = 0; - if (!PyArg_ParseTuple(pos, "ii", &dx, &dy)) - { - PyErr_SetString(PyExc_TypeError, "given blend destination rect is not valid"); - return NULL; - } - } - - PyObject* ret = alpha_over(dest, src, mask, dx, dy, xsize, ysize); - if (ret == dest) - { - /* Python needs us to own our return value */ - Py_INCREF(dest); - } - return ret; + /* raw input python variables */ + PyObject *dest, *src, *pos, *mask; + /* destination position and size */ + int dx, dy, xsize, ysize; + + if (!PyArg_ParseTuple(args, "OOOO", &dest, &src, &pos, &mask)) + return NULL; + + /* destination position read */ + if (!PyArg_ParseTuple(pos, "iiii", &dx, &dy, &xsize, &ysize)) { + /* try again, but this time try to read a point */ + xsize = 0; + ysize = 0; + if (!PyArg_ParseTuple(pos, "ii", &dx, &dy)) { + PyErr_SetString(PyExc_TypeError, + "given blend destination rect is not valid"); + return NULL; + } + } + + PyObject *ret = alpha_over(dest, src, mask, dx, dy, xsize, ysize); + if (ret == dest) { + /* Python needs us to own our return value */ + Py_INCREF(dest); + } + return ret; } diff --git a/src/iterate.c b/src/iterate.c index 7a5d1ef..c7676d6 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -1,3 +1,20 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + #include "overviewer.h" #include @@ -12,17 +29,18 @@ static inline int isTransparent(unsigned char b) { } // helper to handle alpha_over calls involving a texture tuple -static inline PyObject* texture_alpha_over(PyObject* dest, PyObject* t, int imgx, int imgy) +static inline PyObject * +texture_alpha_over(PyObject *dest, PyObject *t, int imgx, int imgy) { - PyObject* src, * mask; - - src = PyTuple_GET_ITEM(t, 0); - mask = PyTuple_GET_ITEM(t, 1); - if (mask == Py_None) { - mask = src; - } - - return alpha_over(dest, src, mask, imgx, imgy, 0, 0); + PyObject* src, * mask; + + src = PyTuple_GET_ITEM(t, 0); + mask = PyTuple_GET_ITEM(t, 1); + if (mask == Py_None) { + mask = src; + } + + return alpha_over(dest, src, mask, imgx, imgy, 0, 0); } // TODO triple check this to make sure reference counting is correct diff --git a/src/main.c b/src/main.c index 29fa4e6..7f56918 100644 --- a/src/main.c +++ b/src/main.c @@ -1,18 +1,35 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + #include "overviewer.h" #include static PyMethodDef COverviewerMethods[] = { - {"alpha_over", alpha_over_wrap, METH_VARARGS, - "alpha over composite function"}, + {"alpha_over", alpha_over_wrap, METH_VARARGS, + "alpha over composite function"}, {"render_loop", chunk_render, METH_VARARGS, - "Renders stuffs"}, - {NULL, NULL, 0, NULL} /* Sentinel */ + "Renders stuffs"}, + {NULL, NULL, 0, NULL} /* Sentinel */ }; PyMODINIT_FUNC initc_overviewer(void) { - (void) Py_InitModule("c_overviewer", COverviewerMethods); - import_array(); // for numpy + (void)Py_InitModule("c_overviewer", COverviewerMethods); + import_array(); // for numpy } diff --git a/src/overviewer.h b/src/overviewer.h index 692f5d4..5746b53 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -29,11 +29,12 @@ #include /* in composite.c */ -Imaging imaging_python_to_c(PyObject* obj); -PyObject* alpha_over(PyObject* dest, PyObject* src, PyObject* mask, int dx, int dy, int xsize, int ysize); -PyObject* alpha_over_wrap(PyObject* self, PyObject* args); +Imaging imaging_python_to_c(PyObject *obj); +PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, + int dy, int xsize, int ysize); +PyObject *alpha_over_wrap(PyObject *self, PyObject *args); /* in iterate.c */ -PyObject* chunk_render(PyObject *self, PyObject *args); +PyObject *chunk_render(PyObject *self, PyObject *args); #endif /* __OVERVIEWER_H_INCLUDED__ */ From 6519d71eaa3f5b22bef3b0ea7b2cfba2348783ad Mon Sep 17 00:00:00 2001 From: Xon Date: Wed, 9 Mar 2011 19:10:35 -0500 Subject: [PATCH 019/213] changed setup.py to include numpy include paths from --- setup.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 1f5c93a..059631d 100644 --- a/setup.py +++ b/setup.py @@ -35,8 +35,17 @@ if py2exe != None: # c_overviewer extension # +# Third-party modules - we depend on numpy for everything +import numpy +# Obtain the numpy include directory. This logic works across numpy versions. +try: + numpy_include = numpy.get_include() +except AttributeError: + numpy_include = numpy.get_numpy_include() + + c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c'] -setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.'], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) +setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) # tell build_ext to build the extension in-place # (NOT in build/) setup_kwargs['options']['build_ext'] = {'inplace' : 1} From c79b0b9b208bde4ddd23e5311db36518efdfe423 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Fri, 11 Mar 2011 17:43:18 -0500 Subject: [PATCH 020/213] changed imgy calculation and moved "imgy -= 12" to start of loop from discussion in --- src/iterate.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index c7676d6..ae894d4 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -93,23 +93,21 @@ chunk_render(PyObject *self, PyObject *args) { for (x = 15; x > -1; x--) { for (y = 0; y < 16; y++) { imgx = xoff + x*12 + y*12; - imgy = yoff - x*6 + y*6 + 1632; // 1632 == 128*12 + 16*12//2 + /* 128*12 -- offset for z direction, 15*6 -- offset for x */ + imgy = yoff - x*6 + y*6 + 128*12 + 15*6; for (z = 0; z < 128; z++) { - //printf("c/imgx/%d\n", imgx); - //printf("c/imgy/%d\n", imgy); + imgy -= 12; + if ((imgx >= imgsize0 + 24) || (imgx <= -24)) { - imgy -= 12; // NOTE: we need to do this at every continue continue; } if ((imgy >= imgsize1 + 24) || (imgy <= -24)) { - imgy -= 12; continue; } // get blockid unsigned char block = getBlock(blocks, x, z, y); // Note the order: x,z,y if (block == 0) { - imgy -= 12; continue; } //printf("checking blockid %hhu\n", block); @@ -120,7 +118,6 @@ chunk_render(PyObject *self, PyObject *args) { !isTransparent(getBlock(blocks, x-1, z, y)) && !isTransparent(getBlock(blocks, x, z+1, y)) && !isTransparent(getBlock(blocks, x, z, y+1)) ) { - imgy -= 12; continue; } @@ -131,7 +128,6 @@ chunk_render(PyObject *self, PyObject *args) { // PyList_GetItem returns borrowed ref if (t == Py_None) { printf("t == Py_None. blockid=%d\n", block); - imgy -= 12; continue; } @@ -145,7 +141,6 @@ chunk_render(PyObject *self, PyObject *args) { void* ancilData_p = PyArray_GETPTR3(blockdata_expanded, x, y, z); unsigned char ancilData = *((unsigned char*)ancilData_p); if (block == 85) { // fence. skip the generate_pseudo_ancildata for now - imgy -= 12; continue; } PyObject *tmp = PyTuple_New(2); @@ -157,10 +152,8 @@ chunk_render(PyObject *self, PyObject *args) { Py_DECREF(tmp); if (t != NULL) texture_alpha_over(img, t, imgx, imgy ); - imgy -= 12; continue; } - imgy -= 12; } } } From 997c247d6c715c095de75c919fa41a49dd9850a1 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Fri, 11 Mar 2011 18:05:21 -0500 Subject: [PATCH 021/213] updated marker positioning code to jive with updated rendering code --- web_assets/functions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web_assets/functions.js b/web_assets/functions.js index 9b42fda..174a43a 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -365,6 +365,7 @@ function initialize() { var lat = 0.5; // the following metrics mimic those in ChunkRenderer.chunk_render in "chunk.py" + // or, equivalently, chunk_render in src/iterate.c // each block on X axis adds 12px to x and subtracts 6px from y lng += 12 * x * perPixel; @@ -377,9 +378,8 @@ function initialize() { // each block down along Z adds 12px to y lat += 12 * (128 - z) * perPixel; - // add on 12 px to the X coordinate and 18px to the Y to center our point + // add on 12 px to the X coordinate to center our point lng += 12 * perPixel; - lat += 18 * perPixel; return new google.maps.LatLng(lat, lng); } From ba83b6bf005ec057c453799700e33f5dabb016a1 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Thu, 17 Mar 2011 22:22:45 -0400 Subject: [PATCH 022/213] added contrib script to clean out old caches --- contrib/clearOldCache.py | 94 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 contrib/clearOldCache.py diff --git a/contrib/clearOldCache.py b/contrib/clearOldCache.py new file mode 100644 index 0000000..18b402b --- /dev/null +++ b/contrib/clearOldCache.py @@ -0,0 +1,94 @@ +#!/usr/bin/python + +usage = "python contrib/%prog [OPTIONS] " + +description = """ +This script will delete files from the old chunk-based cache, a lot +like the old `gmap.py -d World/` command. You should only use this if +you're updating from an older version of Overviewer, and you want to +clean up your world folder. +""" + +from optparse import OptionParser +import sys +import re +import os.path + +# sys.path wrangling, so we can access Overviewer code +overviewer_dir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] +sys.path.insert(0, overviewer_dir) + +import world +from gmap import list_worlds + +def main(): + parser = OptionParser(usage=usage, description=description) + parser.add_option("-d", "--dry-run", dest="dry", action="store_true", + help="Don't actually delete anything. Best used with -v.") + parser.add_option("-k", "--keep-dirs", dest="keep", action="store_true", + help="Keep the world directories intact, even if they are empty.") + parser.add_option("-v", "--verbose", dest="verbose", action="store_true", + help="Log each and every file that is deleted.") + + opt, args = parser.parse_args() + + if not len(args) == 1: + parser.print_help() + sys.exit(1) + + worlddir = args[0] + + if not os.path.exists(worlddir): + # world given is either world number, or name + worlds = world.get_worlds() + + # if there are no worlds found at all, exit now + if not worlds: + parser.print_help() + print "\nInvalid world path" + sys.exit(1) + + try: + worldnum = int(worlddir) + worlddir = worlds[worldnum]['path'] + except ValueError: + # it wasn't a number or path, try using it as a name + try: + worlddir = worlds[worlddir]['path'] + except KeyError: + # it's not a number, name, or path + parser.print_help() + print "Invalid world name or path" + sys.exit(1) + except KeyError: + # it was an invalid number + parser.print_help() + print "Invalid world number" + sys.exit(1) + + files_deleted = 0 + dirs_deleted = 0 + + imgre = re.compile(r'img\.[^.]+\.[^.]+\.nocave\.\w+\.png$') + for dirpath, dirnames, filenames in os.walk(worlddir, topdown=False): + for f in filenames: + if imgre.match(f): + filepath = os.path.join(dirpath, f) + if opt.verbose: + print "Deleting %s" % (filepath,) + if not opt.dry: + os.unlink(filepath) + files_deleted += 1 + + if not opt.keep: + if len(os.listdir(dirpath)) == 0: + if opt.verbose: + print "Deleting %s" % (dirpath,) + if not opt.dry: + os.rmdir(dirpath) + dirs_deleted += 1 + + print "%i files and %i directories deleted." % (files_deleted, dirs_deleted) + +if __name__ == "__main__": + main() From 6941bc3378baec784f1c0ba7bd780f3c4934cddb Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Thu, 17 Mar 2011 23:04:41 -0400 Subject: [PATCH 023/213] fixed up new C code to be (mostly) C89-compliant *mostly*, because the "inline" keyword isn't C standard until C99, but I'm leaving it in because most compilers support it and it's really handy. If it becomes an issue, we can deal with it later. --- src/composite.c | 6 ++- src/iterate.c | 98 +++++++++++++++++++++++++++++-------------------- src/main.c | 3 +- 3 files changed, 65 insertions(+), 42 deletions(-) diff --git a/src/composite.c b/src/composite.c index 6904e75..4b58403 100644 --- a/src/composite.c +++ b/src/composite.c @@ -30,7 +30,7 @@ (tmp = (a) * (b) + 128, ((((tmp) >> 8) + (tmp)) >> 8)) typedef struct { - PyObject_HEAD; + PyObject_HEAD Imaging image; } ImagingObject; @@ -214,6 +214,8 @@ alpha_over_wrap(PyObject *self, PyObject *args) PyObject *dest, *src, *pos, *mask; /* destination position and size */ int dx, dy, xsize, ysize; + /* return value: dest image on success */ + PyObject *ret; if (!PyArg_ParseTuple(args, "OOOO", &dest, &src, &pos, &mask)) return NULL; @@ -230,7 +232,7 @@ alpha_over_wrap(PyObject *self, PyObject *args) } } - PyObject *ret = alpha_over(dest, src, mask, dx, dy, xsize, ysize); + ret = alpha_over(dest, src, mask, dx, dy, xsize, ysize); if (ret == dest) { /* Python needs us to own our return value */ Py_INCREF(dest); diff --git a/src/iterate.c b/src/iterate.c index ae894d4..824b25c 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -19,16 +19,16 @@ #include -// macro for getting blockID from a chunk of memory +/* macro for getting blockID from a chunk of memory */ #define getBlock(blockThing, x,y,z) blockThing[ y + ( z * 128 + ( x * 128 * 16) ) ] static inline int isTransparent(unsigned char b) { - // TODO expand this to include all transparent blocks + /* TODO expand this to include all transparent blocks */ return b == 0; } -// helper to handle alpha_over calls involving a texture tuple +/* helper to handle alpha_over calls involving a texture tuple */ static inline PyObject * texture_alpha_over(PyObject *dest, PyObject *t, int imgx, int imgy) { @@ -43,7 +43,7 @@ texture_alpha_over(PyObject *dest, PyObject *t, int imgx, int imgy) return alpha_over(dest, src, mask, imgx, imgy, 0, 0); } -// TODO triple check this to make sure reference counting is correct +/* TODO triple check this to make sure reference counting is correct */ PyObject* chunk_render(PyObject *self, PyObject *args) { @@ -51,51 +51,63 @@ chunk_render(PyObject *self, PyObject *args) { PyObject *blockdata_expanded; int xoff, yoff; PyObject *img; + + PyObject *imgsize, *imgsize0_py, *imgsize1_py; + int imgsize0, imgsize1; + + PyObject *blocks_py; + char *blocks; + + PyObject *textures, *blockmap, *special_blocks, *specialblockmap; + + int imgx, imgy; + int x, y, z; if (!PyArg_ParseTuple(args, "OOiiO", &chunk, &img, &xoff, &yoff, &blockdata_expanded)) return Py_BuildValue("i", "-1"); - // tuple - PyObject *imgsize = PyObject_GetAttrString(img, "size"); + /* tuple */ + imgsize = PyObject_GetAttrString(img, "size"); - PyObject *imgsize0_py = PySequence_GetItem(imgsize, 0); - PyObject *imgsize1_py = PySequence_GetItem(imgsize, 1); + imgsize0_py = PySequence_GetItem(imgsize, 0); + imgsize1_py = PySequence_GetItem(imgsize, 1); Py_DECREF(imgsize); - int imgsize0 = PyInt_AsLong(imgsize0_py); - int imgsize1 = PyInt_AsLong(imgsize1_py); + imgsize0 = PyInt_AsLong(imgsize0_py); + imgsize1 = PyInt_AsLong(imgsize1_py); Py_DECREF(imgsize0_py); Py_DECREF(imgsize1_py); - // get the block data directly from numpy: - PyObject *blocks_py = PyObject_GetAttrString(chunk, "blocks"); - char *blocks = PyArray_BYTES(blocks_py); + /* get the block data directly from numpy: */ + blocks_py = PyObject_GetAttrString(chunk, "blocks"); + blocks = PyArray_BYTES(blocks_py); Py_DECREF(blocks_py); - //PyObject *left_blocks = PyObject_GetAttrString(chunk, "left_blocks"); - //PyObject *right_blocks = PyObject_GetAttrString(chunk, "right_blocks"); - //PyObject *transparent_blocks = PyObject_GetAttrString(chunk, "transparent_blocks"); + /* + PyObject *left_blocks = PyObject_GetAttrString(chunk, "left_blocks"); + PyObject *right_blocks = PyObject_GetAttrString(chunk, "right_blocks"); + PyObject *transparent_blocks = PyObject_GetAttrString(chunk, "transparent_blocks"); + */ - PyObject *textures = PyImport_ImportModule("textures"); + textures = PyImport_ImportModule("textures"); - // TODO can these be global static? these don't change during program execution - PyObject *blockmap = PyObject_GetAttrString(textures, "blockmap"); - PyObject *special_blocks = PyObject_GetAttrString(textures, "special_blocks"); - PyObject *specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); + /* TODO can these be global static? these don't change during program execution */ + blockmap = PyObject_GetAttrString(textures, "blockmap"); + special_blocks = PyObject_GetAttrString(textures, "special_blocks"); + specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); Py_DECREF(textures); - //printf("render_loop\n"); - - int imgx, imgy; - int x, y, z; for (x = 15; x > -1; x--) { for (y = 0; y < 16; y++) { imgx = xoff + x*12 + y*12; /* 128*12 -- offset for z direction, 15*6 -- offset for x */ imgy = yoff - x*6 + y*6 + 128*12 + 15*6; for (z = 0; z < 128; z++) { + unsigned char block; + PyObject *blockid; + imgy -= 12; if ((imgx >= imgsize0 + 24) || (imgx <= -24)) { @@ -105,13 +117,16 @@ chunk_render(PyObject *self, PyObject *args) { continue; } - // get blockid - unsigned char block = getBlock(blocks, x, z, y); // Note the order: x,z,y + /* get blockid + note the order: x, z, y */ + block = getBlock(blocks, x, z, y); if (block == 0) { continue; } - //printf("checking blockid %hhu\n", block); - PyObject *blockid = PyInt_FromLong(block); // TODO figure out how to DECREF this easily, instead at every 'continue'. + + /* TODO figure out how to DECREF this easily, instead of at + every continue */ + blockid = PyInt_FromLong(block); if ( (x != 0) && (y != 15) && (z != 127) && @@ -123,32 +138,37 @@ chunk_render(PyObject *self, PyObject *args) { if (!PySequence_Contains(special_blocks, blockid)) { - //t = textures.blockmap[blockid] + /* t = textures.blockmap[blockid] */ PyObject *t = PyList_GetItem(blockmap, block); - // PyList_GetItem returns borrowed ref + /* PyList_GetItem returns borrowed ref */ if (t == Py_None) { printf("t == Py_None. blockid=%d\n", block); continue; } - // note that this version of alpha_over has a different signature than the - // version in _composite.c + /* note that this version of alpha_over has a different signature than the + version in _composite.c */ texture_alpha_over(img, t, imgx, imgy ); - } else { - // this should be a pointer to a unsigned char + PyObject *tmp, *t; + + /* this should be a pointer to a unsigned char */ void* ancilData_p = PyArray_GETPTR3(blockdata_expanded, x, y, z); unsigned char ancilData = *((unsigned char*)ancilData_p); - if (block == 85) { // fence. skip the generate_pseudo_ancildata for now + if (block == 85) { + /* fence. skip the generate_pseudo_ancildata for now */ continue; } - PyObject *tmp = PyTuple_New(2); + + tmp = PyTuple_New(2); - Py_INCREF(blockid); // because SetItem steals + Py_INCREF(blockid); /* because SetItem steals */ PyTuple_SetItem(tmp, 0, blockid); PyTuple_SetItem(tmp, 1, PyInt_FromLong(ancilData)); - PyObject *t = PyDict_GetItem(specialblockmap, tmp); // this is a borrowed reference. no need to decref + + /* this is a borrowed reference. no need to decref */ + t = PyDict_GetItem(specialblockmap, tmp); Py_DECREF(tmp); if (t != NULL) texture_alpha_over(img, t, imgx, imgy ); diff --git a/src/main.c b/src/main.c index 7f56918..f56fb41 100644 --- a/src/main.c +++ b/src/main.c @@ -31,5 +31,6 @@ PyMODINIT_FUNC initc_overviewer(void) { (void)Py_InitModule("c_overviewer", COverviewerMethods); - import_array(); // for numpy + /* for numpy */ + import_array(); } From 08597ab1c04291e613f3460557c31b7bac25837c Mon Sep 17 00:00:00 2001 From: Xon Date: Sat, 19 Mar 2011 02:48:57 +0800 Subject: [PATCH 024/213] Fix performance regression caused by the lack of region/chunk cache at the world level --- nbt.py | 38 +++++++++++++++++++++++++++++--------- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/nbt.py b/nbt.py index b1bdf4e..82f1aae 100644 --- a/nbt.py +++ b/nbt.py @@ -204,7 +204,7 @@ class MCRFileReader(object): and y must be between 0 and 31, or None. If they are None, then there will be no file seek before doing the read.""" - if x != None and y != None: + if x is not None and y is not None: if (not x >= 0) or (not x < 32) or (not y >= 0) or (not y < 32): raise ValueError("Chunk location out of range.") @@ -234,7 +234,7 @@ class MCRFileReader(object): None. If they are, None, then there will be no file seek before doing the read.""" - if x != None and y != None: + if x is not None and y is not None: if (not x >= 0) or (not x < 32) or (not y >= 0) or (not y < 32): raise ValueError("Chunk location out of range.") @@ -286,9 +286,25 @@ class MCRFileReader(object): chunk doesn't exist, this number may be nonsense. Like load_chunk(), this will wrap x and y into the range [0, 31]. """ - - return self._read_chunk_timestamp(x % 32, y % 32) + x = x % 32 + y = y % 32 + if self._timestamps is None: + #self.get_chunks() + return self._read_chunk_timestamp(x, y) + else: + return self._timestamps[x + y * 32] + def chunkExists(self, x, y): + """Determines if a chunk exists without triggering loading of the backend data""" + x = x % 32 + y = y % 32 + if self._locations is None: + #self.get_chunks() + location = self._read_chunk_location(x, y) + else: + location = self._locations[x + y * 32] + return location is not None + def load_chunk(self, x, y): """Return a NBTFileReader instance for the given chunk, or None if the given chunk doesn't exist in this region file. If @@ -296,9 +312,14 @@ class MCRFileReader(object): modulo'd into this range (x % 32, etc.) This is so you can provide chunk coordinates in global coordinates, and still have the chunks load out of regions properly.""" - - location = self._read_chunk_location(x % 32, y % 32) - if not location: + x = x % 32 + y = y % 32 + if self._locations is None: + #self.get_chunks() + location = self._read_chunk_location(x % 32, y % 32) + else: + location = self._locations[x + y * 32] + if location is None: return None # seek to the data @@ -320,8 +341,7 @@ class MCRFileReader(object): is_gzip = False else: # unsupported! - raise Exception("Unsupported chunk compression type: %i" % (compression,)) - + raise Exception("Unsupported chunk compression type: %i" % (compression)) # turn the rest of the data into a StringIO object # (using data_length - 1, as we already read 1 byte for compression) data = self._file.read(data_length - 1) From 383e8197af80e6f88df012346ced0f5b9de01836 Mon Sep 17 00:00:00 2001 From: Xon Date: Sat, 19 Mar 2011 02:50:44 +0800 Subject: [PATCH 025/213] Performance improvements on update scan --- chunk.py | 10 ++-- composite.py | 4 +- quadtree.py | 127 ++++++++++++++++++++++----------------------------- setup.py | 2 +- textures.py | 19 ++++---- world.py | 41 +++++++++++------ 6 files changed, 99 insertions(+), 104 deletions(-) diff --git a/chunk.py b/chunk.py index 8454c2b..ad4f45c 100644 --- a/chunk.py +++ b/chunk.py @@ -371,16 +371,16 @@ class ChunkRenderer(object): # and finally check for a block with same blockid. I we aren't in the border of a chunk, # check for the block having the sme blockid. - if (up_right_blocks != None and up_right_blocks[0,y,z] == blockid) if x == 15 else blocks[x+1,y,z] == blockid: + if (up_right_blocks is not None and up_right_blocks[0,y,z] == blockid) if x == 15 else blocks[x+1,y,z] == blockid: pseudo_data = pseudo_data | 0b1000 - if (right_blocks != None and right_blocks[x,0,z] == blockid) if y == 15 else blocks[x,y + 1,z] == blockid: + if (right_blocks is not None and right_blocks[x,0,z] == blockid) if y == 15 else blocks[x,y + 1,z] == blockid: pseudo_data = pseudo_data | 0b0100 - if (left_blocks != None and left_blocks[15,y,z] == blockid) if x == 0 else blocks[x - 1,y,z] == blockid: + if (left_blocks is not None and left_blocks[15,y,z] == blockid) if x == 0 else blocks[x - 1,y,z] == blockid: pseudo_data = pseudo_data | 0b0010 - if (up_left_blocks != None and up_left_blocks[x,15,z] == blockid) if y == 0 else blocks[x,y - 1,z] == blockid: + if (up_left_blocks is not None and up_left_blocks[x,15,z] == blockid) if y == 0 else blocks[x,y - 1,z] == blockid: pseudo_data = pseudo_data | 0b0001 # rotate the bits for other north orientations @@ -443,7 +443,7 @@ class ChunkRenderer(object): # make sure we have a correctly-ranged coordinates and enough # info about the chunk - if not (blocks != None and skylight != None and blocklight != None and + if not (blocks is not None and skylight is not None and blocklight is not None and local_x >= 0 and local_x < 16 and local_y >= 0 and local_y < 16 and local_z >= 0 and local_z < 128): # we have no useful info, return default diff --git a/composite.py b/composite.py index f3edf6b..6cbe44e 100644 --- a/composite.py +++ b/composite.py @@ -36,11 +36,11 @@ def alpha_over(dest, src, pos_or_rect=(0, 0), mask=None): either be a position or a rectangle, specifying where on dest to put src. Falls back to dest.paste() if the alpha_over extension can't be found.""" - if mask == None: + if mask is None: mask = src global extension_alpha_over - if extension_alpha_over != None: + if extension_alpha_over is not None: # extension ALWAYS expects rects, so convert if needed if len(pos_or_rect) == 2: pos_or_rect = (pos_or_rect[0], pos_or_rect[1], src.size[0], src.size[1]) diff --git a/quadtree.py b/quadtree.py index 1edbb9e..1d43a8d 100644 --- a/quadtree.py +++ b/quadtree.py @@ -26,6 +26,7 @@ import logging import util import cPickle import stat +import errno from time import gmtime, strftime, sleep from PIL import Image @@ -438,7 +439,7 @@ class QuadtreeGen(object): # return (col, row, chunkx, chunky, regionpath) chunkx, chunky = self.world.unconvert_coords(col, row) c = self.world.get_region_path(chunkx, chunky) - if os.path.exists(c): + if c is not None: chunklist.append((col, row, chunkx, chunky, c)) return chunklist @@ -451,53 +452,38 @@ def render_innertile(dest, name, imgformat, optimizeimg): imgpath = os.path.join(dest, name) + "." + imgformat if name == "base": - q0path = os.path.join(dest, "0." + imgformat) - q1path = os.path.join(dest, "1." + imgformat) - q2path = os.path.join(dest, "2." + imgformat) - q3path = os.path.join(dest, "3." + imgformat) + quadPath = [[(0,0),os.path.join(dest, "0." + imgformat)],[(192,0),os.path.join(dest, "1." + imgformat)], [(0, 192),os.path.join(dest, "2." + imgformat)],[(192,192),os.path.join(dest, "3." + imgformat)]] else: - q0path = os.path.join(dest, name, "0." + imgformat) - q1path = os.path.join(dest, name, "1." + imgformat) - q2path = os.path.join(dest, name, "2." + imgformat) - q3path = os.path.join(dest, name, "3." + imgformat) - - # Check which ones exist - if not os.path.exists(q0path): - q0path = None - if not os.path.exists(q1path): - q1path = None - if not os.path.exists(q2path): - q2path = None - if not os.path.exists(q3path): - q3path = None - + quadPath = [[(0,0),os.path.join(dest, name, "0." + imgformat)],[(192,0),os.path.join(dest, name, "1." + imgformat)],[(0, 192),os.path.join(dest, name, "2." + imgformat)],[(192,192),os.path.join(dest, name, "3." + imgformat)]] + + #stat the tile, we need to know if it exists or it's mtime + try: + tile_mtime = os.stat(imgpath)[stat.ST_MTIME]; + except OSError, e: + if e.errno != errno.ENOENT: + raise + tile_mtime = None + + #check mtimes on each part of the quad, this also checks if they exist + needs_rerender = tile_mtime is None + quadPath_filtered = [] + for path in quadPath: + try: + quad_mtime = os.stat(path[1])[stat.ST_MTIME]; + quadPath_filtered.append(path) + if quad_mtime > tile_mtime: + needs_rerender = True + except OSError: + # We need to stat all the quad files, so keep looping + pass # do they all not exist? - if not (q0path or q1path or q2path or q3path): - if os.path.exists(imgpath): + if quadPath_filtered == []: + if tile_mtime is not None: os.unlink(imgpath) return - - # check the mtimes - try: - tile_mtime = os.path.getmtime(imgpath) - needs_rerender = False - - # remove non-existant paths - components = [q0path, q1path, q2path, q3path] - components = filter(lambda p: p != None, components) - - for mtime in [os.path.getmtime(path) for path in components]: - if mtime > tile_mtime: - needs_rerender = True - break - - # quit now if we don't need rerender - if not needs_rerender: - return - except OSError: - # one of our mtime calls failed, so we'll continue - pass - + # quit now if we don't need rerender + if not needs_rerender: + return #logging.debug("writing out innertile {0}".format(imgpath)) # Create the actual image now @@ -506,30 +492,12 @@ def render_innertile(dest, name, imgformat, optimizeimg): # we'll use paste (NOT alpha_over) for quadtree generation because # this is just straight image stitching, not alpha blending - if q0path: + for path in quadPath_filtered: try: - quad0 = Image.open(q0path).resize((192,192), Image.ANTIALIAS) - img.paste(quad0, (0,0)) + quad = Image.open(path[1]).resize((192,192), Image.ANTIALIAS) + img.paste(quad, path[0]) except Exception, e: - logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", q0path, e) - if q1path: - try: - quad1 = Image.open(q1path).resize((192,192), Image.ANTIALIAS) - img.paste(quad1, (192,0)) - except Exception, e: - logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", q1path, e) - if q2path: - try: - quad2 = Image.open(q2path).resize((192,192), Image.ANTIALIAS) - img.paste(quad2, (0, 192)) - except Exception, e: - logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", q2path, e) - if q3path: - try: - quad3 = Image.open(q3path).resize((192,192), Image.ANTIALIAS) - img.paste(quad3, (192, 192)) - except Exception, e: - logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", q3path, e) + logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", path[1], e) # Save it if imgformat == 'jpg': @@ -594,12 +562,20 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) _, _, chunkx, chunky, region = chunk with open(region, 'rb') as region: r = nbt.MCRFileReader(region) - return r.load_chunk(chunkx, chunky) != None + return r.chunkExists(chunkx, chunky) chunks = filter(chunk_exists, chunks) + #stat the file, we need to know if it exists or it's mtime + try: + tile_mtime = os.stat(imgpath)[stat.ST_MTIME]; + except OSError, e: + if e.errno != errno.ENOENT: + raise + tile_mtime = None + if not chunks: # No chunks were found in this tile - if os.path.exists(imgpath): + if tile_mtime is not None: os.unlink(imgpath) return None @@ -611,18 +587,23 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) except OSError, e: # Ignore errno EEXIST: file exists. Since this is multithreaded, # two processes could conceivably try and create the same directory - # at the same time. - import errno + # at the same time. if e.errno != errno.EEXIST: raise # check chunk mtimes to see if they are newer try: - tile_mtime = os.path.getmtime(imgpath) + #tile_mtime = os.path.getmtime(imgpath) + regionMtimes = {} needs_rerender = False for col, row, chunkx, chunky, regionfile in chunks: - # check region file mtime first - if os.path.getmtime(regionfile) <= tile_mtime: + # check region file mtime first. + # Note: we cache the value since it's actually very likely we will have multipule chunks in the same region, and syscalls are expensive + regionMtime = regionMtimes.get(regionfile,None) + if regionMtime is None: + regionMtime = os.path.getmtime(regionfile) + regionMtimes[regionfile] = regionMtime + if regionMtime <= tile_mtime: continue # checking chunk mtime diff --git a/setup.py b/setup.py index 059631d..cf12c18 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup_kwargs['cmdclass'] = {} # py2exe options # -if py2exe != None: +if py2exe is not None: setup_kwargs['console'] = ['gmap.py'] setup_kwargs['data_files'] = [('textures', ['textures/lava.png', 'textures/water.png', 'textures/fire.png']), ('', ['config.js', 'COPYING.txt', 'README.rst']), diff --git a/textures.py b/textures.py index f19c5c3..08d70f8 100644 --- a/textures.py +++ b/textures.py @@ -878,15 +878,16 @@ def getBiomeData(worlddir, chunkX, chunkY): if biomeFile == currentBiomeFile: return currentBiomeData - f = open(os.path.join(worlddir, "biomes", biomeFile), "rb") - rawdata = f.read() - f.close() - - # make sure the file size is correct - if not len(rawdata) == 512 * 512 * 2: - raise Exception("Biome file %s is not valid." % (biomeFile,)) - - data = numpy.frombuffer(rawdata, dtype=numpy.dtype(">u2")) + try: + with open(os.path.join(worlddir, "biomes", biomeFile), "rb") as f: + rawdata = f.read() + # make sure the file size is correct + if not len(rawdata) == 512 * 512 * 2: + raise Exception("Biome file %s is not valid." % (biomeFile,)) + data = numpy.frombuffer(rawdata, dtype=numpy.dtype(">u2")) + except IOError: + data = None + pass # no biome data currentBiomeFile = biomeFile currentBiomeData = data diff --git a/world.py b/world.py index 826a651..b50ddfa 100644 --- a/world.py +++ b/world.py @@ -67,10 +67,16 @@ class World(object): mincol = maxcol = minrow = maxrow = 0 - def __init__(self, worlddir, useBiomeData=False): + def __init__(self, worlddir, useBiomeData=False,regionlist=None): self.worlddir = worlddir self.useBiomeData = useBiomeData + #find region files, or load the region list + regionfiles = {} + for x, y, regionfile in self._iterate_regionfiles(): + regionfiles[(x,y)] = (x,y,regionfile) + self.regionfiles = regionfiles + # figure out chunk format is in use # if not mcregion, error out early data = nbt.load(os.path.join(self.worlddir, "level.dat"))[1]['Data'] @@ -102,11 +108,9 @@ class World(object): def get_region_path(self, chunkX, chunkY): """Returns the path to the region that contains chunk (chunkX, chunkY) """ + _, _, regionfile = self.regionfiles.get((chunkX//32, chunkY//32),(None,None,None)); + return regionfile - chunkFile = "region/r.%s.%s.mcr" % (chunkX//32, chunkY//32) - - return os.path.join(self.worlddir, chunkFile) - def convert_coords(self, chunkx, chunky): """Takes a coordinate (chunkx, chunky) where chunkx and chunky are in the chunk coordinate system, and figures out the row and column @@ -168,7 +172,7 @@ class World(object): # find the dimensions of the map, in region files minx = maxx = miny = maxy = 0 found_regions = False - for x, y, regionfile in self._iterate_regionfiles(): + for x, y in self.regionfiles: found_regions = True minx = min(minx, x) maxx = max(maxx, x) @@ -203,18 +207,27 @@ class World(object): self.findTrueSpawn() - def _iterate_regionfiles(self): + def _iterate_regionfiles(self,regionlist=None): """Returns an iterator of all of the region files, along with their coordinates Returns (regionx, regiony, filename)""" - - for dirpath, dirnames, filenames in os.walk(os.path.join(self.worlddir, 'region')): - if not dirnames and filenames and "DIM-1" not in dirpath: - for f in filenames: - if f.startswith("r.") and f.endswith(".mcr"): - p = f.split(".") - yield (int(p[1]), int(p[2]), os.path.join(dirpath, f)) + join = os.path.join + if regionlist is not None: + for path in regionlist: + if path.endswith("\n"): + path = path[:-1] + f = os.path.basename(path) + if f.startswith("r.") and f.endswith(".mcr"): + p = f.split(".") + yield (int(p[1]), int(p[2]), join(self.worlddir, 'region', f)) + else: + for dirpath, dirnames, filenames in os.walk(os.path.join(self.worlddir, 'region')): + if not dirnames and filenames and "DIM-1" not in dirpath: + for f in filenames: + if f.startswith("r.") and f.endswith(".mcr"): + p = f.split(".") + yield (int(p[1]), int(p[2]), join(dirpath, f)) def get_save_dir(): """Returns the path to the local saves directory From b92dcd96ee32f119de5a3b2223618041a692abe2 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Fri, 18 Mar 2011 16:22:48 -0400 Subject: [PATCH 026/213] increased max result queue size back to brownan/master normal --- quadtree.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quadtree.py b/quadtree.py index 1d43a8d..f33cacc 100644 --- a/quadtree.py +++ b/quadtree.py @@ -361,10 +361,10 @@ class QuadtreeGen(object): logging.info("The others will go faster") for result in self._apply_render_worldtiles(pool): results.append(result) - if len(results) > 10 + 2*procs: + if len(results) > 10000: # Empty the queue before adding any more, so that memory # required has an upper bound - while len(results) > 10: + while len(results) > 500: results.popleft().get() complete += 1 self.print_statusline(complete, total, 1) @@ -386,8 +386,8 @@ class QuadtreeGen(object): logging.info("Starting level {0}".format(level)) for result in self._apply_render_inntertile(pool, zoom): results.append(result) - if len(results) > 10 + 2*procs: - while len(results) > 10: + if len(results) > 10000: + while len(results) > 500: results.popleft().get() complete += 1 self.print_statusline(complete, total, level) From f9ae158457ce571015b5c6c7f0ccb89495e22f9a Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Fri, 18 Mar 2011 16:37:47 -0400 Subject: [PATCH 027/213] removed log message in iterate.c --- src/iterate.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index 824b25c..d5c24cc 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -142,9 +142,7 @@ chunk_render(PyObject *self, PyObject *args) { PyObject *t = PyList_GetItem(blockmap, block); /* PyList_GetItem returns borrowed ref */ if (t == Py_None) { - printf("t == Py_None. blockid=%d\n", block); continue; - } /* note that this version of alpha_over has a different signature than the From 28b750b694540fe74b59c2e306a341d7d96819fd Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sat, 19 Mar 2011 00:15:29 -0400 Subject: [PATCH 028/213] changed block lookup to use numpy macros this fixes (probably all of) the errors we saw before, but not because we're using the official numpy macro; it's because the old macro messed up on coordinates like (x, y+1, z) due to operator precedence. --- src/iterate.c | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index d5c24cc..7d38555 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -20,12 +20,11 @@ #include /* macro for getting blockID from a chunk of memory */ -#define getBlock(blockThing, x,y,z) blockThing[ y + ( z * 128 + ( x * 128 * 16) ) ] +#define getBlock(blockThing, x,y,z) (*(unsigned char *)(PyArray_GETPTR3(blockThing, (x), (y), (z)))) static inline int isTransparent(unsigned char b) { /* TODO expand this to include all transparent blocks */ - return b == 0; - + return b == 0 || b == 8 || b == 9 || b == 18; } /* helper to handle alpha_over calls involving a texture tuple */ @@ -56,7 +55,6 @@ chunk_render(PyObject *self, PyObject *args) { int imgsize0, imgsize1; PyObject *blocks_py; - char *blocks; PyObject *textures, *blockmap, *special_blocks, *specialblockmap; @@ -81,8 +79,6 @@ chunk_render(PyObject *self, PyObject *args) { /* get the block data directly from numpy: */ blocks_py = PyObject_GetAttrString(chunk, "blocks"); - blocks = PyArray_BYTES(blocks_py); - Py_DECREF(blocks_py); /* PyObject *left_blocks = PyObject_GetAttrString(chunk, "left_blocks"); @@ -119,7 +115,7 @@ chunk_render(PyObject *self, PyObject *args) { /* get blockid note the order: x, z, y */ - block = getBlock(blocks, x, z, y); + block = getBlock(blocks_py, x, y, z); if (block == 0) { continue; } @@ -130,9 +126,9 @@ chunk_render(PyObject *self, PyObject *args) { if ( (x != 0) && (y != 15) && (z != 127) && - !isTransparent(getBlock(blocks, x-1, z, y)) && - !isTransparent(getBlock(blocks, x, z+1, y)) && - !isTransparent(getBlock(blocks, x, z, y+1)) ) { + !isTransparent(getBlock(blocks_py, x-1, y, z)) && + !isTransparent(getBlock(blocks_py, x, y, z+1)) && + !isTransparent(getBlock(blocks_py, x, y+1, z))) { continue; } @@ -176,6 +172,7 @@ chunk_render(PyObject *self, PyObject *args) { } } + Py_DECREF(blocks_py); Py_DECREF(blockmap); Py_DECREF(special_blocks); Py_DECREF(specialblockmap); From 1ac922983f1068ed5c8e613e266defb670eb28c7 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 19 Mar 2011 00:38:30 -0400 Subject: [PATCH 029/213] Use the list of transparent_blocks from chunk.py. This fix is part 2 (of 2) of the graphical rendering glitch fix. --- src/iterate.c | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index 7d38555..7436603 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -22,9 +22,12 @@ /* macro for getting blockID from a chunk of memory */ #define getBlock(blockThing, x,y,z) (*(unsigned char *)(PyArray_GETPTR3(blockThing, (x), (y), (z)))) -static inline int isTransparent(unsigned char b) { - /* TODO expand this to include all transparent blocks */ - return b == 0 || b == 8 || b == 9 || b == 18; +static inline int isTransparent(PyObject* tup, unsigned char b) { + PyObject *block = PyInt_FromLong(b); + int ret = PySequence_Contains(tup, block); + Py_DECREF(block); + return ret; + } /* helper to handle alpha_over calls involving a texture tuple */ @@ -56,7 +59,7 @@ chunk_render(PyObject *self, PyObject *args) { PyObject *blocks_py; - PyObject *textures, *blockmap, *special_blocks, *specialblockmap; + PyObject *textures, *blockmap, *special_blocks, *specialblockmap, *chunk_mod; int imgx, imgy; int x, y, z; @@ -83,17 +86,25 @@ chunk_render(PyObject *self, PyObject *args) { /* PyObject *left_blocks = PyObject_GetAttrString(chunk, "left_blocks"); PyObject *right_blocks = PyObject_GetAttrString(chunk, "right_blocks"); - PyObject *transparent_blocks = PyObject_GetAttrString(chunk, "transparent_blocks"); */ textures = PyImport_ImportModule("textures"); + chunk_mod = PyImport_ImportModule("chunk"); /* TODO can these be global static? these don't change during program execution */ blockmap = PyObject_GetAttrString(textures, "blockmap"); special_blocks = PyObject_GetAttrString(textures, "special_blocks"); specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); + PyObject *transparent_blocks = PyObject_GetAttrString(chunk_mod, "transparent_blocks"); + if (transparent_blocks == NULL) { + PyErr_SetString(PyExc_ValueError, + "transparent_blocks is NULL"); + return NULL; + } + Py_DECREF(textures); + Py_DECREF(chunk_mod); for (x = 15; x > -1; x--) { for (y = 0; y < 16; y++) { @@ -126,9 +137,9 @@ chunk_render(PyObject *self, PyObject *args) { if ( (x != 0) && (y != 15) && (z != 127) && - !isTransparent(getBlock(blocks_py, x-1, y, z)) && - !isTransparent(getBlock(blocks_py, x, y, z+1)) && - !isTransparent(getBlock(blocks_py, x, y+1, z))) { + !isTransparent(transparent_blocks, getBlock(blocks_py, x-1, y, z)) && + !isTransparent(transparent_blocks, getBlock(blocks_py, x, y, z+1)) && + !isTransparent(transparent_blocks, getBlock(blocks_py, x, y+1, z))) { continue; } From 1afb2b3d53b37512a0adb6e30b0bc6b18b4f9810 Mon Sep 17 00:00:00 2001 From: Xon Date: Sat, 19 Mar 2011 13:36:40 +0800 Subject: [PATCH 030/213] Addded intial region caching. Loads all the offsets & timestamps @ start to share to worker proceses. From 14609247 function calls (14608852 primitive calls) in 118.278 CPU seconds to 12232301 function calls (12231906 primitive calls) in 75.825 CPU seconds --- chunk.py | 18 ++++++++--------- nbt.py | 56 +++++++++++++++++++++++++++++------------------------ quadtree.py | 14 ++++++-------- world.py | 24 ++++++++++++++++++++++- 4 files changed, 69 insertions(+), 43 deletions(-) diff --git a/chunk.py b/chunk.py index ad4f45c..ab5d41f 100644 --- a/chunk.py +++ b/chunk.py @@ -46,12 +46,12 @@ image # alpha_over extension, BUT this extension may fall back to PIL's # paste(), which DOES need the workaround.) -def get_lvldata(filename, x, y): +def get_lvldata(world,filename, x, y): """Takes a filename and chunkcoords and returns the Level struct, which contains all the level info""" try: - d = nbt.load_from_region(filename, x, y) + d = world.load_from_region(filename, x, y) except Exception, e: logging.warning("Error opening chunk (%i, %i) in %s. It may be corrupt. %s", x, y, filename, e) raise ChunkCorrupt(str(e)) @@ -64,10 +64,10 @@ def get_blockarray(level): Block array, which just contains all the block ids""" return numpy.frombuffer(level['Blocks'], dtype=numpy.uint8).reshape((16,16,128)) -def get_blockarray_fromfile(filename): +def get_blockarray_fromfile(world,filename): """Same as get_blockarray except takes a filename and uses get_lvldata to open it. This is a shortcut""" - level = get_lvldata(filename) + level = get_lvldata(world,filename) return get_blockarray(level) def get_skylight_array(level): @@ -196,7 +196,7 @@ class ChunkRenderer(object): """Loads and returns the level structure""" if not hasattr(self, "_level"): try: - self._level = get_lvldata(self.regionfile, self.chunkX, self.chunkY) + self._level = get_lvldata(self.world,self.regionfile, self.chunkX, self.chunkY) except NoSuchChunk, e: logging.debug("Skipping non-existant chunk") raise @@ -228,7 +228,7 @@ class ChunkRenderer(object): """Loads and sets data from lower-left chunk""" chunk_path = self.world.get_region_path(self.chunkX - 1, self.chunkY) try: - chunk_data = get_lvldata(chunk_path, self.chunkX - 1, self.chunkY) + chunk_data = get_lvldata(self.world,chunk_path, self.chunkX - 1, self.chunkY) self._left_skylight = get_skylight_array(chunk_data) self._left_blocklight = get_blocklight_array(chunk_data) self._left_blocks = get_blockarray(chunk_data) @@ -262,7 +262,7 @@ class ChunkRenderer(object): """Loads and sets data from lower-right chunk""" chunk_path = self.world.get_region_path(self.chunkX, self.chunkY + 1) try: - chunk_data = get_lvldata(chunk_path, self.chunkX, self.chunkY + 1) + chunk_data = get_lvldata(self.world,chunk_path, self.chunkX, self.chunkY + 1) self._right_skylight = get_skylight_array(chunk_data) self._right_blocklight = get_blocklight_array(chunk_data) self._right_blocks = get_blockarray(chunk_data) @@ -296,7 +296,7 @@ class ChunkRenderer(object): """Loads and sets data from upper-right chunk""" chunk_path = self.world.get_region_path(self.chunkX + 1, self.chunkY) try: - chunk_data = get_lvldata(chunk_path, self.chunkX + 1, self.chunkY) + chunk_data = get_lvldata(self.world,chunk_path, self.chunkX + 1, self.chunkY) self._up_right_skylight = get_skylight_array(chunk_data) self._up_right_blocklight = get_blocklight_array(chunk_data) self._up_right_blocks = get_blockarray(chunk_data) @@ -316,7 +316,7 @@ class ChunkRenderer(object): """Loads and sets data from upper-left chunk""" chunk_path = self.world.get_region_path(self.chunkX, self.chunkY - 1) try: - chunk_data = get_lvldata(chunk_path, self.chunkX, self.chunkY - 1) + chunk_data = get_lvldata(self.world,chunk_path, self.chunkX, self.chunkY - 1) self._up_left_skylight = get_skylight_array(chunk_data) self._up_left_blocklight = get_blocklight_array(chunk_data) self._up_left_blocks = get_blockarray(chunk_data) diff --git a/nbt.py b/nbt.py index 82f1aae..52d7545 100644 --- a/nbt.py +++ b/nbt.py @@ -34,14 +34,16 @@ def _file_loader(func): def load(fileobj): return NBTFileReader(fileobj).read_all() -@_file_loader -def load_from_region(fileobj, x, y): - nbt = MCRFileReader(fileobj).load_chunk(x, y) - if not nbt: +def load_from_region(filename, x, y): + nbt = load_region(filename).load_chunk(x, y) + if nbt is None: return None ## return none. I think this is who we should indicate missing chunks - #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) - return nbt.read_all() - + #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) + return nbt.read_all() + +def load_region(filename): + return MCRFileReader(filename) + class NBTFileReader(object): def __init__(self, fileobj, is_gzip=True): if is_gzip: @@ -178,9 +180,9 @@ class MCRFileReader(object): chunks (as instances of NBTFileReader), getting chunk timestamps, and for listing chunks contained in the file.""" - def __init__(self, fileobj): - self._file = fileobj - + def __init__(self, filename): + self._file = None + self._filename = filename # cache used when the entire header tables are read in get_chunks() self._locations = None self._timestamps = None @@ -250,7 +252,7 @@ class MCRFileReader(object): return timestamp - def get_chunks(self): + def get_chunk_info(self,closeFile = True): """Return a list of all chunks contained in this region file, as a list of (x, y) coordinate tuples. To load these chunks, provide these coordinates to load_chunk().""" @@ -258,6 +260,9 @@ class MCRFileReader(object): if self._chunks: return self._chunks + if self._file is None: + self._file = open(self._filename,'rb'); + self._chunks = [] self._locations = [] self._timestamps = [] @@ -278,7 +283,11 @@ class MCRFileReader(object): for x in xrange(32): timestamp = self._read_chunk_timestamp() self._timestamps.append(timestamp) - + + if closeFile: + #free the file object since it isn't safe to be reused in child processes (seek point goes wonky!) + self._file.close() + self._file = None return self._chunks def get_chunk_timestamp(self, x, y): @@ -289,20 +298,16 @@ class MCRFileReader(object): x = x % 32 y = y % 32 if self._timestamps is None: - #self.get_chunks() - return self._read_chunk_timestamp(x, y) - else: - return self._timestamps[x + y * 32] + self.get_chunk_info() + return self._timestamps[x + y * 32] def chunkExists(self, x, y): """Determines if a chunk exists without triggering loading of the backend data""" x = x % 32 y = y % 32 if self._locations is None: - #self.get_chunks() - location = self._read_chunk_location(x, y) - else: - location = self._locations[x + y * 32] + self.get_chunk_info() + location = self._locations[x + y * 32] return location is not None def load_chunk(self, x, y): @@ -315,13 +320,14 @@ class MCRFileReader(object): x = x % 32 y = y % 32 if self._locations is None: - #self.get_chunks() - location = self._read_chunk_location(x % 32, y % 32) - else: - location = self._locations[x + y * 32] + self.get_chunk_info() + + location = self._locations[x + y * 32] if location is None: return None - + + if self._file is None: + self._file = open(self._filename,'rb'); # seek to the data self._file.seek(location[0]) diff --git a/quadtree.py b/quadtree.py index f33cacc..692a9a5 100644 --- a/quadtree.py +++ b/quadtree.py @@ -556,13 +556,13 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) imgpath = path + "." + quadtree.imgformat + world = quadtree.world # first, remove chunks from `chunks` that don't actually exist in # their region files def chunk_exists(chunk): _, _, chunkx, chunky, region = chunk - with open(region, 'rb') as region: - r = nbt.MCRFileReader(region) - return r.chunkExists(chunkx, chunky) + r = world.load_region(region) + return r.chunkExists(chunkx, chunky) chunks = filter(chunk_exists, chunks) #stat the file, we need to know if it exists or it's mtime @@ -607,11 +607,9 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) continue # checking chunk mtime - with open(regionfile, 'rb') as regionfile: - region = nbt.MCRFileReader(regionfile) - if region.get_chunk_timestamp(chunkx, chunky) > tile_mtime: - needs_rerender = True - if needs_rerender: + region = world.load_region(regionfile) + if region.get_chunk_timestamp(chunkx, chunky) > tile_mtime: + needs_rerender = True break # if after all that, we don't need a rerender, return diff --git a/world.py b/world.py index b50ddfa..4a0de3f 100644 --- a/world.py +++ b/world.py @@ -11,7 +11,7 @@ # Public License for more details. # # You should have received a copy of the GNU General Public License along -# with the Overviewer. If not, see . +# with the Overviewer. If not, see . import functools import os @@ -72,10 +72,16 @@ class World(object): self.useBiomeData = useBiomeData #find region files, or load the region list + #this also caches all the region file header info regionfiles = {} + regions = {} for x, y, regionfile in self._iterate_regionfiles(): + mcr = nbt.MCRFileReader(regionfile) + mcr.get_chunk_info() + regions[regionfile] = mcr regionfiles[(x,y)] = (x,y,regionfile) self.regionfiles = regionfiles + self.regions = regions # figure out chunk format is in use # if not mcregion, error out early @@ -110,6 +116,22 @@ class World(object): """ _, _, regionfile = self.regionfiles.get((chunkX//32, chunkY//32),(None,None,None)); return regionfile + + + + def load_from_region(self,filename, x, y): + nbt = self.load_region(filename).load_chunk(x, y) + if nbt is None: + return None ## return none. I think this is who we should indicate missing chunks + #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) + return nbt.read_all() + + + #filo region cache + def load_region(self,filename): + #return nbt.MCRFileReader(filename) + return self.regions[filename] + def convert_coords(self, chunkx, chunky): """Takes a coordinate (chunkx, chunky) where chunkx and chunky are From 6a52e5f796403e83fcc788d98989405e022d90f1 Mon Sep 17 00:00:00 2001 From: Xon Date: Sat, 19 Mar 2011 13:39:59 +0800 Subject: [PATCH 031/213] Fix typo in file header --- world.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/world.py b/world.py index 4a0de3f..7ed790c 100644 --- a/world.py +++ b/world.py @@ -11,7 +11,7 @@ # Public License for more details. # # You should have received a copy of the GNU General Public License along -# with the Overviewer. If not, see . +# with the Overviewer. If not, see . import functools import os From 79d021279f75f2b1dbf48b7c11bcd57bf50e5173 Mon Sep 17 00:00:00 2001 From: Xon Date: Sat, 19 Mar 2011 13:59:40 +0800 Subject: [PATCH 032/213] Tweaked _get_chunks_in_range & related call tree. ~12% improvement by inlining self.world.get_region_path ina pure update scan. --- quadtree.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/quadtree.py b/quadtree.py index 692a9a5..fbee945 100644 --- a/quadtree.py +++ b/quadtree.py @@ -428,6 +428,9 @@ class QuadtreeGen(object): """Get chunks that are relevant to the tile rendering function that's rendering that range""" chunklist = [] + unconvert_coords = self.world.unconvert_coords + #get_region_path = self.world.get_region_path + get_region = self.world.regionfiles.get for row in xrange(rowstart-16, rowend+1): for col in xrange(colstart, colend+1): # due to how chunks are arranged, we can only allow @@ -437,8 +440,9 @@ class QuadtreeGen(object): continue # return (col, row, chunkx, chunky, regionpath) - chunkx, chunky = self.world.unconvert_coords(col, row) - c = self.world.get_region_path(chunkx, chunky) + chunkx, chunky = unconvert_coords(col, row) + #c = get_region_path(chunkx, chunky) + _, _, c = get_region((chunkx//32, chunky//32),(None,None,None)); if c is not None: chunklist.append((col, row, chunkx, chunky, c)) return chunklist From 2be64f2aa7a041becea95c3d2014c8ce8b527025 Mon Sep 17 00:00:00 2001 From: Xon Date: Sat, 19 Mar 2011 16:01:33 +0800 Subject: [PATCH 033/213] render_inntertile & render_inntertile now called in batches in the worker process, speeds up update scan with a lot of tiles to skip. --- quadtree.py | 84 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/quadtree.py b/quadtree.py index fbee945..67eb7d3 100644 --- a/quadtree.py +++ b/quadtree.py @@ -292,10 +292,13 @@ class QuadtreeGen(object): shutil.rmtree(getpath("3")) os.rename(getpath("new3"), getpath("3")) - def _apply_render_worldtiles(self, pool): + def _apply_render_worldtiles(self, pool,batch_size): """Returns an iterator over result objects. Each time a new result is requested, a new task is added to the pool and a result returned. """ + + batch = [] + tiles = 0 for path in iterate_base4(self.p): # Get the range for this tile colstart, rowstart = self._get_range_by_path(path) @@ -310,24 +313,42 @@ class QuadtreeGen(object): tilechunks = self._get_chunks_in_range(colstart, colend, rowstart, rowend) #logging.debug(" tilechunks: %r", tilechunks) - - # Put this in the pool + + # Put this in the batch to be submited to the pool # (even if tilechunks is empty, render_worldtile will delete - # existing images if appropriate) - yield pool.apply_async(func=render_worldtile, args= (self, - tilechunks, colstart, colend, rowstart, rowend, dest)) + # existing images if appropriate) + batch.append((tilechunks, colstart, colend, rowstart, rowend, dest)) + tiles += 1 + if tiles >= batch_size: + tiles = 0 + yield pool.apply_async(func=render_worldtile_batch, args= (self,batch)) + batch = [] - def _apply_render_inntertile(self, pool, zoom): + if tiles > 0: + yield pool.apply_async(func=render_worldtile_batch, args= (self,batch)) + + + def _apply_render_inntertile(self, pool, zoom,batch_size): """Same as _apply_render_worltiles but for the inntertile routine. Returns an iterator that yields result objects from tasks that have been applied to the pool. """ + batch = [] + tiles = 0 for path in iterate_base4(zoom): # This image is rendered at: dest = os.path.join(self.destdir, self.tiledir, *(str(x) for x in path[:-1])) name = str(path[-1]) - - yield pool.apply_async(func=render_innertile, args= (dest, name, self.imgformat, self.optimizeimg)) + + batch.append((dest, name, self.imgformat, self.optimizeimg)) + tiles += 1 + if tiles >= batch_size: + tiles = 0 + yield pool.apply_async(func=render_innertile_batch, args= (batch)) + batch = [] + + if tiles > 0: + yield pool.apply_async(func=render_innertile_batch, args= (batch)) def go(self, procs): """Renders all tiles""" @@ -359,20 +380,21 @@ class QuadtreeGen(object): logging.info("There are {0} total levels to render".format(self.p)) logging.info("Don't worry, each level has only 25% as many tiles as the last.") logging.info("The others will go faster") - for result in self._apply_render_worldtiles(pool): + count = 0 + batch_size = 50 + for result in self._apply_render_worldtiles(pool,batch_size): results.append(result) - if len(results) > 10000: + if len(results) > (10000/batch_size): # Empty the queue before adding any more, so that memory # required has an upper bound - while len(results) > 500: - results.popleft().get() - complete += 1 + while len(results) > (500/batch_size): + complete += results.popleft().get() self.print_statusline(complete, total, 1) # Wait for the rest of the results while len(results) > 0: - results.popleft().get() - complete += 1 + + complete += results.popleft().get() self.print_statusline(complete, total, 1) self.print_statusline(complete, total, 1, True) @@ -384,17 +406,15 @@ class QuadtreeGen(object): complete = 0 total = 4**zoom logging.info("Starting level {0}".format(level)) - for result in self._apply_render_inntertile(pool, zoom): + for result in self._apply_render_inntertile(pool, zoom,batch_size): results.append(result) - if len(results) > 10000: - while len(results) > 500: - results.popleft().get() - complete += 1 + if len(results) > (10000/batch_size): + while len(results) > (500/batch_size): + complete += results.popleft().get() self.print_statusline(complete, total, level) # Empty the queue while len(results) > 0: - results.popleft().get() - complete += 1 + complete += results.popleft().get() self.print_statusline(complete, total, level) self.print_statusline(complete, total, level, True) @@ -448,6 +468,14 @@ class QuadtreeGen(object): return chunklist @catch_keyboardinterrupt +def render_innertile_batch( batch): + count = 0 + #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) + for job in batch: + count += 1 + render_worldtile(job[0],job[1],job[2],job[3]) + return count + def render_innertile(dest, name, imgformat, optimizeimg): """ Renders a tile at os.path.join(dest, name)+".ext" by taking tiles from @@ -512,6 +540,14 @@ def render_innertile(dest, name, imgformat, optimizeimg): optimize_image(imgpath, imgformat, optimizeimg) @catch_keyboardinterrupt +def render_worldtile_batch(quadtree, batch): + count = 0 + #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) + for job in batch: + count += 1 + render_worldtile(quadtree,job[0],job[1],job[2],job[3],job[4],job[5]) + return count + def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path): """Renders just the specified chunks into a tile and save it. Unlike usual python conventions, rowend and colend are inclusive. Additionally, the @@ -529,7 +565,7 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) Standard tile size has colend-colstart=2 and rowend-rowstart=4 There is no return value - """ + """ # width of one chunk is 384. Each column is half a chunk wide. The total # width is (384 + 192*(numcols-1)) since the first column contributes full From e113a24ae0279637abfb1d92a60cd14b14a7e135 Mon Sep 17 00:00:00 2001 From: Xon Date: Sat, 19 Mar 2011 16:23:11 +0800 Subject: [PATCH 034/213] Fixed render_innertile_batch --- quadtree.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quadtree.py b/quadtree.py index 67eb7d3..d577325 100644 --- a/quadtree.py +++ b/quadtree.py @@ -344,11 +344,11 @@ class QuadtreeGen(object): tiles += 1 if tiles >= batch_size: tiles = 0 - yield pool.apply_async(func=render_innertile_batch, args= (batch)) + yield pool.apply_async(func=render_innertile_batch, args= (self,batch)) batch = [] if tiles > 0: - yield pool.apply_async(func=render_innertile_batch, args= (batch)) + yield pool.apply_async(func=render_innertile_batch, args= (self,batch)) def go(self, procs): """Renders all tiles""" @@ -468,12 +468,12 @@ class QuadtreeGen(object): return chunklist @catch_keyboardinterrupt -def render_innertile_batch( batch): +def render_innertile_batch(quadtree, batch): count = 0 #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) for job in batch: count += 1 - render_worldtile(job[0],job[1],job[2],job[3]) + render_innertile(job[0],job[1],job[2],job[3]) return count def render_innertile(dest, name, imgformat, optimizeimg): From b9433173c92498684d8397717318055457b3a9a3 Mon Sep 17 00:00:00 2001 From: Xon Date: Sat, 19 Mar 2011 23:36:26 +0800 Subject: [PATCH 035/213] Fixed worker processes being passed the full quadtree object. Caused massive performance regressions when caching stuff in quadtree.world Offloaded self._get_chunks_in_range into worker process. --- quadtree.py | 61 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/quadtree.py b/quadtree.py index d577325..452b481 100644 --- a/quadtree.py +++ b/quadtree.py @@ -82,6 +82,8 @@ def catch_keyboardinterrupt(func): raise return newfunc +child_quadtree = None + class QuadtreeGen(object): def __init__(self, worldobj, destdir, depth=None, tiledir="tiles", imgformat=None, optimizeimg=None, lighting=False, night=False, spawn=False): """Generates a quadtree from the world given into the @@ -308,24 +310,17 @@ class QuadtreeGen(object): # This image is rendered at: dest = os.path.join(self.destdir, self.tiledir, *(str(x) for x in path)) #logging.debug("this is rendered at %s", dest) - - # And uses these chunks - tilechunks = self._get_chunks_in_range(colstart, colend, rowstart, - rowend) - #logging.debug(" tilechunks: %r", tilechunks) - # Put this in the batch to be submited to the pool - # (even if tilechunks is empty, render_worldtile will delete - # existing images if appropriate) - batch.append((tilechunks, colstart, colend, rowstart, rowend, dest)) + # Put this in the batch to be submited to the pool + batch.append((colstart, colend, rowstart, rowend, dest)) tiles += 1 if tiles >= batch_size: tiles = 0 - yield pool.apply_async(func=render_worldtile_batch, args= (self,batch)) + yield pool.apply_async(func=render_worldtile_batch, args= [batch]) batch = [] if tiles > 0: - yield pool.apply_async(func=render_worldtile_batch, args= (self,batch)) + yield pool.apply_async(func=render_worldtile_batch, args= (batch,)) def _apply_render_inntertile(self, pool, zoom,batch_size): @@ -344,12 +339,15 @@ class QuadtreeGen(object): tiles += 1 if tiles >= batch_size: tiles = 0 - yield pool.apply_async(func=render_innertile_batch, args= (self,batch)) + yield pool.apply_async(func=render_innertile_batch, args= [batch]) batch = [] - if tiles > 0: - yield pool.apply_async(func=render_innertile_batch, args= (self,batch)) + if tiles > 0: + yield pool.apply_async(func=render_innertile_batch, args= [batch]) + def pool_initializer(args): + logging.debug("Child process {0}".format(os.getpid())) + def go(self, procs): """Renders all tiles""" @@ -365,12 +363,18 @@ class QuadtreeGen(object): for _ in xrange(curdepth - self.p): self._decrease_depth() + logging.debug("Parent process {0}".format(os.getpid())) + #stash the quadtree object so child process's can + global child_quadtree + child_quadtree = self # Create a pool if procs == 1: pool = FakePool() else: - pool = multiprocessing.Pool(processes=procs) - + pool = multiprocessing.Pool(processes=procs,initializer=self.pool_initializer,initargs=()) + #warm up the pool so it reports all the worker id's + pool.map(bool,xrange(multiprocessing.cpu_count()),1) + # Render the highest level of tiles from the chunks results = collections.deque() complete = 0 @@ -381,7 +385,7 @@ class QuadtreeGen(object): logging.info("Don't worry, each level has only 25% as many tiles as the last.") logging.info("The others will go faster") count = 0 - batch_size = 50 + batch_size = 10 for result in self._apply_render_worldtiles(pool,batch_size): results.append(result) if len(results) > (10000/batch_size): @@ -393,7 +397,6 @@ class QuadtreeGen(object): # Wait for the rest of the results while len(results) > 0: - complete += results.popleft().get() self.print_statusline(complete, total, 1) @@ -468,7 +471,7 @@ class QuadtreeGen(object): return chunklist @catch_keyboardinterrupt -def render_innertile_batch(quadtree, batch): +def render_innertile_batch(batch): count = 0 #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) for job in batch: @@ -540,12 +543,28 @@ def render_innertile(dest, name, imgformat, optimizeimg): optimize_image(imgpath, imgformat, optimizeimg) @catch_keyboardinterrupt -def render_worldtile_batch(quadtree, batch): +def render_worldtile_batch(batch): + global child_quadtree + return render_worldtile_batch_(child_quadtree, batch) + +def render_worldtile_batch_(quadtree, batch): count = 0 + _get_chunks_in_range = quadtree._get_chunks_in_range #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) for job in batch: count += 1 - render_worldtile(quadtree,job[0],job[1],job[2],job[3],job[4],job[5]) + colstart = job[0] + colend = job[1] + rowstart = job[2] + rowend = job[3] + path = job[4] + # (even if tilechunks is empty, render_worldtile will delete + # existing images if appropriate) + # And uses these chunks + tilechunks = _get_chunks_in_range(colstart, colend, rowstart,rowend) + #logging.debug(" tilechunks: %r", tilechunks) + + render_worldtile(quadtree,tilechunks,colstart, colend, rowstart, rowend, path) return count def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path): From 6f340dceee3fda5cb1a9a52b9b849196211fad57 Mon Sep 17 00:00:00 2001 From: Xon Date: Sun, 20 Mar 2011 01:19:23 +0800 Subject: [PATCH 036/213] New conrtib script to validate a region file --- contrib/validateRegionFile.py | 76 +++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 contrib/validateRegionFile.py diff --git a/contrib/validateRegionFile.py b/contrib/validateRegionFile.py new file mode 100644 index 0000000..5bd7e14 --- /dev/null +++ b/contrib/validateRegionFile.py @@ -0,0 +1,76 @@ +#!/usr/bin/python + +usage = "python contrib/%prog [OPTIONS] " + +description = """ +This script will delete files from the old chunk-based cache, a lot +like the old `gmap.py -d World/` command. You should only use this if +you're updating from an older version of Overviewer, and you want to +clean up your world folder. +""" + +from optparse import OptionParser +import sys +import re +import os.path +import logging + +# sys.path wrangling, so we can access Overviewer code +overviewer_dir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] +sys.path.insert(0, overviewer_dir) + +import nbt +import chunk + +def main(): + parser = OptionParser(usage=usage, description=description) +# parser.add_option("-d", "--dry-run", dest="dry", action="store_true", +# help="Don't actually delete anything. Best used with -v.") + + opt, args = parser.parse_args() + + if not len(args) == 1: + parser.print_help() + sys.exit(1) + + regionfile = args[0] + + if not os.path.exists(regionfile): + parser.print_help() + print "\nFile not found" + sys.exit(1) + chunk_pass = 0 + chunk_total = 0 + print( "Loading region: %s" % ( regionfile)) + try: + mcr = nbt.load_region(regionfile) + except IOError, e: + print("Error opening regionfile. It may be corrupt. %s"%( e)) + pass + if mcr is not None: + try: + chunks = mcr.get_chunk_info(False) + except IOError, e: + print("Error opening regionfile. It may be corrupt. %s"%( e)) + chunks = [] + pass + for x, y in chunks: + chunk_total += 1 + #try: + chunk_data = mcr.load_chunk(x, y) + if chunk_data is None: + print("Chunk %s:%s is unexpectedly empty"%(x, y)) + else: + try: + processed = chunk_data.read_all() + if processed == []: + print("Chunk %s:%s is an unexpectedly empty set"%(x, y)) + else: + chunk_pass += 1 + except Exception, e: + print("Error opening chunk (%i, %i) It may be corrupt. %s"%( x, y, e)) + else: + print("Error opening regionfile.") + print("Done; Passed %s/%s"%(chunk_pass,chunk_total)) +if __name__ == "__main__": + main() From cfabf161485d57879de91b963938b75504af850d Mon Sep 17 00:00:00 2001 From: Xon Date: Sun, 20 Mar 2011 01:22:34 +0800 Subject: [PATCH 037/213] Clarify if opening the region file failed or if the headers are wonky --- contrib/validateRegionFile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/validateRegionFile.py b/contrib/validateRegionFile.py index 5bd7e14..3846784 100644 --- a/contrib/validateRegionFile.py +++ b/contrib/validateRegionFile.py @@ -51,7 +51,7 @@ def main(): try: chunks = mcr.get_chunk_info(False) except IOError, e: - print("Error opening regionfile. It may be corrupt. %s"%( e)) + print("Error opening regionfile(bad header info). It may be corrupt. %s"%( e)) chunks = [] pass for x, y in chunks: From 76f85d0d2c68d805c1d2e4190f6913ab7a9bc0b1 Mon Sep 17 00:00:00 2001 From: Xon Date: Sun, 20 Mar 2011 01:57:47 +0800 Subject: [PATCH 038/213] Added ctrl-c handling, output is a single line (verbose reports what error occured), added optparsing, supports multipule files or a dir --- contrib/validateRegionFile.py | 109 +++++++++++++++++++--------------- 1 file changed, 62 insertions(+), 47 deletions(-) diff --git a/contrib/validateRegionFile.py b/contrib/validateRegionFile.py index 3846784..1e5320a 100644 --- a/contrib/validateRegionFile.py +++ b/contrib/validateRegionFile.py @@ -1,12 +1,9 @@ #!/usr/bin/python -usage = "python contrib/%prog [OPTIONS] " +usage = "python contrib/%prog [OPTIONS] ()*" description = """ -This script will delete files from the old chunk-based cache, a lot -like the old `gmap.py -d World/` command. You should only use this if -you're updating from an older version of Overviewer, and you want to -clean up your world folder. +This script will valide a minecraft region file for errors """ from optparse import OptionParser @@ -21,56 +18,74 @@ sys.path.insert(0, overviewer_dir) import nbt import chunk +import quadtree def main(): parser = OptionParser(usage=usage, description=description) -# parser.add_option("-d", "--dry-run", dest="dry", action="store_true", -# help="Don't actually delete anything. Best used with -v.") + parser.add_option("-r", "--regions", dest="regiondir", help="Use path to the regions instead of a list of files") + parser.add_option("-v", dest="verbose", action="store_true", help="Lists why a chunk in a region failed") opt, args = parser.parse_args() - if not len(args) == 1: + if opt.regiondir: + if os.path.exists(opt.regiondir): + for dirpath, dirnames, filenames in os.walk(opt.regiondir, 'region'): + if not dirnames and filenames and "DIM-1" not in dirpath: + for f in filenames: + if f.startswith("r.") and f.endswith(".mcr"): + p = f.split(".") + args.append(os.path.join(dirpath, f)) + + if len(args) < 1: + print "You must list at least one region file" parser.print_help() sys.exit(1) - - regionfile = args[0] - - if not os.path.exists(regionfile): - parser.print_help() - print "\nFile not found" - sys.exit(1) - chunk_pass = 0 - chunk_total = 0 - print( "Loading region: %s" % ( regionfile)) - try: - mcr = nbt.load_region(regionfile) - except IOError, e: - print("Error opening regionfile. It may be corrupt. %s"%( e)) - pass - if mcr is not None: + + for regionfile in args: + _,shortname = os.path.split(regionfile) + chunk_pass = 0 + chunk_total = 0 + if not os.path.exists(regionfile): + print("Region:%s Passed %s/%s"%(shortname,chunk_pass,chunk_total)) + continue try: - chunks = mcr.get_chunk_info(False) + mcr = nbt.load_region(regionfile) except IOError, e: - print("Error opening regionfile(bad header info). It may be corrupt. %s"%( e)) - chunks = [] - pass - for x, y in chunks: - chunk_total += 1 - #try: - chunk_data = mcr.load_chunk(x, y) - if chunk_data is None: - print("Chunk %s:%s is unexpectedly empty"%(x, y)) - else: - try: - processed = chunk_data.read_all() - if processed == []: - print("Chunk %s:%s is an unexpectedly empty set"%(x, y)) - else: - chunk_pass += 1 - except Exception, e: - print("Error opening chunk (%i, %i) It may be corrupt. %s"%( x, y, e)) - else: - print("Error opening regionfile.") - print("Done; Passed %s/%s"%(chunk_pass,chunk_total)) + if options.verbose: + print("Error opening regionfile. It may be corrupt. %s"%( e)) + if mcr is not None: + try: + chunks = mcr.get_chunk_info(False) + except IOError, e: + if options.verbose: + print("Error opening regionfile(bad header info). It may be corrupt. %s"%( e)) + chunks = [] + for x, y in chunks: + chunk_total += 1 + #try: + chunk_data = mcr.load_chunk(x, y) + if chunk_data is None: + if options.verbose: + print("Chunk %s:%s is unexpectedly empty"%(x, y)) + else: + try: + processed = chunk_data.read_all() + if processed == []: + if options.verbose: + print("Chunk %s:%s is an unexpectedly empty set"%(x, y)) + else: + chunk_pass += 1 + except Exception, e: + if options.verbose: + print("Error opening chunk (%i, %i) It may be corrupt. %s"%( x, y, e)) + else: + if options.verbose: + print("Error opening regionfile.") + + print("Region:%s Passed %s/%s"%(shortname,chunk_pass,chunk_total)) if __name__ == "__main__": - main() + try: + main() + except KeyboardInterrupt: + print "Caught Ctrl-C" + From fae67de9f0fb156b5a3d2408ab3431130aab9ccc Mon Sep 17 00:00:00 2001 From: Alex Headley Date: Sat, 19 Mar 2011 14:28:19 -0400 Subject: [PATCH 039/213] fixed some bugs in the validateRegion script --- contrib/validateRegionFile.py | 45 +++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/contrib/validateRegionFile.py b/contrib/validateRegionFile.py index 1e5320a..34ec1fe 100644 --- a/contrib/validateRegionFile.py +++ b/contrib/validateRegionFile.py @@ -12,15 +12,15 @@ import re import os.path import logging -# sys.path wrangling, so we can access Overviewer code -overviewer_dir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] -sys.path.insert(0, overviewer_dir) -import nbt -import chunk -import quadtree def main(): + # sys.path wrangling, so we can access Overviewer code + overviewer_dir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + sys.path.insert(0, overviewer_dir) + import nbt + #import chunk + #import quadtree parser = OptionParser(usage=usage, description=description) parser.add_option("-r", "--regions", dest="regiondir", help="Use path to the regions instead of a list of files") parser.add_option("-v", dest="verbose", action="store_true", help="Lists why a chunk in a region failed") @@ -46,41 +46,56 @@ def main(): chunk_pass = 0 chunk_total = 0 if not os.path.exists(regionfile): - print("Region:%s Passed %s/%s"%(shortname,chunk_pass,chunk_total)) + print("File not found: %s"%( regionfile)) + #print("Region:%s Passed %s/%s"%(shortname,chunk_pass,chunk_total)) continue try: mcr = nbt.load_region(regionfile) except IOError, e: - if options.verbose: + if opt.verbose: print("Error opening regionfile. It may be corrupt. %s"%( e)) + continue if mcr is not None: try: chunks = mcr.get_chunk_info(False) except IOError, e: - if options.verbose: + if opt.verbose: print("Error opening regionfile(bad header info). It may be corrupt. %s"%( e)) chunks = [] + continue + except Exception, e: + if opt.verbose: + print("Error opening regionfile (%s): %s"%( regionfile,e)) + continue for x, y in chunks: chunk_total += 1 - #try: - chunk_data = mcr.load_chunk(x, y) + try: + chunk_data = mcr.load_chunk(x, y) + except Exception, e: + if opt.verbose: + print("Error reading chunk (%i,%i): %s"%(x,y,e)) + continue if chunk_data is None: - if options.verbose: + if opt.verbose: print("Chunk %s:%s is unexpectedly empty"%(x, y)) + continue else: try: processed = chunk_data.read_all() if processed == []: - if options.verbose: + if opt.verbose: print("Chunk %s:%s is an unexpectedly empty set"%(x, y)) + continue else: chunk_pass += 1 except Exception, e: - if options.verbose: + if opt.verbose: print("Error opening chunk (%i, %i) It may be corrupt. %s"%( x, y, e)) + continue else: - if options.verbose: + if opt.verbose: print("Error opening regionfile.") + continue print("Region:%s Passed %s/%s"%(shortname,chunk_pass,chunk_total)) if __name__ == "__main__": From 26d781f24910a789fe604cb79f5245457b20eccc Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 19 Mar 2011 14:37:48 -0400 Subject: [PATCH 040/213] declaration tweak to make some compilers happy --- src/iterate.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index 7436603..031864c 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -59,7 +59,7 @@ chunk_render(PyObject *self, PyObject *args) { PyObject *blocks_py; - PyObject *textures, *blockmap, *special_blocks, *specialblockmap, *chunk_mod; + PyObject *textures, *blockmap, *special_blocks, *specialblockmap, *chunk_mod, *transparent_blocks; int imgx, imgy; int x, y, z; @@ -95,7 +95,7 @@ chunk_render(PyObject *self, PyObject *args) { blockmap = PyObject_GetAttrString(textures, "blockmap"); special_blocks = PyObject_GetAttrString(textures, "special_blocks"); specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); - PyObject *transparent_blocks = PyObject_GetAttrString(chunk_mod, "transparent_blocks"); + transparent_blocks = PyObject_GetAttrString(chunk_mod, "transparent_blocks"); if (transparent_blocks == NULL) { PyErr_SetString(PyExc_ValueError, "transparent_blocks is NULL"); From 5ae361824bf6ce00360b30dd9277a92a33005945 Mon Sep 17 00:00:00 2001 From: Alex Headley Date: Sat, 19 Mar 2011 17:45:20 -0400 Subject: [PATCH 041/213] cleaned up contrib/validateRegionFile script --- contrib/validateRegionFile.py | 201 +++++++++++++++++----------------- 1 file changed, 100 insertions(+), 101 deletions(-) diff --git a/contrib/validateRegionFile.py b/contrib/validateRegionFile.py index 34ec1fe..7dd2763 100644 --- a/contrib/validateRegionFile.py +++ b/contrib/validateRegionFile.py @@ -1,106 +1,105 @@ -#!/usr/bin/python +#!/usr/bin/env python -usage = "python contrib/%prog [OPTIONS] ()*" - -description = """ -This script will valide a minecraft region file for errors -""" - -from optparse import OptionParser -import sys -import re import os.path -import logging +import sys +overviewer_dir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] +sys.path.insert(0, overviewer_dir) +import nbt - - -def main(): - # sys.path wrangling, so we can access Overviewer code - overviewer_dir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] - sys.path.insert(0, overviewer_dir) - import nbt - #import chunk - #import quadtree - parser = OptionParser(usage=usage, description=description) - parser.add_option("-r", "--regions", dest="regiondir", help="Use path to the regions instead of a list of files") - parser.add_option("-v", dest="verbose", action="store_true", help="Lists why a chunk in a region failed") - - opt, args = parser.parse_args() - - if opt.regiondir: - if os.path.exists(opt.regiondir): - for dirpath, dirnames, filenames in os.walk(opt.regiondir, 'region'): - if not dirnames and filenames and "DIM-1" not in dirpath: - for f in filenames: - if f.startswith("r.") and f.endswith(".mcr"): - p = f.split(".") - args.append(os.path.join(dirpath, f)) - - if len(args) < 1: - print "You must list at least one region file" - parser.print_help() - sys.exit(1) - - for regionfile in args: - _,shortname = os.path.split(regionfile) - chunk_pass = 0 - chunk_total = 0 - if not os.path.exists(regionfile): - print("File not found: %s"%( regionfile)) - #print("Region:%s Passed %s/%s"%(shortname,chunk_pass,chunk_total)) - continue - try: - mcr = nbt.load_region(regionfile) - except IOError, e: - if opt.verbose: - print("Error opening regionfile. It may be corrupt. %s"%( e)) - continue - if mcr is not None: - try: - chunks = mcr.get_chunk_info(False) - except IOError, e: - if opt.verbose: - print("Error opening regionfile(bad header info). It may be corrupt. %s"%( e)) - chunks = [] - continue - except Exception, e: - if opt.verbose: - print("Error opening regionfile (%s): %s"%( regionfile,e)) - continue - for x, y in chunks: - chunk_total += 1 - try: - chunk_data = mcr.load_chunk(x, y) - except Exception, e: - if opt.verbose: - print("Error reading chunk (%i,%i): %s"%(x,y,e)) - continue - if chunk_data is None: - if opt.verbose: - print("Chunk %s:%s is unexpectedly empty"%(x, y)) - continue - else: - try: - processed = chunk_data.read_all() - if processed == []: - if opt.verbose: - print("Chunk %s:%s is an unexpectedly empty set"%(x, y)) - continue - else: - chunk_pass += 1 - except Exception, e: - if opt.verbose: - print("Error opening chunk (%i, %i) It may be corrupt. %s"%( x, y, e)) - continue - else: - if opt.verbose: - print("Error opening regionfile.") - continue - - print("Region:%s Passed %s/%s"%(shortname,chunk_pass,chunk_total)) -if __name__ == "__main__": +def check_region(region_filename): + chunk_errors = [] + if not os.path.exists(region_filename): + raise Exception('Region file not found: %s' % region_filename) try: - main() - except KeyboardInterrupt: - print "Caught Ctrl-C" + region = nbt.load_region(region_filename) + except IOError, e: + raise Exception('Error loading region (%s): %s' % (region_filename, e)) + try: + chunks = region.get_chunk_info(False) + except IOError, e: + raise Exception('Error reading region header (%s): %s' % (region_filename, e)) + except Exception, e: + raise Exception('Error reading region (%s): %s' % (region_filename, e)) + for x,y in chunks: + try: + check_chunk(region, x, y) + except Exception, e: + chunk_errors.append(e) + return (chunk_errors, len(chunks)) + +def check_chunk(region, x, y): + try: + data = region.load_chunk(x ,y) + except Exception, e: + raise Exception('Error reading chunk (%i, %i): %s' % (x, y, e)) + if data is None: + raise Exception('Chunk (%i, %i) is unexpectedly empty' % (x, y)) + else: + try: + processed_data = data.read_all() + except Exception, e: + raise Exception('Error reading chunk (%i, %i) data: %s' % (x, y, e)) + if processed_data == []: + raise Exception('Chunk (%i, %i) is an unexpectedly empty set' % (x, y)) + +if __name__ == '__main__': + try: + from optparse import OptionParser + + parser = OptionParser(usage='python contrib/%prog [OPTIONS] ', + description='This script will valide a minecraft region file for errors.') + parser.add_option('-v', dest='verbose', action='store_true', help='Print additional information.') + opts, args = parser.parse_args() + region_files = [] + for path in args: + if os.path.isdir(path): + for dirpath, dirnames, filenames in os.walk(path, True): + for filename in filenames: + if filename.startswith('r.') and filename.endswith('.mcr'): + if filename not in region_files: + region_files.append(os.path.join(dirpath, filename)) + elif opts.verbose: + print('Ignoring non-region file: %s' % os.path.join(dirpath, filename)) + elif os.path.isfile(path): + dirpath,filename = os.path.split(path) + if filename.startswith('r.') and filename.endswith('.mcr'): + if path not in region_files: + region_files.append(path) + else: + print('Ignoring non-region file: %s' % path) + else: + if opts.verbose: + print('Ignoring arg: %s' % path) + if len(region_files) < 1: + print 'You must list at least one region file.' + parser.print_help() + sys.exit(1) + else: + overall_chunk_total = 0 + bad_chunk_total = 0 + bad_region_total = 0 + for region_file in region_files: + try: + (chunk_errors, region_chunks) = check_region(region_file) + bad_chunk_total += len(chunk_errors) + overall_chunk_total += region_chunks + except Exception, e: + bad_region_total += 1 + print('FAILED(%s): %s' % (region_file, e)) + else: + if len(chunk_errors) is not 0: + print('WARNING(%s) Chunks: %i/%' % (region_file, region_chunks - len(chunk_errors), region_chunks)) + if opts.verbose: + for error in chunk_errors: + print(error) + elif opts.verbose: + print ('PASSED(%s) Chunks: %i/%i' % (region_file, region_chunks - len(chunk_errors), region_chunks)) + if opts.verbose: + print 'REGIONS: %i/%i' % (len(region_files) - bad_region_total, len(region_files)) + print 'CHUNKS: %i/%i' % (overall_chunk_total - bad_chunk_total, overall_chunk_total) + except KeyboardInterrupt: + sys.exit(1) + except Exception, e: + print('ERROR: %s' % e) + From 494caba5980949ce856e75fea22346489c3cd0fb Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sat, 19 Mar 2011 18:53:29 -0400 Subject: [PATCH 042/213] added a nice error if c_overviewer is missing, simplified composite.py now that c_overviewer is required, we don't need to have a PIL paste() fallback. The next step is to remove composite.py entirely! --- composite.py | 17 ++--------------- gmap.py | 12 ++++++++++-- src/composite.c | 1 + 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/composite.py b/composite.py index 6cbe44e..be5387f 100644 --- a/composite.py +++ b/composite.py @@ -23,12 +23,7 @@ Overviewer. It defaults to the PIL paste function when the custom alpha-over extension cannot be found. """ -extension_alpha_over = None -try: - from c_overviewer import alpha_over as _extension_alpha_over - extension_alpha_over = _extension_alpha_over -except ImportError: - pass +from c_overviewer import alpha_over as extension_alpha_over def alpha_over(dest, src, pos_or_rect=(0, 0), mask=None): """Composite src over dest, using mask as the alpha channel (if @@ -40,12 +35,4 @@ def alpha_over(dest, src, pos_or_rect=(0, 0), mask=None): mask = src global extension_alpha_over - if extension_alpha_over is not None: - # extension ALWAYS expects rects, so convert if needed - if len(pos_or_rect) == 2: - pos_or_rect = (pos_or_rect[0], pos_or_rect[1], src.size[0], src.size[1]) - extension_alpha_over(dest, src, pos_or_rect, mask) - else: - # fallback - dest.paste(src, pos_or_rect, mask) - + return extension_alpha_over(dest, src, pos_or_rect, mask) diff --git a/gmap.py b/gmap.py index 57edb0c..13a24c9 100755 --- a/gmap.py +++ b/gmap.py @@ -27,11 +27,19 @@ import re import multiprocessing import time import logging -import optimizeimages -import composite logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s") +# make sure the c_overviewer extension is available +try: + import c_overviewer +except ImportError: + print "You need to compile the c_overviewer module to run Minecraft Overviewer." + print "Run `python setup.py build`, or see the README for details." + sys.exit(1) + +import optimizeimages +import composite import world import quadtree diff --git a/src/composite.c b/src/composite.c index 4b58403..1cfdf7c 100644 --- a/src/composite.c +++ b/src/composite.c @@ -223,6 +223,7 @@ alpha_over_wrap(PyObject *self, PyObject *args) /* destination position read */ if (!PyArg_ParseTuple(pos, "iiii", &dx, &dy, &xsize, &ysize)) { /* try again, but this time try to read a point */ + PyErr_Clear(); xsize = 0; ysize = 0; if (!PyArg_ParseTuple(pos, "ii", &dx, &dy)) { From ec255bf29b1f5d0245abd15548aa41e1e75c17b3 Mon Sep 17 00:00:00 2001 From: Xon Date: Sun, 20 Mar 2011 08:24:43 +0800 Subject: [PATCH 043/213] Added region cache invalidation & reloading. Cached region mtimes. --- quadtree.py | 8 +------- world.py | 16 +++++++++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/quadtree.py b/quadtree.py index fbee945..0267113 100644 --- a/quadtree.py +++ b/quadtree.py @@ -597,16 +597,10 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) # check chunk mtimes to see if they are newer try: - #tile_mtime = os.path.getmtime(imgpath) - regionMtimes = {} needs_rerender = False for col, row, chunkx, chunky, regionfile in chunks: # check region file mtime first. - # Note: we cache the value since it's actually very likely we will have multipule chunks in the same region, and syscalls are expensive - regionMtime = regionMtimes.get(regionfile,None) - if regionMtime is None: - regionMtime = os.path.getmtime(regionfile) - regionMtimes[regionfile] = regionMtime + regionMtime = world.get_region_mtime(regionfile) if regionMtime <= tile_mtime: continue diff --git a/world.py b/world.py index 7ed790c..f8e880b 100644 --- a/world.py +++ b/world.py @@ -78,7 +78,7 @@ class World(object): for x, y, regionfile in self._iterate_regionfiles(): mcr = nbt.MCRFileReader(regionfile) mcr.get_chunk_info() - regions[regionfile] = mcr + regions[regionfile] = (mcr,os.path.getmtime(regionfile)) regionfiles[(x,y)] = (x,y,regionfile) self.regionfiles = regionfiles self.regions = regions @@ -117,8 +117,6 @@ class World(object): _, _, regionfile = self.regionfiles.get((chunkX//32, chunkY//32),(None,None,None)); return regionfile - - def load_from_region(self,filename, x, y): nbt = self.load_region(filename).load_chunk(x, y) if nbt is None: @@ -127,11 +125,15 @@ class World(object): return nbt.read_all() - #filo region cache - def load_region(self,filename): - #return nbt.MCRFileReader(filename) - return self.regions[filename] + #used to reload a changed region + def reload_region(self,filename): + self.regions[filename] = (nbt.MCRFileReader(filename),os.path.getmtime(regionfile)) + def load_region(self,filename): + return self.regions[filename][0] + + def get_region_mtime(self,filename): + return self.regions[filename][1] def convert_coords(self, chunkx, chunky): """Takes a coordinate (chunkx, chunky) where chunkx and chunky are From 0046da559477a9e6be5e0603743f0f7b61265b26 Mon Sep 17 00:00:00 2001 From: Xon Date: Sat, 19 Mar 2011 16:01:33 +0800 Subject: [PATCH 044/213] render_inntertile & render_inntertile now called in batches in the worker process, speeds up update scan with a lot of tiles to skip. --- quadtree.py | 84 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/quadtree.py b/quadtree.py index fbee945..67eb7d3 100644 --- a/quadtree.py +++ b/quadtree.py @@ -292,10 +292,13 @@ class QuadtreeGen(object): shutil.rmtree(getpath("3")) os.rename(getpath("new3"), getpath("3")) - def _apply_render_worldtiles(self, pool): + def _apply_render_worldtiles(self, pool,batch_size): """Returns an iterator over result objects. Each time a new result is requested, a new task is added to the pool and a result returned. """ + + batch = [] + tiles = 0 for path in iterate_base4(self.p): # Get the range for this tile colstart, rowstart = self._get_range_by_path(path) @@ -310,24 +313,42 @@ class QuadtreeGen(object): tilechunks = self._get_chunks_in_range(colstart, colend, rowstart, rowend) #logging.debug(" tilechunks: %r", tilechunks) - - # Put this in the pool + + # Put this in the batch to be submited to the pool # (even if tilechunks is empty, render_worldtile will delete - # existing images if appropriate) - yield pool.apply_async(func=render_worldtile, args= (self, - tilechunks, colstart, colend, rowstart, rowend, dest)) + # existing images if appropriate) + batch.append((tilechunks, colstart, colend, rowstart, rowend, dest)) + tiles += 1 + if tiles >= batch_size: + tiles = 0 + yield pool.apply_async(func=render_worldtile_batch, args= (self,batch)) + batch = [] - def _apply_render_inntertile(self, pool, zoom): + if tiles > 0: + yield pool.apply_async(func=render_worldtile_batch, args= (self,batch)) + + + def _apply_render_inntertile(self, pool, zoom,batch_size): """Same as _apply_render_worltiles but for the inntertile routine. Returns an iterator that yields result objects from tasks that have been applied to the pool. """ + batch = [] + tiles = 0 for path in iterate_base4(zoom): # This image is rendered at: dest = os.path.join(self.destdir, self.tiledir, *(str(x) for x in path[:-1])) name = str(path[-1]) - - yield pool.apply_async(func=render_innertile, args= (dest, name, self.imgformat, self.optimizeimg)) + + batch.append((dest, name, self.imgformat, self.optimizeimg)) + tiles += 1 + if tiles >= batch_size: + tiles = 0 + yield pool.apply_async(func=render_innertile_batch, args= (batch)) + batch = [] + + if tiles > 0: + yield pool.apply_async(func=render_innertile_batch, args= (batch)) def go(self, procs): """Renders all tiles""" @@ -359,20 +380,21 @@ class QuadtreeGen(object): logging.info("There are {0} total levels to render".format(self.p)) logging.info("Don't worry, each level has only 25% as many tiles as the last.") logging.info("The others will go faster") - for result in self._apply_render_worldtiles(pool): + count = 0 + batch_size = 50 + for result in self._apply_render_worldtiles(pool,batch_size): results.append(result) - if len(results) > 10000: + if len(results) > (10000/batch_size): # Empty the queue before adding any more, so that memory # required has an upper bound - while len(results) > 500: - results.popleft().get() - complete += 1 + while len(results) > (500/batch_size): + complete += results.popleft().get() self.print_statusline(complete, total, 1) # Wait for the rest of the results while len(results) > 0: - results.popleft().get() - complete += 1 + + complete += results.popleft().get() self.print_statusline(complete, total, 1) self.print_statusline(complete, total, 1, True) @@ -384,17 +406,15 @@ class QuadtreeGen(object): complete = 0 total = 4**zoom logging.info("Starting level {0}".format(level)) - for result in self._apply_render_inntertile(pool, zoom): + for result in self._apply_render_inntertile(pool, zoom,batch_size): results.append(result) - if len(results) > 10000: - while len(results) > 500: - results.popleft().get() - complete += 1 + if len(results) > (10000/batch_size): + while len(results) > (500/batch_size): + complete += results.popleft().get() self.print_statusline(complete, total, level) # Empty the queue while len(results) > 0: - results.popleft().get() - complete += 1 + complete += results.popleft().get() self.print_statusline(complete, total, level) self.print_statusline(complete, total, level, True) @@ -448,6 +468,14 @@ class QuadtreeGen(object): return chunklist @catch_keyboardinterrupt +def render_innertile_batch( batch): + count = 0 + #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) + for job in batch: + count += 1 + render_worldtile(job[0],job[1],job[2],job[3]) + return count + def render_innertile(dest, name, imgformat, optimizeimg): """ Renders a tile at os.path.join(dest, name)+".ext" by taking tiles from @@ -512,6 +540,14 @@ def render_innertile(dest, name, imgformat, optimizeimg): optimize_image(imgpath, imgformat, optimizeimg) @catch_keyboardinterrupt +def render_worldtile_batch(quadtree, batch): + count = 0 + #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) + for job in batch: + count += 1 + render_worldtile(quadtree,job[0],job[1],job[2],job[3],job[4],job[5]) + return count + def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path): """Renders just the specified chunks into a tile and save it. Unlike usual python conventions, rowend and colend are inclusive. Additionally, the @@ -529,7 +565,7 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) Standard tile size has colend-colstart=2 and rowend-rowstart=4 There is no return value - """ + """ # width of one chunk is 384. Each column is half a chunk wide. The total # width is (384 + 192*(numcols-1)) since the first column contributes full From 1d666c7be756d94126bcf7b0fa7d8230f40b6a50 Mon Sep 17 00:00:00 2001 From: Xon Date: Sat, 19 Mar 2011 16:23:11 +0800 Subject: [PATCH 045/213] Fixed render_innertile_batch --- quadtree.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/quadtree.py b/quadtree.py index 67eb7d3..d577325 100644 --- a/quadtree.py +++ b/quadtree.py @@ -344,11 +344,11 @@ class QuadtreeGen(object): tiles += 1 if tiles >= batch_size: tiles = 0 - yield pool.apply_async(func=render_innertile_batch, args= (batch)) + yield pool.apply_async(func=render_innertile_batch, args= (self,batch)) batch = [] if tiles > 0: - yield pool.apply_async(func=render_innertile_batch, args= (batch)) + yield pool.apply_async(func=render_innertile_batch, args= (self,batch)) def go(self, procs): """Renders all tiles""" @@ -468,12 +468,12 @@ class QuadtreeGen(object): return chunklist @catch_keyboardinterrupt -def render_innertile_batch( batch): +def render_innertile_batch(quadtree, batch): count = 0 #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) for job in batch: count += 1 - render_worldtile(job[0],job[1],job[2],job[3]) + render_innertile(job[0],job[1],job[2],job[3]) return count def render_innertile(dest, name, imgformat, optimizeimg): From cd7b9456a956accc9c8ff6429af78d88eaecf2b7 Mon Sep 17 00:00:00 2001 From: Xon Date: Sat, 19 Mar 2011 23:36:26 +0800 Subject: [PATCH 046/213] Fixed worker processes being passed the full quadtree object. Caused massive performance regressions when caching stuff in quadtree.world Offloaded self._get_chunks_in_range into worker process. --- quadtree.py | 61 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/quadtree.py b/quadtree.py index d577325..452b481 100644 --- a/quadtree.py +++ b/quadtree.py @@ -82,6 +82,8 @@ def catch_keyboardinterrupt(func): raise return newfunc +child_quadtree = None + class QuadtreeGen(object): def __init__(self, worldobj, destdir, depth=None, tiledir="tiles", imgformat=None, optimizeimg=None, lighting=False, night=False, spawn=False): """Generates a quadtree from the world given into the @@ -308,24 +310,17 @@ class QuadtreeGen(object): # This image is rendered at: dest = os.path.join(self.destdir, self.tiledir, *(str(x) for x in path)) #logging.debug("this is rendered at %s", dest) - - # And uses these chunks - tilechunks = self._get_chunks_in_range(colstart, colend, rowstart, - rowend) - #logging.debug(" tilechunks: %r", tilechunks) - # Put this in the batch to be submited to the pool - # (even if tilechunks is empty, render_worldtile will delete - # existing images if appropriate) - batch.append((tilechunks, colstart, colend, rowstart, rowend, dest)) + # Put this in the batch to be submited to the pool + batch.append((colstart, colend, rowstart, rowend, dest)) tiles += 1 if tiles >= batch_size: tiles = 0 - yield pool.apply_async(func=render_worldtile_batch, args= (self,batch)) + yield pool.apply_async(func=render_worldtile_batch, args= [batch]) batch = [] if tiles > 0: - yield pool.apply_async(func=render_worldtile_batch, args= (self,batch)) + yield pool.apply_async(func=render_worldtile_batch, args= (batch,)) def _apply_render_inntertile(self, pool, zoom,batch_size): @@ -344,12 +339,15 @@ class QuadtreeGen(object): tiles += 1 if tiles >= batch_size: tiles = 0 - yield pool.apply_async(func=render_innertile_batch, args= (self,batch)) + yield pool.apply_async(func=render_innertile_batch, args= [batch]) batch = [] - if tiles > 0: - yield pool.apply_async(func=render_innertile_batch, args= (self,batch)) + if tiles > 0: + yield pool.apply_async(func=render_innertile_batch, args= [batch]) + def pool_initializer(args): + logging.debug("Child process {0}".format(os.getpid())) + def go(self, procs): """Renders all tiles""" @@ -365,12 +363,18 @@ class QuadtreeGen(object): for _ in xrange(curdepth - self.p): self._decrease_depth() + logging.debug("Parent process {0}".format(os.getpid())) + #stash the quadtree object so child process's can + global child_quadtree + child_quadtree = self # Create a pool if procs == 1: pool = FakePool() else: - pool = multiprocessing.Pool(processes=procs) - + pool = multiprocessing.Pool(processes=procs,initializer=self.pool_initializer,initargs=()) + #warm up the pool so it reports all the worker id's + pool.map(bool,xrange(multiprocessing.cpu_count()),1) + # Render the highest level of tiles from the chunks results = collections.deque() complete = 0 @@ -381,7 +385,7 @@ class QuadtreeGen(object): logging.info("Don't worry, each level has only 25% as many tiles as the last.") logging.info("The others will go faster") count = 0 - batch_size = 50 + batch_size = 10 for result in self._apply_render_worldtiles(pool,batch_size): results.append(result) if len(results) > (10000/batch_size): @@ -393,7 +397,6 @@ class QuadtreeGen(object): # Wait for the rest of the results while len(results) > 0: - complete += results.popleft().get() self.print_statusline(complete, total, 1) @@ -468,7 +471,7 @@ class QuadtreeGen(object): return chunklist @catch_keyboardinterrupt -def render_innertile_batch(quadtree, batch): +def render_innertile_batch(batch): count = 0 #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) for job in batch: @@ -540,12 +543,28 @@ def render_innertile(dest, name, imgformat, optimizeimg): optimize_image(imgpath, imgformat, optimizeimg) @catch_keyboardinterrupt -def render_worldtile_batch(quadtree, batch): +def render_worldtile_batch(batch): + global child_quadtree + return render_worldtile_batch_(child_quadtree, batch) + +def render_worldtile_batch_(quadtree, batch): count = 0 + _get_chunks_in_range = quadtree._get_chunks_in_range #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) for job in batch: count += 1 - render_worldtile(quadtree,job[0],job[1],job[2],job[3],job[4],job[5]) + colstart = job[0] + colend = job[1] + rowstart = job[2] + rowend = job[3] + path = job[4] + # (even if tilechunks is empty, render_worldtile will delete + # existing images if appropriate) + # And uses these chunks + tilechunks = _get_chunks_in_range(colstart, colend, rowstart,rowend) + #logging.debug(" tilechunks: %r", tilechunks) + + render_worldtile(quadtree,tilechunks,colstart, colend, rowstart, rowend, path) return count def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path): From ac0055a6d71dae10bda681cb7cd9e13e6ff0ab03 Mon Sep 17 00:00:00 2001 From: Xon Date: Sun, 20 Mar 2011 07:30:15 +0800 Subject: [PATCH 047/213] Fix Windows compat with stashing the quadtree object into each worker process --- quadtree.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/quadtree.py b/quadtree.py index 452b481..f7dcbb0 100644 --- a/quadtree.py +++ b/quadtree.py @@ -83,7 +83,12 @@ def catch_keyboardinterrupt(func): return newfunc child_quadtree = None - +def pool_initializer(quadtree): + logging.debug("Child process {0}".format(os.getpid())) + #stash the quadtree object in a global variable after fork() for windows compat. + global child_quadtree + child_quadtree = quadtree + class QuadtreeGen(object): def __init__(self, worldobj, destdir, depth=None, tiledir="tiles", imgformat=None, optimizeimg=None, lighting=False, night=False, spawn=False): """Generates a quadtree from the world given into the @@ -344,9 +349,6 @@ class QuadtreeGen(object): if tiles > 0: yield pool.apply_async(func=render_innertile_batch, args= [batch]) - - def pool_initializer(args): - logging.debug("Child process {0}".format(os.getpid())) def go(self, procs): """Renders all tiles""" @@ -364,14 +366,11 @@ class QuadtreeGen(object): self._decrease_depth() logging.debug("Parent process {0}".format(os.getpid())) - #stash the quadtree object so child process's can - global child_quadtree - child_quadtree = self # Create a pool if procs == 1: pool = FakePool() else: - pool = multiprocessing.Pool(processes=procs,initializer=self.pool_initializer,initargs=()) + pool = multiprocessing.Pool(processes=procs,initializer=pool_initializer,initargs=(self,)) #warm up the pool so it reports all the worker id's pool.map(bool,xrange(multiprocessing.cpu_count()),1) From 346ee004e87d73ba95a2af5cb58bd1c9f137aade Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sat, 19 Mar 2011 21:17:29 -0400 Subject: [PATCH 048/213] fixed some things left over from configfile merge --- configParser.py | 4 +++- gmap.py | 1 - quadtree.py | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/configParser.py b/configParser.py index 0f43a34..7ed285d 100644 --- a/configParser.py +++ b/configParser.py @@ -1,5 +1,6 @@ from optparse import OptionParser import sys +import os.path class OptionsResults(object): pass @@ -61,7 +62,8 @@ class ConfigOptionParser(object): g['args'] = args try: - execfile(self.configFile, g, l) + if os.path.exists(self.configFile): + execfile(self.configFile, g, l) except NameError, ex: import traceback traceback.print_exc() diff --git a/gmap.py b/gmap.py index 50b6822..0dc7191 100755 --- a/gmap.py +++ b/gmap.py @@ -58,7 +58,6 @@ def main(): parser.add_option("-p", "--processes", dest="procs", help="How many worker processes to start. Default %s" % cpus, default=cpus, action="store", type="int") parser.add_option("-z", "--zoom", dest="zoom", help="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", action="store", type="int", configFileOnly=True) parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true", commandLineOnly=True) - parser.add_option("--cachedir", dest="cachedir", help="Sets the directory where the Overviewer will save chunk images, which is an intermediate step before the tiles are generated. You must use the same directory each time to gain any benefit from the cache. If not set, this defaults to your world directory.") parser.add_option("--chunklist", dest="chunklist", help="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.") parser.add_option("--rendermode", dest="rendermode", help="Specifies the render type: normal (default), lighting, night, or spawn.", type="choice", choices=["normal", "lighting", "night", "spawn"], required=True, default="normal") parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.", configFileOnly=True ) diff --git a/quadtree.py b/quadtree.py index ec97e50..73d982b 100644 --- a/quadtree.py +++ b/quadtree.py @@ -107,7 +107,7 @@ class QuadtreeGen(object): self.lighting = rendermode in ("lighting", "night", "spawn") self.night = rendermode in ("night", "spawn") - self.spawn = spawn in ("spawn",) + self.spawn = rendermode in ("spawn",) # Make the destination dir if not os.path.exists(destdir): From e096eb45ff0fba8cf8d1dac4b4282ac4b3472717 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 19 Mar 2011 22:55:59 -0400 Subject: [PATCH 049/213] Initial version of C lighting code Needs *lots* of performance work --- chunk.py | 5 +- gmap.py | 2 +- src/.composite.c.swp | Bin 0 -> 24576 bytes src/.iterate.c.swp | Bin 0 -> 32768 bytes src/composite.c | 30 ++++++ src/iterate.c | 243 +++++++++++++++++++++++++++++++++++++++++++ src/main.c | 2 + src/overviewer.h | 2 + world.py | 3 +- 9 files changed, 284 insertions(+), 3 deletions(-) create mode 100644 src/.composite.c.swp create mode 100644 src/.iterate.c.swp diff --git a/chunk.py b/chunk.py index ab5d41f..2086485 100644 --- a/chunk.py +++ b/chunk.py @@ -545,7 +545,10 @@ class ChunkRenderer(object): if not img: img = Image.new("RGBA", (384, 1728), (38,92,255,0)) - c_overviewer.render_loop(self, img, xoff, yoff, blockData_expanded) + if self.world.lighting: + c_overviewer.render_loop_lighting(self, img, xoff, yoff, blockData_expanded) + else: + c_overviewer.render_loop(self, img, xoff, yoff, blockData_expanded) for entity in tileEntities: if entity['id'] == 'Sign': diff --git a/gmap.py b/gmap.py index 57edb0c..92bff15 100755 --- a/gmap.py +++ b/gmap.py @@ -140,7 +140,7 @@ def main(): logging.info("Notice: Not using biome data for tinting") # First do world-level preprocessing - w = world.World(worlddir, useBiomeData=useBiomeData) + w = world.World(worlddir, useBiomeData=useBiomeData, lighting=options.lighting) w.go(options.procs) # Now generate the tiles diff --git a/src/.composite.c.swp b/src/.composite.c.swp new file mode 100644 index 0000000000000000000000000000000000000000..a7256a664bf54db729c9af0a7a72cf37da4bd72d GIT binary patch literal 24576 zcmYc?2=nw+FxN9;U|?VnU|=xLYW1~jkYcE`Vqi#2&dAIIi4(xZmBgrGV5kEdsGpKr zmYSoVUzS=_mYG_fTBMtnUzDw%m|m2b7GIQ_m=d3#ms6=Cs7$O-M7#N|Lg@J*=fPsO*0m^4(U|^_ZU|`6B@|hVJ z7Zl1X!+mxJhKuYB3>VlL7&fvqFic`+ zV3^3xz>v?*z+lhLz`)ARz`(-Jz;KU^fng&X1H)7{28Ika28K{J1_p071_oC)1_l#0 z1_om`NLaf;#kAQN7#P_Y7;dvNFsx@~U|7n^z_6B;fnf$K149`r14B3~1A_xA1A{#) z1A_u90|O^30|N&u1H(ra28RF43=9vL85q7XGBBKBWMEK$f`CfbqN4cV)RN$mqRhN> zjettmisbmP#GKMp*P^2QA|0^G%rpg!%-rCjWL>+8;>@a41w~th%v|5Z;%t~eO^pIn zK_$8Zuz;pQHP|G5Err~~>{Nx~(xOy_+{EH+1+enm#FFHU)MAC=qGX7GmOjLdwP0N! z#}=iQlosVF_=WoTSi=QD63AF7J+mw|4`Og;Zen_>LP~yWu|i&ci9$wVS*k*AeoCrB zj8c%hlVgmMLVl4#jFL}`l9DFEMB)RaxTGjKw?G3H4j`j-6u?F+X@UaCKtWAS0lOj} zs0vW1fc-+AH;eO2i;`0ni1cWXyAxio*4Cm2Il@(-Fvcp4RS={ag7x*a@=Hs=@ocM* zm{X9EXbrIhBCVi6gcUHQ3JOT35os@x%8IiUP@M_xa4!9vY9h#*I1AsK;`$Bu16Y~^6Zb?ZXyI3->yx4zX3x&;S{jnU@apk!EJDCNzSfzK56!N*Yj;3Di{J#G#N_Qc{$e zR9cd%pq`nlu8;|-s}dEG^NUi!p2*Bg2Rol|9TiepfLv#TT%xa~kXfP*F;iDpp(ru4 zI8`ArPazdlwSxmPwIZ_w9OMcL3ZPmZUSH|jUh)`hs9@Rt~En711L_= z6FAHn3R?L|S(*$`Qy3U(VbKFI0+NKFve@j1_i%M|VE~0_N@^OYT~L}_qEOAiz^PG^ zTL3Bv5;YaH6f}}F6|@x$jVyE&G&D3oQkn{Ob_y0CQ7{k6)nrgkNlnYlOI7d<^>OhG zGcq;RNYqhC(orbMEzsnIf*4R}rWNJqDg=1?s23~5Lp`XM42leWErp!S>{JDfL~My95kItodpB?`Hv$r%c1iNz(UMGE3At^CATcIQ$9OEgeX^Evd zC17Jft-fMC1y4}APf0D#OwUVA0jWz$RVXb6F*AztOVcwH5DSJ2Ds>dfGcuDiK>9;K z&P>b90X3@%a#C|s^GZNYN-ixf$hp%AP zQ&8|s12sr>6pB+*6>Ks}N(!v>^~=l4_0sc7_413-^>Z?lQ}c>bi}mfm0TY>D3M<>d zZP=pJ#&!rd=a!96uEwJ0%1A)quVCo@^W2V#&yVorWuI)j21 z$bV>3Ajju|%9@nalEloMVz7LLqHaS(owi)h=+e@h=QYEq(Zo3P>`cvNTjs_ zD0D!zU@EA|4$8Qhxdl0ysVNHOiA6<;c_kpPflTnt%u7u!N=z$JKm;Qw{iI|TLkrIo zh0Hv#-5L1>AV(yYC}fr>lxODTfU1tt;?%U#935x?ROXi!DdZP`l9Q%FVqT>}P9i9I zm!%eg(k0mcNtIy3+=^0D6@v5AO3D+9QWe}lDK!zKO-CU$vm_(6NCB!x!59_?&;Zu~ zg_uSnI6da(r(~v8f}B&Dmy%ipHn=3UD7P4<2VcAuD-?iYvN$6(1z$j zWqzqba$=rBQ7R&YKn_UEOVQ6Sf(8RpIxkizNGvLWxd&Tl>w!}>sQ(|s$H1_J57Pe! z&FNp}XJF{$XJ9DcXJAO$H1_MkAYzu9|OZOsNTgO z{ljKdWYi<0Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0s|2OkWpCZ=weZ731nCf zA_^LJ45*Cv^aGFarlb~^Ku38&68c&S0pMY6g}l_%lwyU_V$h&betDh(XuJSwU>Rs$ z6g;>E9WE_OEm5$wRREg>o#6xvAq^C1fFyN5Gn+c#c}X3GlnNb%lu8}Q%z_SN@E$gX zj&Lc01#%wL0rqF)&Q$V_>M_ zV_*p9V_-1lV_;z6V_x@CD641+T08bSGgD% z4nXJocXBZ>WN|StIB+pAyyIkGxXH=DaG8^V;SwhU!(vVbhDc7RKL#R^j@mgI0;3@? z8UmvsFd71*AuwD+0Mw=eVeIXy(Is|7tT}|QMu03e9P&%-@GP%{dlkIOl7LUq)F7?J z08RKKt*;`g+W_q{fch!0&H`wi0N%Be$ejn`6FA(HkY!4kz67rTQczIP*HTE%NCi#s zXQV2WB^G7omli`7Re+ap7K6G{uvMfb8L5@fMWvu=b5QpT915Vkhv9$dG82&f3No~?gi~RLbTh0orb<}q_`*_ z3k<=lIE=s~bm5r3mO@EtZb5!gVo{|+a$-($X%3`Y2MV0Tq@2`ZkiS7*F3p3kc>oz! zp`%c#qmT*d1%p=ur554P2U1>Kp`%b-30DnWUkW}~4Yc|Kv@{FU@y#m%b!X!<5{u)( zYdS%(QJfv0pO#jfS_0*Orsh*p;YKAVCTFCkC_uWU1x5J!6VqOZ=(~yP5Ag@E+243CX>43Z4AZ~GKa&l^Mv7Q2G z2}er4f}ei~cys|YvXGLRT$Bn~xdAan5fp)-C`v;a7*GJMJ;+N-Fx2ueFy!zsFl6&EFu3tBFevdbF#O|YV7SH2z_5**fnh5* z149*b4*_WJfGjrygE%(>gBUjh!xt_FhHYF73a49Z*#45v657*0aR3z|6@7y>vM z7<4!p7_>PV82C9E7(Q_@FkItcVA#pQz%ZACfuVtefuV?lfx(x9fq|EUf#EYd1H&zL z28J{23=BKj85pLqGcdTZGcd@oGcbH*V_?|D#=wxs#=zji#=s!W#=!84m4V>}D+9xG zRtAQntPBj>Ss56%u`)0$W@TV#VP#;*V`X3nVP#-2WMyDbXJueegs$`FXJugEWo2OC zVP#-A!@|H&$-=;p$HKs1$HKs1!@|HI&ceX(fti8f1TzD}DrN?T8fFHDOlAg#3}yxf zRb~bT0cHjUer5)S*Gvoy%b6G$a+nwxBAFN%+?W^`1eh2Y_(AcFrIiNFa-hXs8j1mx zjz#J50f|M$sUf8WIjI^2`Ng1hdYMW(3TmJnq?S^t2`k<~I>BpH!7E$gr8PL?q6&e^ zy`oBm#Pr0>Jjj|f@H(=R%-mFklA=mbP6C|+30gFk56YvkA}UP*kCQTy9i^k72DVp6 zK@IFAxFhwoz!@1QaYQ2M|q zX+a^RucZKTMKRJqtwM1=Xq6Od@t2Yb8aK_Ugp`9&pMfGDGCT{7dQjM4ECYt907V4| zXXb%IOF`QfJO~_*Ts?xMK^V4ELfcls1QM(uQ5eq5Lth&YN@m4H$%uLwvUnDz0lJzV zp1VNesBQv@fY!`|U52^=0we{)n3_N`VBq)1u_+~2>|4Ja8t=hQwL;=g06xVB(%Yz#?VF>%rF=YGC;u=Y5A-c zsDwe*X{f27jnz&1S_*XF-z%HoqBS@f!ihrREf;LT4&qF2Ic6%sfy+g*H85%0M(|xu&)) z@@@u@Ck5z}OjqJffzasH%FM$|Y_!*ik!(QD0VPyKB%?YD&G$y8rl3v!KsPu<7gs81D}cAL z6jvyKHt-c^LklnkZAftjY5F6)1T`l!57kg`zZKjxMK=?+y|5s&A~gq80U@PqsA152 zfNCPBy8v@$3f!3~AZMD8zU>Cx1c-;gfr)<;4kB@))Ci!c0AYPCc+M!vS4hd%fz@8n zS{4-h;6{jojje(KDCWUU5ik#y50JXViQs(>MX3r!skw=ndBxym%;2sSDDRczgBCrf z#PGIS{_CLP`ZB zw7}+6C}=}^F-Ycs6++Ab`6VZ_Kp`bF7qT6oSO?UK1kJprrhtl5u&coqE9lz7yQE+f zzyeUaKv^KQM4_|*R@xv8gqwnhnqo*Cg3SgOIgp+sLO}|W0#LMpHRz%RkPRd!f=xka zErw(^uqbx3kraT<2Jv;#0u5?5NM2t{0n*1+fOY;L&Aozrct%I~9@Mpimbb8Gst#ly zEvP*QYMo%TVnJ;fkUa=5K!O_-vEXb9=0c(rqz)4Ha9J=HA`6OeFc+L$aVB0g-{@DR z7UhH8Sgep(0BYV9WhR!S=2XHX4pdyiiW!i-prHU=J9q&Bu^bdk`Q-|^poRQNl_ja* zS^MJb%mPr$54<4=(l^dattf%CJrqF7K$d`f1-1iHc!A0lSg{AU1XL55DA+3)D_AKQ zK#L@(m1&7Z3YmE&`OpS9C~`cc`@5Eu=C(GVC7fzc2c4S~@R7!85Z z5Eu=CVHW}=1#Are85kIt7#J9`85kHCp_r9{fgyl_fguOVXMqe4FdT>S*%%lY+!z=b z4nq0t3=9l03=9nSpnPViycr|J%wZRBqaGR!fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c z4S~@R7!83T6atAU3=H}V3=Ho<9e)AH7{DKX28L7o3=GHl85rvL85oTC85o}NF)$qF zV_-PI$G}j^$G~9B$H4H6mx19BF9XAVUIvB|UIqqzUIqqjUIqp&UIqpsUIqq1UIvCw zJPZsUc^DXW@h~ui@-Q%j@Gvk4@h~vFGh8vs=42L-x7#46cF!XRTFjR6fFqCjIFgS8DFsO1eFeq>` zFtBqnFfeg4FwEv)V3@|iz|h3Oz);A+z)--!z#ziG!0?)#f#DT91H&zL28QMA3=GTI z85l~~85o?{85p$K85lI#85sVvF)&WvM@0GVrF1i z!OXzW#LU1D!OXy*$jrdN!_2_I#>~L5gNcD5n~8zJpNWA%g$WX8??CZJe0oz*P*6xq zOis;BEY2>DHi%WQRS2jIDJ{rJjdxEi@hnNr)qqRK7gXvf7-(8EfHdjrCl(i{7L{ng zlsjjnCTF84)`ThzsPs?DN=+_N(1IHkZ5#_R28&#LK_%D~5KSOwIF^(Y1(y_M=A~;S zXO!k;$LHp!=qM<`O;f_KC@Ci~IXgZ%KPSJ4IJ1y6z)Y(J`xE5XqSTVoqC5q^P#+(N zt3iV3SgE8aF|W8Fu_!gKBt9u8KRLTtA+s2)31%WlOF*S-QBizwY6;Xs0hO*5$?;){ zIi;ztMMe2VI$&Ef(-btYm}hGXHcwNb8mtPN3I$k*;t4oReJ~dm7o;XQj2vIl+g5`83hXpP`bhzqnIW^wZIG_$uTg+FwOc}3L*Y3{tC&7c?u;N zsl}-ZNvR6yIr&M6ISR!ki6xoI_6ksml>9vP5{2Z9#Ju!Wg_KfIa#JWM%1A z2m=(-Q%gYMuK<=%NJ%V7R7lAzN=+`wsZ>ZS%Fk8EE6pvav;v1aG$`U-T%Chl-83?D z(~C2!QVrt^D&YkHnw$Yb4yGCs0eL0yj>SItdFfb8fvPmXp%Q8)cE>;+0yP5UG_Wbb zsfDGfdC7=E6{``lynVYIzc6pVnC&1QF?qpVo`A_xQ@_BEJ_Cz0sj7( znf^*T3JPk_gr^3!TCE~KElo#3tr9|kqZky!@u?LBiFqlhDVmy)1eBRqqEMluP^qI( z1u0-b0-3q#6*>x;x#^VKn(s1m(;?y@ zYaqc03Pp$*!g#R5P=eH20nH?^cdZ#1z;t|3YFx&qGTNfP&)z~8qlcIQ80kC8NjZB z7ywFhAax41AUzN-fsz+Q3{!fVFw%mBhOh<>!J6$0SW_G|z#(UmSvH>uA6X(E>SIN1-&YI5RyjH3d|Y7AYiw zVv#{PB{dBcGU=%$PN0^N2H3igjLf`r9fb;=N}VcA1r03?RIOT?8ldu~D6tY0qX8j7 z#wgk}Dl~NzG%7(vm8PbqCMc|P6O)Vb71HvHK-EeKC`7>)dAdNVl0*e?CQ!&vQ^-xt z%`d717lX=~dC57YDX9uJ;HpMHv8X7qG9TPx)yuGBV1Uajff74piLzdX5(5KBw}O^J zd1grlxC--!uVB_wQ1DDs$jb-iiqupEn~ai@0xNy}^73-M^t@8N{GxRIoXq6ZyyDbi zeLFn{1ucch{8EMDjQrA^6orh$vQ&kl)a2C6veXoXM1|!1f=ZB+OEOXw-2Flo+*9*X zixP7b0!ou|GLsd2AOdPBGX z90Ia1skB5P+%v?(KQu(a(JxXV+%YJ~(Jv&@8XP+Lr6mffWvSp6m_lZ5K~83Bib6Rk zobyURUIUrno0*rIT$GqrqJRiSQ2RM0v$&)vGpV#BHANv4VnRlK0mu=FB?_4(3gwwO zIiNOOX>n>=X^svw04noKixlz;KrJ>+g~Yr{g`C8a)FOql)S}|d{5-J#lPbX;aVttq zRS3>cD=AMbN>y;nFU?Cy1ZmSzNX;zCKWK_OX?SX2UY4>n8mKsks3)c@CEVPJ>=P528y=JXHnGcYXT zXJDAX&%n^n&%jW?&%ofs&%nUL&%p4CkAdL~9|OZKJ_d%Bd<+b+d<+a0d<+cid<+b1 zd<+b%d<+aHc^Me;co`Ukc^MdX@GvmU;$dLu;9+2B=V4%I<6&TM^^R89s4 zaZUz?4;%~(^Ent8YB?Af(m5Cyf;bo$G&mR-)HxU!96=PdF%yi0^BUsvHf0(GVD+ApmLm7C}amFuPhtn%0nZ9->_b?aF{# zFu92Z@Fp9q-3OJ2c6BhMaq0t0fI2fUD{CQL22fiY;z|$;gi(3eML?p-`FSOod8Mh4 zmMKUC9YdS7@rbrBsNb!l0BTR?!n%11P(Pq+2K5a}6ck}&U*Mhux*9~VmE;!SFbC2s zcF9aG0ga7;$2Os12o925PzMsXZhb9<5>R!TS*(z#kd$9klwY2jqEM8YmRbbr+3P9f ze`YEZ&MX71v_941M6%-JmjVuT2-ULHOT}yHcbQBCh9Ws;#d1792W{yi@ zNg`};71<0B8>=}6&}M&T3XY%&sEqgY0}rZzg94;ZL0?NDDK$BL`WclGMbU zVvxVEMnXw$0k{hR>4y2GmTQ2LHON)y7^`;7FQb1A%4oXK*X9TtX1M&fU1O?Q60*{+$LOcF2$H8b= zvW0dol2R2C6$;z-0+f~%Y++**#d=`1xrqhQ zU`A$2EH=AAzEcdS#2k%)mc7vW3sFLWvKwXqV-H*c1%qNH%18y~XrB&fpa5w|0#q$R zvXr)=jsj?00Y}AzAw#IxjRwVof<^_X9yI{jt)Ni} zVjG%5I8|VdkvWLRfEoPI7zU>tlnMzffjzyXmZcU|D!`%$TiSrl&ZK3gmlmZefW`?j z^2y0;$>h+;bmYj;bmYj=7pG3 z$-}@2pn%EQ1A&cndq%frCn#>2qi%EQ2*!Nb77$HTzzm79U# z8aD&OHf{!ntytCrcyKc?*l{y3@N+XT@NqLR{N!R_xW>i6aEOb6VLcZE!zwNYhH@?j zhG;GZhA=J$23sx$1`93*hKrmG3}-kQ7qBGt%9KuW_H3*jHuYa`7<*GQx9rO4Xg}R5om-|qe{V6 z!N6LfO2J0K(8$7Cp-NjDlp_%CQ~+fl(BcHpRE&bILWP!@g0?~>m^3u9&@wbq&{i-s z)iOghTwe=h9!OSKS0O(wtvIy=+ybqF&U9wx=jnj8DWJ+%pck2-X$J*c1<-Vmg0?~h z$V8C!$e{%G2*`;B)(Vv%kC<62R3iKVmab5+RWLNQR;W<0Q_wZER;bX`h4~X4xQMBO(0gUz(Nf)D~r0)LJ1nesEs0Ub&GXs z7t(gaf7+rWsZ*kFJ_1h6M?tltEeufjfS{IY&o92}v=`q|sFqh++gf zDGp8iP#$Pf92~OnJ_Bqi8dL#d;uk6anp7vC13nQ9)dE(IHEV&FE+~O!^ARxyn%c}s z1(nC(LJm~gL5sJ5(Nz=4*~O3w4;(g#i~=rIKs_oXS@85RWZlk?TQvcn=~mDpyjTKO zgq9R#7UZNVB!gB*Lb@|0`3kv-*{KS}pe_%5{7@k|zZ5iznhu((OU^GU0xy`<2Q9h) z2P@9x1{z`mt3c!`Xe9y;e_D+kgR%g^M)*=p$a*WVd+-b^V{|Z~iyT0PA~3>u(8>^K z8G(=nvGnx|iZb&`(lnG(^7B%`lg&u$D`N6sIT)lCff0i}pzaT7}~6weA4gnk$c zVmoFZ4J40nJSc49^OM1A3?RV+l1Im&jDf5YTt0z%+R$Zc$a1(%L1aEO^A3<8in|EKvG7^d+vF!b^>F!b;vkqz~IEkz#z-V!0?Ngf#D@D1H)Nf28KPn z3=B2A3=DC+3=BTJ3=BNH3=Hph7#QyGFfiQaVPM$9!@$tT!@v;1!@yw1!@wZH!@$4> z9TQl|&A`yY&A<@M&A<@E&A<@I&A=cE+BX1Q|Ifn3!0?Qdf#C`#1H)xb28L~%3=FZH z3=A=x3=Gkn3=HO+3=E$*7#MbPFff#HFfdqfFff>NFfd$aXJA;$&cKiXT_2#s&cGlJ z>ff_5FjTQIFzB!`FtD;QFl>RY*$1ryNM~hWuw`Xnuwi9j_|C$>aFB(8p^}AxA%%s3 zL5hWe;SOkhKD5uioSA`P88ZVz6*B{a1+?G(l8J#~9}@#ZITHhe783)*E=C5135*O3 z{pkI5^aKUsBl0BDd=*B5!l@D@frQDN7o)0uR5FYNW3cBotRagORv;$U@FKxxkQoRJ zH;oh%FdR>cW`x}e3JP%R;j;=FXw3&51*AlVqE`VlM+lxt8J!eG=^G%2(&(h<$ek30 zbbOFg!-$#?9YGV8$Z-r}!_|%*jCQFAy(Ui*2ZJ)=Yp0`kIton18@kAz&*Ik-1O0THBc!xr03Njg)pLb z0iGWqZDtKLq5~QTD~>iIyiFT4DhNt>2u+5BHG#%3K=c3Uj0_BuK?eXr*Z*JVXJGK* zXJGJ#?)zWF$H0)y$H1V^$H4HAmw{m^v`=5n%fOJz%fJxK%fJxC%fJu_Jp+J|mw};| zhk?NcwD+Hzf#DE01H(aX28MiY1_n*&{QVCu28Qok3=HSE7#N(Pd-fM`GBC7pGBC74 z=jTE5@=G}w7&18+7^FEE81Az(Fl=LIVAu+shwo))VCZ3IVCZINV5necVDMsRV7SJ{ zz|hLZz+lD3z`)JMz`(`Ez_5drfuV?%fx(HDfx(fLfx&^5fkB*=fuVC$9nHd<~F)=V4V`5-f$i%=<$Hc&p z%f!GC$i%?l0E#ar28KtB3=9{L#t=Y>02$*cgOHV>u(6KLDkvaQGbmYNueTH+W29I{ zXi;oXP*A{a38qSP5fC4TevlMV4h1R1=~(bSM_j|nAe{BGwEnlLVNg+ih$YtnQUtbxq-V=Q=AGApia`J-?cy$wWBdVs3f@f}G zda7$)Mq*xas-6=(FcD`~L>t6v>ZL*yAP-S%Vm7crP6AcWiXhLx+U%fJ6xdyjNP)=e zaSwfu&IW)27zHDe7E*y*F+>2<4%#`9U!;(iU#_E2 zo~i)a1)GytRjH7emjd3O4kz@BO`(vv?do+vVlr{+%}CC+lk4k zu*w2=;g2ZkKtYP69$cT|E9yWEc&x#XsDDrkPaMsG5jTkoUph~Du?@>tPRbB>$3%m>rnb7?N0lW+h_Ph)X zcDxJ>k9ZgucJe^Z5?H~*z);G=z)-@&z~IEgz#zcG!0>~cf#Ewh1H)Nv28PAZGX=7_ z85pv-85lCT85qpD85sU?F);k)Vqo~j#lWzJi-92ldVXLC7XyP57X!m5P6mdLoD2*z zq3Z{PIT;vEK-Ucfa4;~aLH7cH&H}u`&cJY*oq^#LI|IW;b_Ry&>Se=3ilDZM|#xTqaiRF z0*DZRHX+~?p0PWuyYE2*6rjPV(fNZRKYsv91BgaE8M6n7v;$p944IP#nTrjNo@Sb# zoSmCk09!x|T@wY}WCQMqK?gS7Q%jJ}r-iQ81RaWqss&~cc=C62Uk>*gK z`%*xgU!c8mM1F_Lfws9o<)9*l;FJhjL;zhT4iiU=Exsize; + int ysize = imDest->ysize; + + for (y = 0; y < ysize; y++) { + UINT8 *out = (UINT8 *)imDest->image[y]; + //UINT8 *outmask = (UINT8 *)imDest->image[y] + 3; + + for (x = 0; x < xsize; x++) { + //printf("old out: %d\n", *out); + *out *= factor; + //printf("new out: %d\n", *out); + out++; + } + } + return NULL; + +} + /* the alpha_over function, in a form that can be called from C */ /* if xsize, ysize are negative, they are instead set to the size of the image in src */ /* returns NULL on error, dest on success. You do NOT need to decref the return! */ diff --git a/src/iterate.c b/src/iterate.c index 031864c..61e2ff1 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -45,6 +45,249 @@ texture_alpha_over(PyObject *dest, PyObject *t, int imgx, int imgy) return alpha_over(dest, src, mask, imgx, imgy, 0, 0); } +PyObject* +chunk_render_lighting(PyObject *self, PyObject *args) { + + PyObject *chunk; + PyObject *blockdata_expanded; + int xoff, yoff; + PyObject *img; + + PyObject *imgsize, *imgsize0_py, *imgsize1_py; + int imgsize0, imgsize1; + + PyObject *blocks_py; + + PyObject *textures, *blockmap, *special_blocks, *specialblockmap, *chunk_mod, *transparent_blocks; + + int imgx, imgy; + int x, y, z; + + if (!PyArg_ParseTuple(args, "OOiiO", &chunk, &img, &xoff, &yoff, &blockdata_expanded)) + return Py_BuildValue("i", "-1"); + + /* tuple */ + imgsize = PyObject_GetAttrString(img, "size"); + + imgsize0_py = PySequence_GetItem(imgsize, 0); + imgsize1_py = PySequence_GetItem(imgsize, 1); + Py_DECREF(imgsize); + + imgsize0 = PyInt_AsLong(imgsize0_py); + imgsize1 = PyInt_AsLong(imgsize1_py); + Py_DECREF(imgsize0_py); + Py_DECREF(imgsize1_py); + + + /* get the block data directly from numpy: */ + blocks_py = PyObject_GetAttrString(chunk, "blocks"); + + /* + PyObject *left_blocks = PyObject_GetAttrString(chunk, "left_blocks"); + PyObject *right_blocks = PyObject_GetAttrString(chunk, "right_blocks"); + */ + + textures = PyImport_ImportModule("textures"); + chunk_mod = PyImport_ImportModule("chunk"); + + /* TODO can these be global static? these don't change during program execution */ + blockmap = PyObject_GetAttrString(textures, "blockmap"); + special_blocks = PyObject_GetAttrString(textures, "special_blocks"); + specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); + transparent_blocks = PyObject_GetAttrString(chunk_mod, "transparent_blocks"); + if (transparent_blocks == NULL) { + PyErr_SetString(PyExc_ValueError, + "transparent_blocks is NULL"); + return NULL; + } + PyObject *black_color = PyObject_GetAttrString(chunk_mod, "black_color"); + PyObject *facemasks_py = PyObject_GetAttrString(chunk_mod, "facemasks"); + PyObject *facemasks[3]; + //assert(PyTuple_Check(facemasks_py)); + facemasks[0] = PyTuple_GetItem(facemasks_py, 0); + facemasks[1] = PyTuple_GetItem(facemasks_py, 1); + facemasks[2] = PyTuple_GetItem(facemasks_py, 2); + + + Py_DECREF(textures); + Py_DECREF(chunk_mod); + + + + for (x = 15; x > -1; x--) { + for (y = 0; y < 16; y++) { + imgx = xoff + x*12 + y*12; + /* 128*12 -- offset for z direction, 15*6 -- offset for x */ + imgy = yoff - x*6 + y*6 + 128*12 + 15*6; + for (z = 0; z < 128; z++) { + unsigned char block; + PyObject *blockid; + + imgy -= 12; + + if ((imgx >= imgsize0 + 24) || (imgx <= -24)) { + continue; + } + if ((imgy >= imgsize1 + 24) || (imgy <= -24)) { + continue; + } + + /* get blockid + note the order: x, z, y */ + block = getBlock(blocks_py, x, y, z); + if (block == 0) { + continue; + } + + /* TODO figure out how to DECREF this easily, instead of at + every continue */ + blockid = PyInt_FromLong(block); + + + if ( (x != 0) && (y != 15) && (z != 127) && + !isTransparent(transparent_blocks, getBlock(blocks_py, x-1, y, z)) && + !isTransparent(transparent_blocks, getBlock(blocks_py, x, y, z+1)) && + !isTransparent(transparent_blocks, getBlock(blocks_py, x, y+1, z))) { + continue; + } + + //# top face + //black_coeff, face_occlude = self.get_lighting_coefficient(x, y, z + 1) + + + PyObject *t = NULL; + if (!PySequence_Contains(special_blocks, blockid)) { + /* t = textures.blockmap[blockid] */ + PyObject *t = PyList_GetItem(blockmap, block); + /* PyList_GetItem returns borrowed ref */ + + /* note that this version of alpha_over has a different signature than the + version in _composite.c */ + texture_alpha_over(img, t, imgx, imgy ); + } else { + PyObject *tmp; + + /* this should be a pointer to a unsigned char */ + void* ancilData_p = PyArray_GETPTR3(blockdata_expanded, x, y, z); + unsigned char ancilData = *((unsigned char*)ancilData_p); + if (block == 85) { + /* fence. skip the generate_pseudo_ancildata for now */ + continue; + } + + tmp = PyTuple_New(2); + + Py_INCREF(blockid); /* because SetItem steals */ + PyTuple_SetItem(tmp, 0, blockid); + PyTuple_SetItem(tmp, 1, PyInt_FromLong(ancilData)); + + /* this is a borrowed reference. no need to decref */ + t = PyDict_GetItem(specialblockmap, tmp); + Py_DECREF(tmp); + + } + if ((t != NULL) && (t != Py_None)) + texture_alpha_over(img, t, imgx, imgy ); + + { + // returns new references + PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x, y, z+1); + PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); + double black_coeff = PyFloat_AsDouble(black_coeff_py); + Py_DECREF(black_coeff_py); + + PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); + int face_occlude = PyInt_AsLong(face_occlude_py); + Py_DECREF(face_occlude_py); + + + if (!face_occlude) { + //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) + + PyObject *mask = PyObject_CallMethod(facemasks[0], "copy", NULL); // new ref + //printf("black_coeff: %f\n", black_coeff); + brightness(mask, black_coeff); + //printf("done with brightness\n"); + alpha_over(img, black_color, mask, imgx, imgy, 0, 0); + //printf("done with alpha_over\n"); + Py_DECREF(mask); + + } + } + + + { + // returns new references + PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x-1, y, z); + PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); + double black_coeff = PyFloat_AsDouble(black_coeff_py); + Py_DECREF(black_coeff_py); + + PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); + int face_occlude = PyInt_AsLong(face_occlude_py); + Py_DECREF(face_occlude_py); + + + if (!face_occlude) { + //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) + /* for now, we are lazy and call Image.blend to change the brightness of facemask + * TODO find some faster way? + */ + + PyObject *mask = PyObject_CallMethod(facemasks[1], "copy", NULL); // new ref + //printf("black_coeff: %f\n", black_coeff); + brightness(mask, black_coeff); + //printf("done with brightness\n"); + alpha_over(img, black_color, mask, imgx, imgy, 0, 0); + //printf("done with alpha_over\n"); + Py_DECREF(mask); + + } + } + + + { + // returns new references + PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x, y+1, z); + PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); + double black_coeff = PyFloat_AsDouble(black_coeff_py); + Py_DECREF(black_coeff_py); + + PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); + int face_occlude = PyInt_AsLong(face_occlude_py); + Py_DECREF(face_occlude_py); + + + if (!face_occlude) { + //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) + + PyObject *mask = PyObject_CallMethod(facemasks[2], "copy", NULL); // new ref + //printf("black_coeff: %f\n", black_coeff); + brightness(mask, black_coeff); + //printf("done with brightness\n"); + alpha_over(img, black_color, mask, imgx, imgy, 0, 0); + //printf("done with alpha_over\n"); + Py_DECREF(mask); + + } + } + + + + } + } + } + + Py_DECREF(facemasks_py); + Py_DECREF(blocks_py); + Py_DECREF(blockmap); + Py_DECREF(special_blocks); + Py_DECREF(specialblockmap); + + return Py_BuildValue("i",2); + +} + /* TODO triple check this to make sure reference counting is correct */ PyObject* chunk_render(PyObject *self, PyObject *args) { diff --git a/src/main.c b/src/main.c index f56fb41..48968dd 100644 --- a/src/main.c +++ b/src/main.c @@ -24,6 +24,8 @@ static PyMethodDef COverviewerMethods[] = { "alpha over composite function"}, {"render_loop", chunk_render, METH_VARARGS, "Renders stuffs"}, + {"render_loop_lighting", chunk_render_lighting, METH_VARARGS, + "Renders stuffs, lighting"}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/src/overviewer.h b/src/overviewer.h index 5746b53..bfd9bb9 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -33,8 +33,10 @@ Imaging imaging_python_to_c(PyObject *obj); PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, int dy, int xsize, int ysize); PyObject *alpha_over_wrap(PyObject *self, PyObject *args); +PyObject *brightness(PyObject *img, double factor); /* in iterate.c */ PyObject *chunk_render(PyObject *self, PyObject *args); +PyObject *chunk_render_lighting(PyObject *self, PyObject *args); #endif /* __OVERVIEWER_H_INCLUDED__ */ diff --git a/world.py b/world.py index 7ed790c..8099e94 100644 --- a/world.py +++ b/world.py @@ -67,9 +67,10 @@ class World(object): mincol = maxcol = minrow = maxrow = 0 - def __init__(self, worlddir, useBiomeData=False,regionlist=None): + def __init__(self, worlddir, useBiomeData=False,regionlist=None, lighting=False): self.worlddir = worlddir self.useBiomeData = useBiomeData + self.lighting = lighting #find region files, or load the region list #this also caches all the region file header info From 29dc98ce2ab857664dfd80b4474b53a8d4251162 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 19 Mar 2011 23:49:56 -0400 Subject: [PATCH 050/213] Fix a -p 1 regression --- quadtree.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/quadtree.py b/quadtree.py index 73d982b..25855fc 100644 --- a/quadtree.py +++ b/quadtree.py @@ -375,6 +375,8 @@ class QuadtreeGen(object): # Create a pool if procs == 1: pool = FakePool() + global child_quadtree + child_quadtree = self else: pool = multiprocessing.Pool(processes=procs,initializer=pool_initializer,initargs=(self,)) #warm up the pool so it reports all the worker id's From 6f60439f14132bb5db4499a0d47f7561c60408ce Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 20 Mar 2011 00:37:34 -0400 Subject: [PATCH 051/213] accept callables in the configFile with type="function" --- configParser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/configParser.py b/configParser.py index 7ed285d..8ca9eba 100644 --- a/configParser.py +++ b/configParser.py @@ -131,6 +131,9 @@ class ConfigOptionParser(object): configResults.__dict__[n] = long(configResults.__dict__[n]) elif a['type'] == "complex": configResults.__dict__[n] = complex(configResults.__dict__[n]) + elif a['type'] == "function": + if not callable(configResults.__dict__[n]): + raise ValueError("Not callable") else: print "Unknown type!" sys.exit(1) From 4e9985c61167aa7335b353527da7002b00c34317 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 20 Mar 2011 00:46:56 -0400 Subject: [PATCH 052/213] changed web_assets_hook to use new config-file function support --- gmap.py | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/gmap.py b/gmap.py index 0dc7191..00acbde 100755 --- a/gmap.py +++ b/gmap.py @@ -62,7 +62,7 @@ def main(): parser.add_option("--rendermode", dest="rendermode", help="Specifies the render type: normal (default), lighting, night, or spawn.", type="choice", choices=["normal", "lighting", "night", "spawn"], required=True, default="normal") parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.", configFileOnly=True ) parser.add_option("--optimize-img", dest="optimizeimg", help="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%", configFileOnly=True) - parser.add_option("--web-assets-hook", dest="web_assets_hook", help="If provided, run this script after the web assets have been copied, but before actual tile rendering begins. See the README for details.", action="store", metavar="SCRIPT", type="string", configFileOnly=True) + parser.add_option("--web-assets-hook", dest="web_assets_hook", help="If provided, run this function after the web assets have been copied, but before actual tile rendering begins. It should accept a QuadtreeGen object as its only argument.", action="store", metavar="SCRIPT", type="function", configFileOnly=True) parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.") parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, help="Print more output. You can specify this option multiple times.") parser.add_option("--skip-js", dest="skipjs", action="store_true", help="Don't output marker.js or regions.js") @@ -140,21 +140,6 @@ def main(): else: optimizeimg = None - if options.web_assets_hook: - if not os.path.exists(options.web_assets_hook): - parser.error("Provided hook script does not exist!") - def web_assets_hook(quadtree): - if options.web_assets_hook == None: - return - try: - subprocess.check_call((options.web_assets_hook, os.path.abspath(quadtree.destdir))) - except OSError, e: - logging.error("could not call web assets hook: %s" % (e,)) - sys.exit(1) - except subprocess.CalledProcessError: - logging.error("web assets hook returned error") - sys.exit(1) - logging.getLogger().setLevel( logging.getLogger().level + 10*options.quiet) logging.getLogger().setLevel( @@ -176,7 +161,7 @@ def main(): # Now generate the tiles # TODO chunklist - q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode=options.rendermode, web_assets_hook=web_assets_hook) + q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode=options.rendermode, web_assets_hook=options.web_assets_hook) q.write_html(options.skipjs) q.go(options.procs) From 329c7557f644b64f6da7b3b0054dbd5d0d2c9304 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 20 Mar 2011 21:13:17 -0400 Subject: [PATCH 053/213] Move some code out of the render_loop function and into a 1-time init routine --- src/iterate.c | 56 +++++++++++++++++++++++++++++------------------- src/main.c | 6 ++++++ src/overviewer.h | 1 + 3 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index 031864c..3a12d53 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -22,6 +22,40 @@ /* macro for getting blockID from a chunk of memory */ #define getBlock(blockThing, x,y,z) (*(unsigned char *)(PyArray_GETPTR3(blockThing, (x), (y), (z)))) +static PyObject *textures = NULL; +static PyObject *chunk_mod = NULL; +static PyObject *blockmap = NULL; +static PyObject *special_blocks = NULL; +static PyObject *specialblockmap = NULL; +static PyObject *transparent_blocks = NULL; + +int init_chunk_render(void) { + + /* if blockmap (or any of these) is not NULL, then that means that we've + * somehow called this function twice. error out so we can notice this + * */ + if (blockmap) return 1; + + textures = PyImport_ImportModule("textures"); + chunk_mod = PyImport_ImportModule("chunk"); + + blockmap = PyObject_GetAttrString(textures, "blockmap"); + special_blocks = PyObject_GetAttrString(textures, "special_blocks"); + specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); + transparent_blocks = PyObject_GetAttrString(chunk_mod, "transparent_blocks"); + + /* ensure none of these pointers are NULL */ + if ((!transparent_blocks) || (!blockmap) || (!special_blocks) || (!specialblockmap)){ + fprintf(stderr, "\ninit_chunk_render failed\n"); + return 1; + } + + Py_DECREF(textures); + Py_DECREF(chunk_mod); + return 0; + +} + static inline int isTransparent(PyObject* tup, unsigned char b) { PyObject *block = PyInt_FromLong(b); int ret = PySequence_Contains(tup, block); @@ -59,8 +93,6 @@ chunk_render(PyObject *self, PyObject *args) { PyObject *blocks_py; - PyObject *textures, *blockmap, *special_blocks, *specialblockmap, *chunk_mod, *transparent_blocks; - int imgx, imgy; int x, y, z; @@ -88,23 +120,6 @@ chunk_render(PyObject *self, PyObject *args) { PyObject *right_blocks = PyObject_GetAttrString(chunk, "right_blocks"); */ - textures = PyImport_ImportModule("textures"); - chunk_mod = PyImport_ImportModule("chunk"); - - /* TODO can these be global static? these don't change during program execution */ - blockmap = PyObject_GetAttrString(textures, "blockmap"); - special_blocks = PyObject_GetAttrString(textures, "special_blocks"); - specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); - transparent_blocks = PyObject_GetAttrString(chunk_mod, "transparent_blocks"); - if (transparent_blocks == NULL) { - PyErr_SetString(PyExc_ValueError, - "transparent_blocks is NULL"); - return NULL; - } - - - Py_DECREF(textures); - Py_DECREF(chunk_mod); for (x = 15; x > -1; x--) { for (y = 0; y < 16; y++) { @@ -184,9 +199,6 @@ chunk_render(PyObject *self, PyObject *args) { } Py_DECREF(blocks_py); - Py_DECREF(blockmap); - Py_DECREF(special_blocks); - Py_DECREF(specialblockmap); return Py_BuildValue("i",2); } diff --git a/src/main.c b/src/main.c index f56fb41..8bdd91d 100644 --- a/src/main.c +++ b/src/main.c @@ -33,4 +33,10 @@ initc_overviewer(void) (void)Py_InitModule("c_overviewer", COverviewerMethods); /* for numpy */ import_array(); + + /* initialize some required variables in iterage.c */ + if (init_chunk_render()) { + fprintf(stderr, "failed to init_chunk_render\n"); + exit(1); // TODO better way to indicate error? + } } diff --git a/src/overviewer.h b/src/overviewer.h index 5746b53..fa583fc 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -36,5 +36,6 @@ PyObject *alpha_over_wrap(PyObject *self, PyObject *args); /* in iterate.c */ PyObject *chunk_render(PyObject *self, PyObject *args); +int init_chunk_render(void); #endif /* __OVERVIEWER_H_INCLUDED__ */ From 0e87368a06a683e923bc51e49b3e25e85f902d43 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 20 Mar 2011 21:23:26 -0400 Subject: [PATCH 054/213] moved google map related code int googlemap.py (from quadtree.py) basic support in googlemap.py for handling multiple QuadtreeGens --- config.js | 5 +- gmap.py | 12 +++-- googlemap.py | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++ quadtree.py | 93 +------------------------------- 4 files changed, 162 insertions(+), 95 deletions(-) create mode 100644 googlemap.py diff --git a/config.js b/config.js index c841130..9296853 100644 --- a/config.js +++ b/config.js @@ -39,13 +39,16 @@ var signGroups = [ * path : string. Location of the rendered tiles. * Optional: * base : string. Base of the url path for tile locations, useful for serving tiles from a different server than the js/html server. - */ + var mapTypeData=[ {'label': 'Unlit', 'path': 'tiles'}, // {'label': 'Day', 'path': 'lighting/tiles'}, // {'label': 'Night', 'path': 'night/tiles'}, // {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'} ]; + */ + +var mapTypeData = {maptypedata}; // Please leave the following variables here: var markerCollection = {}; // holds groups of markers diff --git a/gmap.py b/gmap.py index 00acbde..4be9d92 100755 --- a/gmap.py +++ b/gmap.py @@ -43,6 +43,7 @@ import optimizeimages import composite import world import quadtree +import googlemap helptext = """ %prog [OPTIONS] @@ -159,10 +160,15 @@ def main(): w = world.World(worlddir, useBiomeData=useBiomeData) w.go(options.procs) - # Now generate the tiles + # create the quadtrees # TODO chunklist - q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode=options.rendermode, web_assets_hook=options.web_assets_hook) - q.write_html(options.skipjs) + q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode=options.rendermode) + + # write out the map and web assets + m = googlemap.MapGen([q,], skipjs=options.skipjs, web_assets_hook=options.web_assets_hook) + m.go(options.procs) + + # render the tiles! q.go(options.procs) def delete_all(worlddir, tiledir): diff --git a/googlemap.py b/googlemap.py new file mode 100644 index 0000000..fbecc03 --- /dev/null +++ b/googlemap.py @@ -0,0 +1,147 @@ +# This file is part of the Minecraft Overviewer. +# +# Minecraft Overviewer 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. +# +# Minecraft Overviewer 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 the Overviewer. If not, see . + +import os +import os.path +import cPickle +import Image +import shutil +from time import strftime, gmtime +import json + +import util + +""" +This module has routines related to generating a Google Maps-based +interface out of a set of tiles. + +""" + +def mirror_dir(src, dst, entities=None): + '''copies all of the entities from src to dst''' + if not os.path.exists(dst): + os.mkdir(dst) + if entities and type(entities) != list: raise Exception("Expected a list, got a %r instead" % type(entities)) + + for entry in os.listdir(src): + if entities and entry not in entities: continue + if os.path.isdir(os.path.join(src,entry)): + mirror_dir(os.path.join(src, entry), os.path.join(dst, entry)) + elif os.path.isfile(os.path.join(src,entry)): + try: + shutil.copy(os.path.join(src, entry), os.path.join(dst, entry)) + except IOError: + # maybe permission problems? + os.chmod(os.path.join(src, entry), stat.S_IRUSR) + os.chmod(os.path.join(dst, entry), stat.S_IWUSR) + shutil.copy(os.path.join(src, entry), os.path.join(dst, entry)) + # if this stills throws an error, let it propagate up + +class MapGen(object): + def __init__(self, quadtrees, skipjs=False, web_assets_hook=None): + """Generates a Google Maps interface for the given list of + quadtrees. All of the quadtrees must have the same destdir, + image format, and world.""" + + self.skipjs = skipjs + self.web_assets_hook = web_assets_hook + + if not len(quadtrees) > 0: + raise ValueError("there must be at least one quadtree to work on") + + self.destdir = quadtrees[0].destdir + self.imgformat = quadtrees[0].imgformat + self.world = quadtrees[0].world + self.p = quadtrees[0].p + for i in quadtrees: + if i.destdir != self.destdir or i.imgformat != self.imgformat or i.world != self.world: + raise ValueError("all the given quadtrees must have the same destdir") + + self.quadtrees = quadtrees + + def go(self, procs): + """Writes out config.js, marker.js, and region.js + Copies web assets into the destdir""" + zoomlevel = self.p + imgformat = self.imgformat + configpath = os.path.join(util.get_program_path(), "config.js") + + config = open(configpath, 'r').read() + config = config.replace( + "{maxzoom}", str(zoomlevel)) + config = config.replace( + "{imgformat}", str(imgformat)) + + # create generated map type data, from given quadtrees + maptypedata = map(lambda q: {'label' : q.rendermode.capitalize(), + 'path' : q.tiledir}, self.quadtrees) + config = config.replace("{maptypedata}", json.dumps(maptypedata)) + + with open(os.path.join(self.destdir, "config.js"), 'w') as output: + output.write(config) + + # Write a blank image + for quadtree in self.quadtrees: + blank = Image.new("RGBA", (1,1)) + tileDir = os.path.join(self.destdir, quadtree.tiledir) + if not os.path.exists(tileDir): os.mkdir(tileDir) + blank.save(os.path.join(tileDir, "blank."+self.imgformat)) + + # copy web assets into destdir: + mirror_dir(os.path.join(util.get_program_path(), "web_assets"), self.destdir) + + # Add time in index.html + indexpath = os.path.join(self.destdir, "index.html") + + index = open(indexpath, 'r').read() + index = index.replace( + "{time}", str(strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()))) + + with open(os.path.join(self.destdir, "index.html"), 'w') as output: + output.write(index) + + if self.skipjs: + if self.web_assets_hook: + self.web_assets_hook(self) + return + + # since we will only discover PointsOfInterest in chunks that need to be + # [re]rendered, POIs like signs in unchanged chunks will not be listed + # in self.world.POI. To make sure we don't remove these from markers.js + # we need to merge self.world.POI with the persistant data in world.PersistentData + + self.world.POI += filter(lambda x: x['type'] != 'spawn', self.world.persistentData['POI']) + + # 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)) + + # save persistent data + self.world.persistentData['POI'] = self.world.POI + with open(self.world.pickleFile,"wb") as f: + cPickle.dump(self.world.persistentData,f) + + # write out the default (empty, but documented) region table + with open(os.path.join(self.destdir, "regions.js"), 'w') as output: + output.write('var regionData=[\n') + output.write(' // {"color": "#FFAA00", "opacity": 0.5, "closed": true, "path": [\n') + output.write(' // {"x": 0, "y": 0, "z": 0},\n') + output.write(' // {"x": 0, "y": 10, "z": 0},\n') + output.write(' // {"x": 0, "y": 0, "z": 10}\n') + output.write(' // ]},\n') + output.write('];') + + if self.web_assets_hook: + self.web_assets_hook(self) diff --git a/quadtree.py b/quadtree.py index 25855fc..7583b84 100644 --- a/quadtree.py +++ b/quadtree.py @@ -42,26 +42,6 @@ This module has routines related to generating a quadtree of tiles """ -def mirror_dir(src, dst, entities=None): - '''copies all of the entities from src to dst''' - if not os.path.exists(dst): - os.mkdir(dst) - if entities and type(entities) != list: raise Exception("Expected a list, got a %r instead" % type(entities)) - - for entry in os.listdir(src): - if entities and entry not in entities: continue - if os.path.isdir(os.path.join(src,entry)): - mirror_dir(os.path.join(src, entry), os.path.join(dst, entry)) - elif os.path.isfile(os.path.join(src,entry)): - try: - shutil.copy(os.path.join(src, entry), os.path.join(dst, entry)) - except IOError: - # maybe permission problems? - os.chmod(os.path.join(src, entry), stat.S_IRUSR) - os.chmod(os.path.join(dst, entry), stat.S_IWUSR) - shutil.copy(os.path.join(src, entry), os.path.join(dst, entry)) - # if this stills throws an error, let it propagate up - def iterate_base4(d): """Iterates over a base 4 number with d digits""" return itertools.product(xrange(4), repeat=d) @@ -90,7 +70,7 @@ def pool_initializer(quadtree): child_quadtree = quadtree class QuadtreeGen(object): - def __init__(self, worldobj, destdir, depth=None, tiledir="tiles", imgformat=None, optimizeimg=None, rendermode="normal", web_assets_hook=None): + def __init__(self, worldobj, destdir, depth=None, tiledir="tiles", imgformat=None, optimizeimg=None, rendermode="normal"): """Generates a quadtree from the world given into the given dest directory @@ -103,11 +83,11 @@ class QuadtreeGen(object): assert(imgformat) self.imgformat = imgformat self.optimizeimg = optimizeimg - self.web_assets_hook = web_assets_hook self.lighting = rendermode in ("lighting", "night", "spawn") self.night = rendermode in ("night", "spawn") self.spawn = rendermode in ("spawn",) + self.rendermode = rendermode # Make the destination dir if not os.path.exists(destdir): @@ -160,75 +140,6 @@ class QuadtreeGen(object): return logging.info("{0}/{1} tiles complete on level {2}/{3}".format( complete, total, level, self.p)) - - def write_html(self, skipjs=False): - """Writes out config.js, marker.js, and region.js - Copies web assets into the destdir""" - zoomlevel = self.p - imgformat = self.imgformat - configpath = os.path.join(util.get_program_path(), "config.js") - - config = open(configpath, 'r').read() - config = config.replace( - "{maxzoom}", str(zoomlevel)) - config = config.replace( - "{imgformat}", str(imgformat)) - - with open(os.path.join(self.destdir, "config.js"), 'w') as output: - output.write(config) - - # Write a blank image - blank = Image.new("RGBA", (1,1)) - tileDir = os.path.join(self.destdir, self.tiledir) - if not os.path.exists(tileDir): os.mkdir(tileDir) - blank.save(os.path.join(tileDir, "blank."+self.imgformat)) - - # copy web assets into destdir: - mirror_dir(os.path.join(util.get_program_path(), "web_assets"), self.destdir) - - # Add time in index.html - indexpath = os.path.join(self.destdir, "index.html") - - index = open(indexpath, 'r').read() - index = index.replace( - "{time}", str(strftime("%a, %d %b %Y %H:%M:%S +0000", gmtime()))) - - with open(os.path.join(self.destdir, "index.html"), 'w') as output: - output.write(index) - - if skipjs: - if self.web_assets_hook: - self.web_assets_hook(self) - return - - # since we will only discover PointsOfInterest in chunks that need to be - # [re]rendered, POIs like signs in unchanged chunks will not be listed - # in self.world.POI. To make sure we don't remove these from markers.js - # we need to merge self.world.POI with the persistant data in world.PersistentData - - self.world.POI += filter(lambda x: x['type'] != 'spawn', self.world.persistentData['POI']) - - # 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)) - - # save persistent data - self.world.persistentData['POI'] = self.world.POI - with open(self.world.pickleFile,"wb") as f: - cPickle.dump(self.world.persistentData,f) - - # write out the default (empty, but documented) region table - with open(os.path.join(self.destdir, "regions.js"), 'w') as output: - output.write('var regionData=[\n') - output.write(' // {"color": "#FFAA00", "opacity": 0.5, "closed": true, "path": [\n') - output.write(' // {"x": 0, "y": 0, "z": 0},\n') - output.write(' // {"x": 0, "y": 10, "z": 0},\n') - output.write(' // {"x": 0, "y": 0, "z": 10}\n') - output.write(' // ]},\n') - output.write('];') - - if self.web_assets_hook: - self.web_assets_hook(self) def _get_cur_depth(self): """How deep is the quadtree currently in the destdir? This glances in From 04ef95e4d692a1d8e929651d12d6290f5647d978 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 20 Mar 2011 21:41:25 -0400 Subject: [PATCH 055/213] renamed gmap.py to overviewer.py --- README.rst | 14 +++++++------- contrib/clearOldCache.py | 4 ++-- contrib/findSigns.py | 2 +- contrib/rerenderBlocks.py | 4 ++-- gmap.py => overviewer.py | 0 setup.py | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) rename gmap.py => overviewer.py (100%) diff --git a/README.rst b/README.rst index 3164ef7..5acc920 100644 --- a/README.rst +++ b/README.rst @@ -121,9 +121,9 @@ the same directory as "_composite.c". Running ------- -To generate a set of Google Map tiles, use the gmap.py script like this:: +To generate a set of Google Map tiles, use the overviewer.py script like this:: - python gmap.py [OPTIONS] + python overviewer.py [OPTIONS] The output directory will be created if it doesn't exist. This will generate a set of image tiles for your world in the directory you choose. When it's done, @@ -153,7 +153,7 @@ Options Example:: - python gmap.py --cachedir= + python overviewer.py --cachedir= --imgformat=FORMAT Set the output image format used for the tiles. The default is 'png', @@ -168,7 +168,7 @@ Options Example to run 5 worker processes in parallel:: - python gmap.py -p 5 + python overviewer.py -p 5 -z ZOOM, --zoom=ZOOM The Overviewer by default will detect how many zoom levels are required @@ -191,7 +191,7 @@ Options This will render your map with 7 zoom levels:: - python gmap.py -z 7 + python overviewer.py -z 7 Remember that each additional zoom level adds 4 times as many tiles as the last. This can add up fast, zoom level 10 has over a million tiles. @@ -210,13 +210,13 @@ Options By default, the chunk images are saved in your world directory. This example will remove them:: - python gmap.py -d + python overviewer.py -d You can also delete the tile cache as well. This will force a full re-render, useful if you've changed texture packs and want your world to look uniform. Here's an example:: - python gmap.py -d <# / path> + python overviewer.py -d <# / path> Be warned, this will cause the next rendering of your map to take significantly longer, since it is having to re-generate the files you just diff --git a/contrib/clearOldCache.py b/contrib/clearOldCache.py index 18b402b..01b604d 100644 --- a/contrib/clearOldCache.py +++ b/contrib/clearOldCache.py @@ -4,7 +4,7 @@ usage = "python contrib/%prog [OPTIONS] " description = """ This script will delete files from the old chunk-based cache, a lot -like the old `gmap.py -d World/` command. You should only use this if +like the old `overviewer.py -d World/` command. You should only use this if you're updating from an older version of Overviewer, and you want to clean up your world folder. """ @@ -19,7 +19,7 @@ overviewer_dir = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] sys.path.insert(0, overviewer_dir) import world -from gmap import list_worlds +from overviewer import list_worlds def main(): parser = OptionParser(usage=usage, description=description) diff --git a/contrib/findSigns.py b/contrib/findSigns.py index a5bfa5d..c390654 100644 --- a/contrib/findSigns.py +++ b/contrib/findSigns.py @@ -11,7 +11,7 @@ To run, simply give a path to your world directory, for example: Once that is done, simply re-run the overviewer to generate markers.js: - python gmap.py ../world.test/ output_dir/ + python overviewer.py ../world.test/ output_dir/ Note: if your cachedir is not the same as your world-dir, you'll need to manually move overviewer.dat into the correct location. diff --git a/contrib/rerenderBlocks.py b/contrib/rerenderBlocks.py index 19ab178..51940df 100644 --- a/contrib/rerenderBlocks.py +++ b/contrib/rerenderBlocks.py @@ -3,12 +3,12 @@ ''' This is used to force the regeneration of any chunks that contain a certain blockID. The output is a chunklist file that is suitable to use with the ---chunklist option to gmap.py. +--chunklist option to overviewer.py. Example: python contrib/rerenderBlocks.py --ids=46,79,91 --world=world/> chunklist.txt - python gmap.py --chunklist=chunklist.txt world/ output_dir/ + python overviewer.py --chunklist=chunklist.txt world/ output_dir/ This will rerender any chunks that contain either TNT (46), Ice (79), or a Jack-O-Lantern (91) diff --git a/gmap.py b/overviewer.py similarity index 100% rename from gmap.py rename to overviewer.py diff --git a/setup.py b/setup.py index cf12c18..48a776d 100644 --- a/setup.py +++ b/setup.py @@ -24,7 +24,7 @@ setup_kwargs['cmdclass'] = {} # if py2exe is not None: - setup_kwargs['console'] = ['gmap.py'] + setup_kwargs['console'] = ['overviewer.py'] setup_kwargs['data_files'] = [('textures', ['textures/lava.png', 'textures/water.png', 'textures/fire.png']), ('', ['config.js', 'COPYING.txt', 'README.rst']), ('web_assets', glob.glob('web_assets/*'))] From 56f6218ea8ba32593ce8d720d0f26a2e5e3c8ca7 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 20 Mar 2011 21:45:13 -0400 Subject: [PATCH 056/213] updated .gitignore to include settings.py --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 86d813a..621c845 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ build terrain.png cachedir* +# user-provided settings file +settings.py + # header files that may be copied over, if missing ImPlatform.h Imaging.h From fd0924a93bb9fd3062d804c947455f51248e15ce Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 21 Mar 2011 05:35:11 -0400 Subject: [PATCH 057/213] moved lighting check from world object to quadtree object, where it should be now --- chunk.py | 2 +- overviewer.py | 2 +- world.py | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/chunk.py b/chunk.py index 18d7eca..9a25195 100644 --- a/chunk.py +++ b/chunk.py @@ -550,7 +550,7 @@ class ChunkRenderer(object): if not img: img = Image.new("RGBA", (384, 1728), (38,92,255,0)) - if self.world.lighting: + if self.quadtree.lighting: c_overviewer.render_loop_lighting(self, img, xoff, yoff, blockData_expanded) else: c_overviewer.render_loop(self, img, xoff, yoff, blockData_expanded) diff --git a/overviewer.py b/overviewer.py index d5d17c0..4be9d92 100755 --- a/overviewer.py +++ b/overviewer.py @@ -157,7 +157,7 @@ def main(): logging.info("Notice: Not using biome data for tinting") # First do world-level preprocessing - w = world.World(worlddir, useBiomeData=useBiomeData, lighting=options.lighting) + w = world.World(worlddir, useBiomeData=useBiomeData) w.go(options.procs) # create the quadtrees diff --git a/world.py b/world.py index 97fed17..ce869cb 100644 --- a/world.py +++ b/world.py @@ -67,10 +67,9 @@ class World(object): mincol = maxcol = minrow = maxrow = 0 - def __init__(self, worlddir, useBiomeData=False,regionlist=None, lighting=False): + def __init__(self, worlddir, useBiomeData=False,regionlist=None): self.worlddir = worlddir self.useBiomeData = useBiomeData - self.lighting = lighting #find region files, or load the region list #this also caches all the region file header info From 28813240848f3d8b3ec48d6bb67e82b87904b067 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 21 Mar 2011 06:02:47 -0400 Subject: [PATCH 058/213] merged lighting and non-lighting render code --- chunk.py | 5 +- src/iterate.c | 304 +++++++++-------------------------------------- src/main.c | 2 - src/overviewer.h | 1 - 4 files changed, 59 insertions(+), 253 deletions(-) diff --git a/chunk.py b/chunk.py index 9a25195..1155f40 100644 --- a/chunk.py +++ b/chunk.py @@ -550,10 +550,7 @@ class ChunkRenderer(object): if not img: img = Image.new("RGBA", (384, 1728), (38,92,255,0)) - if self.quadtree.lighting: - c_overviewer.render_loop_lighting(self, img, xoff, yoff, blockData_expanded) - else: - c_overviewer.render_loop(self, img, xoff, yoff, blockData_expanded) + c_overviewer.render_loop(self, img, xoff, yoff, blockData_expanded) for entity in tileEntities: if entity['id'] == 'Sign': diff --git a/src/iterate.c b/src/iterate.c index 4b3a0de..a2478c1 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -19,8 +19,8 @@ #include -/* macro for getting blockID from a chunk of memory */ -#define getBlock(blockThing, x,y,z) (*(unsigned char *)(PyArray_GETPTR3(blockThing, (x), (y), (z)))) +/* macro for getting a value out of a 3D numpy byte array */ +#define getArrayByte3D(array, x,y,z) (*(unsigned char *)(PyArray_GETPTR3((array), (x), (y), (z)))) static PyObject *textures = NULL; static PyObject *chunk_mod = NULL; @@ -50,8 +50,6 @@ int init_chunk_render(void) { return 1; } - Py_DECREF(textures); - Py_DECREF(chunk_mod); return 0; } @@ -79,248 +77,37 @@ texture_alpha_over(PyObject *dest, PyObject *t, int imgx, int imgy) return alpha_over(dest, src, mask, imgx, imgy, 0, 0); } -PyObject* -chunk_render_lighting(PyObject *self, PyObject *args) { - - PyObject *chunk; - PyObject *blockdata_expanded; - int xoff, yoff; - PyObject *img; +/* shades the drawn block with the given facemask/black_color, based on the + lighting results from (x, y, z) */ +static inline void +do_shading_for_face(PyObject *chunk, int x, int y, int z, PyObject *facemask, PyObject *black_color, + PyObject *img, int imgx, int imgy) +{ + // returns new references + PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x, y, z); + PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); + double black_coeff = PyFloat_AsDouble(black_coeff_py); + Py_DECREF(black_coeff_py); - PyObject *imgsize, *imgsize0_py, *imgsize1_py; - int imgsize0, imgsize1; + PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); + int face_occlude = PyInt_AsLong(face_occlude_py); + Py_DECREF(face_occlude_py); - PyObject *blocks_py; - PyObject *textures, *blockmap, *special_blocks, *specialblockmap, *chunk_mod, *transparent_blocks; - - int imgx, imgy; - int x, y, z; - - if (!PyArg_ParseTuple(args, "OOiiO", &chunk, &img, &xoff, &yoff, &blockdata_expanded)) - return Py_BuildValue("i", "-1"); - - /* tuple */ - imgsize = PyObject_GetAttrString(img, "size"); - - imgsize0_py = PySequence_GetItem(imgsize, 0); - imgsize1_py = PySequence_GetItem(imgsize, 1); - Py_DECREF(imgsize); - - imgsize0 = PyInt_AsLong(imgsize0_py); - imgsize1 = PyInt_AsLong(imgsize1_py); - Py_DECREF(imgsize0_py); - Py_DECREF(imgsize1_py); - - - /* get the block data directly from numpy: */ - blocks_py = PyObject_GetAttrString(chunk, "blocks"); - - /* - PyObject *left_blocks = PyObject_GetAttrString(chunk, "left_blocks"); - PyObject *right_blocks = PyObject_GetAttrString(chunk, "right_blocks"); - */ - - textures = PyImport_ImportModule("textures"); - chunk_mod = PyImport_ImportModule("chunk"); - - /* TODO can these be global static? these don't change during program execution */ - blockmap = PyObject_GetAttrString(textures, "blockmap"); - special_blocks = PyObject_GetAttrString(textures, "special_blocks"); - specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); - transparent_blocks = PyObject_GetAttrString(chunk_mod, "transparent_blocks"); - if (transparent_blocks == NULL) { - PyErr_SetString(PyExc_ValueError, - "transparent_blocks is NULL"); - return NULL; - } - PyObject *black_color = PyObject_GetAttrString(chunk_mod, "black_color"); - PyObject *facemasks_py = PyObject_GetAttrString(chunk_mod, "facemasks"); - PyObject *facemasks[3]; - //assert(PyTuple_Check(facemasks_py)); - facemasks[0] = PyTuple_GetItem(facemasks_py, 0); - facemasks[1] = PyTuple_GetItem(facemasks_py, 1); - facemasks[2] = PyTuple_GetItem(facemasks_py, 2); + if (!face_occlude) { + //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) - - Py_DECREF(textures); - Py_DECREF(chunk_mod); - - - - for (x = 15; x > -1; x--) { - for (y = 0; y < 16; y++) { - imgx = xoff + x*12 + y*12; - /* 128*12 -- offset for z direction, 15*6 -- offset for x */ - imgy = yoff - x*6 + y*6 + 128*12 + 15*6; - for (z = 0; z < 128; z++) { - unsigned char block; - PyObject *blockid; - - imgy -= 12; - - if ((imgx >= imgsize0 + 24) || (imgx <= -24)) { - continue; - } - if ((imgy >= imgsize1 + 24) || (imgy <= -24)) { - continue; - } - - /* get blockid - note the order: x, z, y */ - block = getBlock(blocks_py, x, y, z); - if (block == 0) { - continue; - } - - /* TODO figure out how to DECREF this easily, instead of at - every continue */ - blockid = PyInt_FromLong(block); - - - if ( (x != 0) && (y != 15) && (z != 127) && - !isTransparent(transparent_blocks, getBlock(blocks_py, x-1, y, z)) && - !isTransparent(transparent_blocks, getBlock(blocks_py, x, y, z+1)) && - !isTransparent(transparent_blocks, getBlock(blocks_py, x, y+1, z))) { - continue; - } - - //# top face - //black_coeff, face_occlude = self.get_lighting_coefficient(x, y, z + 1) - - - PyObject *t = NULL; - if (!PySequence_Contains(special_blocks, blockid)) { - /* t = textures.blockmap[blockid] */ - PyObject *t = PyList_GetItem(blockmap, block); - /* PyList_GetItem returns borrowed ref */ - - /* note that this version of alpha_over has a different signature than the - version in _composite.c */ - texture_alpha_over(img, t, imgx, imgy ); - } else { - PyObject *tmp; - - /* this should be a pointer to a unsigned char */ - void* ancilData_p = PyArray_GETPTR3(blockdata_expanded, x, y, z); - unsigned char ancilData = *((unsigned char*)ancilData_p); - if (block == 85) { - /* fence. skip the generate_pseudo_ancildata for now */ - continue; - } - - tmp = PyTuple_New(2); - - Py_INCREF(blockid); /* because SetItem steals */ - PyTuple_SetItem(tmp, 0, blockid); - PyTuple_SetItem(tmp, 1, PyInt_FromLong(ancilData)); - - /* this is a borrowed reference. no need to decref */ - t = PyDict_GetItem(specialblockmap, tmp); - Py_DECREF(tmp); - - } - if ((t != NULL) && (t != Py_None)) - texture_alpha_over(img, t, imgx, imgy ); - - { - // returns new references - PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x, y, z+1); - PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); - double black_coeff = PyFloat_AsDouble(black_coeff_py); - Py_DECREF(black_coeff_py); - - PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); - int face_occlude = PyInt_AsLong(face_occlude_py); - Py_DECREF(face_occlude_py); - - - if (!face_occlude) { - //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) - - PyObject *mask = PyObject_CallMethod(facemasks[0], "copy", NULL); // new ref - //printf("black_coeff: %f\n", black_coeff); - brightness(mask, black_coeff); - //printf("done with brightness\n"); - alpha_over(img, black_color, mask, imgx, imgy, 0, 0); - //printf("done with alpha_over\n"); - Py_DECREF(mask); - - } - } - - - { - // returns new references - PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x-1, y, z); - PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); - double black_coeff = PyFloat_AsDouble(black_coeff_py); - Py_DECREF(black_coeff_py); - - PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); - int face_occlude = PyInt_AsLong(face_occlude_py); - Py_DECREF(face_occlude_py); - - - if (!face_occlude) { - //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) - /* for now, we are lazy and call Image.blend to change the brightness of facemask - * TODO find some faster way? - */ - - PyObject *mask = PyObject_CallMethod(facemasks[1], "copy", NULL); // new ref - //printf("black_coeff: %f\n", black_coeff); - brightness(mask, black_coeff); - //printf("done with brightness\n"); - alpha_over(img, black_color, mask, imgx, imgy, 0, 0); - //printf("done with alpha_over\n"); - Py_DECREF(mask); - - } - } - - - { - // returns new references - PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x, y+1, z); - PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); - double black_coeff = PyFloat_AsDouble(black_coeff_py); - Py_DECREF(black_coeff_py); - - PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); - int face_occlude = PyInt_AsLong(face_occlude_py); - Py_DECREF(face_occlude_py); - - - if (!face_occlude) { - //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) - - PyObject *mask = PyObject_CallMethod(facemasks[2], "copy", NULL); // new ref - //printf("black_coeff: %f\n", black_coeff); - brightness(mask, black_coeff); - //printf("done with brightness\n"); - alpha_over(img, black_color, mask, imgx, imgy, 0, 0); - //printf("done with alpha_over\n"); - Py_DECREF(mask); - - } - } - - - - } - } - } - - Py_DECREF(facemasks_py); - Py_DECREF(blocks_py); - Py_DECREF(blockmap); - Py_DECREF(special_blocks); - Py_DECREF(specialblockmap); - - return Py_BuildValue("i",2); - + PyObject *mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref + //printf("black_coeff: %f\n", black_coeff); + brightness(mask, black_coeff); + //printf("done with brightness\n"); + alpha_over(img, black_color, mask, imgx, imgy, 0, 0); + //printf("done with alpha_over\n"); + Py_DECREF(mask); + + } } + /* TODO triple check this to make sure reference counting is correct */ PyObject* @@ -363,6 +150,19 @@ chunk_render(PyObject *self, PyObject *args) { PyObject *right_blocks = PyObject_GetAttrString(chunk, "right_blocks"); */ + PyObject *quadtree = PyObject_GetAttrString(chunk, "quadtree"); + PyObject *lighting_py = PyObject_GetAttrString(quadtree, "lighting"); + int lighting = PyObject_IsTrue(lighting_py); + Py_DECREF(lighting_py); + Py_DECREF(quadtree); + + PyObject *black_color = PyObject_GetAttrString(chunk_mod, "black_color"); + PyObject *facemasks_py = PyObject_GetAttrString(chunk_mod, "facemasks"); + PyObject *facemasks[3]; + // borrowed references, don't need to be decref'd + facemasks[0] = PyTuple_GetItem(facemasks_py, 0); + facemasks[1] = PyTuple_GetItem(facemasks_py, 1); + facemasks[2] = PyTuple_GetItem(facemasks_py, 2); for (x = 15; x > -1; x--) { for (y = 0; y < 16; y++) { @@ -384,7 +184,7 @@ chunk_render(PyObject *self, PyObject *args) { /* get blockid note the order: x, z, y */ - block = getBlock(blocks_py, x, y, z); + block = getArrayByte3D(blocks_py, x, y, z); if (block == 0) { continue; } @@ -395,9 +195,9 @@ chunk_render(PyObject *self, PyObject *args) { if ( (x != 0) && (y != 15) && (z != 127) && - !isTransparent(transparent_blocks, getBlock(blocks_py, x-1, y, z)) && - !isTransparent(transparent_blocks, getBlock(blocks_py, x, y, z+1)) && - !isTransparent(transparent_blocks, getBlock(blocks_py, x, y+1, z))) { + !isTransparent(transparent_blocks, getArrayByte3D(blocks_py, x-1, y, z)) && + !isTransparent(transparent_blocks, getArrayByte3D(blocks_py, x, y, z+1)) && + !isTransparent(transparent_blocks, getArrayByte3D(blocks_py, x, y+1, z))) { continue; } @@ -435,12 +235,24 @@ chunk_render(PyObject *self, PyObject *args) { Py_DECREF(tmp); if (t != NULL) texture_alpha_over(img, t, imgx, imgy ); - continue; + } + + if (lighting) + { + // FIXME whole-block shading for transparent blocks + do_shading_for_face(chunk, x, y, z+1, facemasks[0], black_color, + img, imgx, imgy); + do_shading_for_face(chunk, x-1, y, z, facemasks[1], black_color, + img, imgx, imgy); + do_shading_for_face(chunk, x, y+1, z, facemasks[2], black_color, + img, imgx, imgy); } } } } + Py_DECREF(black_color); + Py_DECREF(facemasks_py); Py_DECREF(blocks_py); return Py_BuildValue("i",2); diff --git a/src/main.c b/src/main.c index 3157b8a..8bdd91d 100644 --- a/src/main.c +++ b/src/main.c @@ -24,8 +24,6 @@ static PyMethodDef COverviewerMethods[] = { "alpha over composite function"}, {"render_loop", chunk_render, METH_VARARGS, "Renders stuffs"}, - {"render_loop_lighting", chunk_render_lighting, METH_VARARGS, - "Renders stuffs, lighting"}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/src/overviewer.h b/src/overviewer.h index 903fcde..1321481 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -37,7 +37,6 @@ PyObject *brightness(PyObject *img, double factor); /* in iterate.c */ PyObject *chunk_render(PyObject *self, PyObject *args); -PyObject *chunk_render_lighting(PyObject *self, PyObject *args); int init_chunk_render(void); #endif /* __OVERVIEWER_H_INCLUDED__ */ From 88d5f151317f93a5b1586babbdcb97f563fe465b Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 21 Mar 2011 06:05:06 -0400 Subject: [PATCH 059/213] fixed blockid memory leak --- src/iterate.c | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index a2478c1..34bd1ed 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -166,12 +166,13 @@ chunk_render(PyObject *self, PyObject *args) { for (x = 15; x > -1; x--) { for (y = 0; y < 16; y++) { + PyObject *blockid = NULL; + imgx = xoff + x*12 + y*12; /* 128*12 -- offset for z direction, 15*6 -- offset for x */ imgy = yoff - x*6 + y*6 + 128*12 + 15*6; for (z = 0; z < 128; z++) { unsigned char block; - PyObject *blockid; imgy -= 12; @@ -189,8 +190,11 @@ chunk_render(PyObject *self, PyObject *args) { continue; } - /* TODO figure out how to DECREF this easily, instead of at - every continue */ + /* decref'd on replacement *and* at the end of the z for block */ + if (blockid) + { + Py_DECREF(blockid); + } blockid = PyInt_FromLong(block); @@ -248,6 +252,12 @@ chunk_render(PyObject *self, PyObject *args) { img, imgx, imgy); } } + + if (blockid) + { + Py_DECREF(blockid); + blockid = NULL; + } } } From cd34d6052bb9dc01dc2be23dda1a7561bcef8dac Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 21 Mar 2011 06:18:51 -0400 Subject: [PATCH 060/213] renamed is_transparent (more like other function names), and exposed it to other C files --- src/iterate.c | 9 +++++---- src/overviewer.h | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index 34bd1ed..fef0053 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -54,7 +54,8 @@ int init_chunk_render(void) { } -static inline int isTransparent(PyObject* tup, unsigned char b) { +inline int +is_transparent(PyObject* tup, unsigned char b) { PyObject *block = PyInt_FromLong(b); int ret = PySequence_Contains(tup, block); Py_DECREF(block); @@ -199,9 +200,9 @@ chunk_render(PyObject *self, PyObject *args) { if ( (x != 0) && (y != 15) && (z != 127) && - !isTransparent(transparent_blocks, getArrayByte3D(blocks_py, x-1, y, z)) && - !isTransparent(transparent_blocks, getArrayByte3D(blocks_py, x, y, z+1)) && - !isTransparent(transparent_blocks, getArrayByte3D(blocks_py, x, y+1, z))) { + !is_transparent(transparent_blocks, getArrayByte3D(blocks_py, x-1, y, z)) && + !is_transparent(transparent_blocks, getArrayByte3D(blocks_py, x, y, z+1)) && + !is_transparent(transparent_blocks, getArrayByte3D(blocks_py, x, y+1, z))) { continue; } diff --git a/src/overviewer.h b/src/overviewer.h index 1321481..8e148ca 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -36,6 +36,7 @@ PyObject *alpha_over_wrap(PyObject *self, PyObject *args); PyObject *brightness(PyObject *img, double factor); /* in iterate.c */ +int is_transparent(PyObject* tup, unsigned char b); PyObject *chunk_render(PyObject *self, PyObject *args); int init_chunk_render(void); From da58be569f9354bdc8f1d019af7503c5f9712077 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 21 Mar 2011 06:21:55 -0400 Subject: [PATCH 061/213] C code style fixes for lighting/normal code merge (woops...) --- src/iterate.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index fef0053..459420b 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -45,7 +45,7 @@ int init_chunk_render(void) { transparent_blocks = PyObject_GetAttrString(chunk_mod, "transparent_blocks"); /* ensure none of these pointers are NULL */ - if ((!transparent_blocks) || (!blockmap) || (!special_blocks) || (!specialblockmap)){ + if ((!transparent_blocks) || (!blockmap) || (!special_blocks) || (!specialblockmap)) { fprintf(stderr, "\ninit_chunk_render failed\n"); return 1; } @@ -192,8 +192,7 @@ chunk_render(PyObject *self, PyObject *args) { } /* decref'd on replacement *and* at the end of the z for block */ - if (blockid) - { + if (blockid) { Py_DECREF(blockid); } blockid = PyInt_FromLong(block); @@ -242,8 +241,7 @@ chunk_render(PyObject *self, PyObject *args) { texture_alpha_over(img, t, imgx, imgy ); } - if (lighting) - { + if (lighting) { // FIXME whole-block shading for transparent blocks do_shading_for_face(chunk, x, y, z+1, facemasks[0], black_color, img, imgx, imgy); @@ -254,8 +252,7 @@ chunk_render(PyObject *self, PyObject *args) { } } - if (blockid) - { + if (blockid) { Py_DECREF(blockid); blockid = NULL; } From 7b7f97d6c5621096d721eb494e2f1c961ad09fc8 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 21 Mar 2011 06:40:37 -0400 Subject: [PATCH 062/213] moved important render state into a struct, to prepare for modular render modes (making the code look worse, so it can look better!) --- src/iterate.c | 79 +++++++++++++++++++++++------------------------- src/overviewer.h | 16 ++++++++++ 2 files changed, 54 insertions(+), 41 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index 459420b..5054a08 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -113,25 +113,25 @@ do_shading_for_face(PyObject *chunk, int x, int y, int z, PyObject *facemask, Py /* TODO triple check this to make sure reference counting is correct */ PyObject* chunk_render(PyObject *self, PyObject *args) { + RenderState state; - PyObject *chunk; PyObject *blockdata_expanded; int xoff, yoff; - PyObject *img; PyObject *imgsize, *imgsize0_py, *imgsize1_py; int imgsize0, imgsize1; PyObject *blocks_py; - int imgx, imgy; - int x, y, z; - - if (!PyArg_ParseTuple(args, "OOiiO", &chunk, &img, &xoff, &yoff, &blockdata_expanded)) + if (!PyArg_ParseTuple(args, "OOiiO", &state.self, &state.img, &xoff, &yoff, &blockdata_expanded)) return Py_BuildValue("i", "-1"); + + /* fill in important modules */ + state.textures = textures; + state.chunk = chunk_mod; /* tuple */ - imgsize = PyObject_GetAttrString(img, "size"); + imgsize = PyObject_GetAttrString(state.img, "size"); imgsize0_py = PySequence_GetItem(imgsize, 0); imgsize1_py = PySequence_GetItem(imgsize, 1); @@ -144,50 +144,47 @@ chunk_render(PyObject *self, PyObject *args) { /* get the block data directly from numpy: */ - blocks_py = PyObject_GetAttrString(chunk, "blocks"); + blocks_py = PyObject_GetAttrString(state.self, "blocks"); /* PyObject *left_blocks = PyObject_GetAttrString(chunk, "left_blocks"); PyObject *right_blocks = PyObject_GetAttrString(chunk, "right_blocks"); */ - PyObject *quadtree = PyObject_GetAttrString(chunk, "quadtree"); + PyObject *quadtree = PyObject_GetAttrString(state.self, "quadtree"); PyObject *lighting_py = PyObject_GetAttrString(quadtree, "lighting"); int lighting = PyObject_IsTrue(lighting_py); Py_DECREF(lighting_py); Py_DECREF(quadtree); - PyObject *black_color = PyObject_GetAttrString(chunk_mod, "black_color"); - PyObject *facemasks_py = PyObject_GetAttrString(chunk_mod, "facemasks"); + PyObject *black_color = PyObject_GetAttrString(state.chunk, "black_color"); + PyObject *facemasks_py = PyObject_GetAttrString(state.chunk, "facemasks"); PyObject *facemasks[3]; // borrowed references, don't need to be decref'd facemasks[0] = PyTuple_GetItem(facemasks_py, 0); facemasks[1] = PyTuple_GetItem(facemasks_py, 1); facemasks[2] = PyTuple_GetItem(facemasks_py, 2); - for (x = 15; x > -1; x--) { - for (y = 0; y < 16; y++) { + for (state.x = 15; state.x > -1; state.x--) { + for (state.y = 0; state.y < 16; state.y++) { PyObject *blockid = NULL; - imgx = xoff + x*12 + y*12; + state.imgx = xoff + state.x*12 + state.y*12; /* 128*12 -- offset for z direction, 15*6 -- offset for x */ - imgy = yoff - x*6 + y*6 + 128*12 + 15*6; - for (z = 0; z < 128; z++) { - unsigned char block; + state.imgy = yoff - state.x*6 + state.y*6 + 128*12 + 15*6; + for (state.z = 0; state.z < 128; state.z++) { + state.imgy -= 12; - imgy -= 12; - - if ((imgx >= imgsize0 + 24) || (imgx <= -24)) { + if ((state.imgx >= imgsize0 + 24) || (state.imgx <= -24)) { continue; } - if ((imgy >= imgsize1 + 24) || (imgy <= -24)) { + if ((state.imgy >= imgsize1 + 24) || (state.imgy <= -24)) { continue; } - /* get blockid - note the order: x, z, y */ - block = getArrayByte3D(blocks_py, x, y, z); - if (block == 0) { + /* get blockid */ + state.block = getArrayByte3D(blocks_py, state.x, state.y, state.z); + if (state.block == 0) { continue; } @@ -195,20 +192,20 @@ chunk_render(PyObject *self, PyObject *args) { if (blockid) { Py_DECREF(blockid); } - blockid = PyInt_FromLong(block); + blockid = PyInt_FromLong(state.block); - if ( (x != 0) && (y != 15) && (z != 127) && - !is_transparent(transparent_blocks, getArrayByte3D(blocks_py, x-1, y, z)) && - !is_transparent(transparent_blocks, getArrayByte3D(blocks_py, x, y, z+1)) && - !is_transparent(transparent_blocks, getArrayByte3D(blocks_py, x, y+1, z))) { + if ( (state.x != 0) && (state.y != 15) && (state.z != 127) && + !is_transparent(transparent_blocks, getArrayByte3D(blocks_py, state.x-1, state.y, state.z)) && + !is_transparent(transparent_blocks, getArrayByte3D(blocks_py, state.x, state.y, state.z+1)) && + !is_transparent(transparent_blocks, getArrayByte3D(blocks_py, state.x, state.y+1, state.z))) { continue; } if (!PySequence_Contains(special_blocks, blockid)) { /* t = textures.blockmap[blockid] */ - PyObject *t = PyList_GetItem(blockmap, block); + PyObject *t = PyList_GetItem(blockmap, state.block); /* PyList_GetItem returns borrowed ref */ if (t == Py_None) { continue; @@ -216,14 +213,14 @@ chunk_render(PyObject *self, PyObject *args) { /* note that this version of alpha_over has a different signature than the version in _composite.c */ - texture_alpha_over(img, t, imgx, imgy ); + texture_alpha_over(state.img, t, state.imgx, state.imgy ); } else { PyObject *tmp, *t; /* this should be a pointer to a unsigned char */ - void* ancilData_p = PyArray_GETPTR3(blockdata_expanded, x, y, z); + void* ancilData_p = PyArray_GETPTR3(blockdata_expanded, state.x, state.y, state.z); unsigned char ancilData = *((unsigned char*)ancilData_p); - if (block == 85) { + if (state.block == 85) { /* fence. skip the generate_pseudo_ancildata for now */ continue; } @@ -238,17 +235,17 @@ chunk_render(PyObject *self, PyObject *args) { t = PyDict_GetItem(specialblockmap, tmp); Py_DECREF(tmp); if (t != NULL) - texture_alpha_over(img, t, imgx, imgy ); + texture_alpha_over(state.img, t, state.imgx, state.imgy ); } if (lighting) { // FIXME whole-block shading for transparent blocks - do_shading_for_face(chunk, x, y, z+1, facemasks[0], black_color, - img, imgx, imgy); - do_shading_for_face(chunk, x-1, y, z, facemasks[1], black_color, - img, imgx, imgy); - do_shading_for_face(chunk, x, y+1, z, facemasks[2], black_color, - img, imgx, imgy); + do_shading_for_face(state.self, state.x, state.y, state.z+1, facemasks[0], black_color, + state.img, state.imgx, state.imgy); + do_shading_for_face(state.self, state.x-1, state.y, state.z, facemasks[1], black_color, + state.img, state.imgx, state.imgy); + do_shading_for_face(state.self, state.x, state.y+1, state.z, facemasks[2], black_color, + state.img, state.imgx, state.imgy); } } diff --git a/src/overviewer.h b/src/overviewer.h index 8e148ca..00dc0ae 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -36,6 +36,22 @@ PyObject *alpha_over_wrap(PyObject *self, PyObject *args); PyObject *brightness(PyObject *img, double factor); /* in iterate.c */ +typedef struct { + /* the ChunkRenderer object */ + PyObject *self; + + /* important modules, for convenience */ + PyObject *textures; + PyObject *chunk; + + /* the tile image and destination */ + PyObject *img; + int imgx, imgy; + + /* the block position and type */ + int x, y, z; + unsigned char block; +} RenderState; int is_transparent(PyObject* tup, unsigned char b); PyObject *chunk_render(PyObject *self, PyObject *args); int init_chunk_render(void); From b908c6e07ce3a267f75a1944d80c0e25a3da7dc6 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 21 Mar 2011 07:40:14 -0400 Subject: [PATCH 063/213] separated iterate and rendermode code, rendermodes are now defined by interface structs --- setup.py | 2 +- src/iterate.c | 150 ++++++++++++++++------------------------------ src/main.c | 2 - src/overviewer.h | 27 +++++++-- src/rendermodes.c | 130 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 204 insertions(+), 107 deletions(-) create mode 100644 src/rendermodes.c diff --git a/setup.py b/setup.py index 48a776d..d918bd7 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ except AttributeError: numpy_include = numpy.get_numpy_include() -c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c'] +c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/rendermodes.c'] setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) # tell build_ext to build the extension in-place # (NOT in build/) diff --git a/src/iterate.c b/src/iterate.c index 5054a08..f0e962f 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -17,10 +17,8 @@ #include "overviewer.h" -#include - -/* macro for getting a value out of a 3D numpy byte array */ -#define getArrayByte3D(array, x,y,z) (*(unsigned char *)(PyArray_GETPTR3((array), (x), (y), (z)))) +/* available render modes */ +extern RenderModeInterface rendermode_normal; static PyObject *textures = NULL; static PyObject *chunk_mod = NULL; @@ -55,61 +53,14 @@ int init_chunk_render(void) { } inline int -is_transparent(PyObject* tup, unsigned char b) { +is_transparent(unsigned char b) { PyObject *block = PyInt_FromLong(b); - int ret = PySequence_Contains(tup, block); + int ret = PySequence_Contains(transparent_blocks, block); Py_DECREF(block); return ret; } -/* helper to handle alpha_over calls involving a texture tuple */ -static inline PyObject * -texture_alpha_over(PyObject *dest, PyObject *t, int imgx, int imgy) -{ - PyObject* src, * mask; - - src = PyTuple_GET_ITEM(t, 0); - mask = PyTuple_GET_ITEM(t, 1); - if (mask == Py_None) { - mask = src; - } - - return alpha_over(dest, src, mask, imgx, imgy, 0, 0); -} - -/* shades the drawn block with the given facemask/black_color, based on the - lighting results from (x, y, z) */ -static inline void -do_shading_for_face(PyObject *chunk, int x, int y, int z, PyObject *facemask, PyObject *black_color, - PyObject *img, int imgx, int imgy) -{ - // returns new references - PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x, y, z); - PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); - double black_coeff = PyFloat_AsDouble(black_coeff_py); - Py_DECREF(black_coeff_py); - - PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); - int face_occlude = PyInt_AsLong(face_occlude_py); - Py_DECREF(face_occlude_py); - - - if (!face_occlude) { - //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) - - PyObject *mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref - //printf("black_coeff: %f\n", black_coeff); - brightness(mask, black_coeff); - //printf("done with brightness\n"); - alpha_over(img, black_color, mask, imgx, imgy, 0, 0); - //printf("done with alpha_over\n"); - Py_DECREF(mask); - - } -} - - /* TODO triple check this to make sure reference counting is correct */ PyObject* chunk_render(PyObject *self, PyObject *args) { @@ -129,8 +80,19 @@ chunk_render(PyObject *self, PyObject *args) { /* fill in important modules */ state.textures = textures; state.chunk = chunk_mod; + + /* set up the render mode */ + /* FIXME deciding on correct rendermode */ + RenderModeInterface *rendermode = &rendermode_normal; + void* rm_data = malloc(rendermode->data_size); + if (rendermode->start) { + if (rendermode->start(rm_data, &state)) { + free(rm_data); + return Py_BuildValue("i", "-1"); + } + } - /* tuple */ + /* get the image size */ imgsize = PyObject_GetAttrString(state.img, "size"); imgsize0_py = PySequence_GetItem(imgsize, 0); @@ -145,36 +107,26 @@ chunk_render(PyObject *self, PyObject *args) { /* get the block data directly from numpy: */ blocks_py = PyObject_GetAttrString(state.self, "blocks"); + state.blocks = blocks_py; /* PyObject *left_blocks = PyObject_GetAttrString(chunk, "left_blocks"); PyObject *right_blocks = PyObject_GetAttrString(chunk, "right_blocks"); */ - PyObject *quadtree = PyObject_GetAttrString(state.self, "quadtree"); - PyObject *lighting_py = PyObject_GetAttrString(quadtree, "lighting"); - int lighting = PyObject_IsTrue(lighting_py); - Py_DECREF(lighting_py); - Py_DECREF(quadtree); - - PyObject *black_color = PyObject_GetAttrString(state.chunk, "black_color"); - PyObject *facemasks_py = PyObject_GetAttrString(state.chunk, "facemasks"); - PyObject *facemasks[3]; - // borrowed references, don't need to be decref'd - facemasks[0] = PyTuple_GetItem(facemasks_py, 0); - facemasks[1] = PyTuple_GetItem(facemasks_py, 1); - facemasks[2] = PyTuple_GetItem(facemasks_py, 2); - for (state.x = 15; state.x > -1; state.x--) { for (state.y = 0; state.y < 16; state.y++) { PyObject *blockid = NULL; + /* set up the render coordinates */ state.imgx = xoff + state.x*12 + state.y*12; /* 128*12 -- offset for z direction, 15*6 -- offset for x */ state.imgy = yoff - state.x*6 + state.y*6 + 128*12 + 15*6; + for (state.z = 0; state.z < 128; state.z++) { state.imgy -= 12; + /* make sure we're rendering inside the image boundaries */ if ((state.imgx >= imgsize0 + 24) || (state.imgx <= -24)) { continue; } @@ -194,28 +146,21 @@ chunk_render(PyObject *self, PyObject *args) { } blockid = PyInt_FromLong(state.block); - - if ( (state.x != 0) && (state.y != 15) && (state.z != 127) && - !is_transparent(transparent_blocks, getArrayByte3D(blocks_py, state.x-1, state.y, state.z)) && - !is_transparent(transparent_blocks, getArrayByte3D(blocks_py, state.x, state.y, state.z+1)) && - !is_transparent(transparent_blocks, getArrayByte3D(blocks_py, state.x, state.y+1, state.z))) { - continue; - } - - - if (!PySequence_Contains(special_blocks, blockid)) { - /* t = textures.blockmap[blockid] */ - PyObject *t = PyList_GetItem(blockmap, state.block); - /* PyList_GetItem returns borrowed ref */ - if (t == Py_None) { + // check for occlusion + if (rendermode->occluded) { + if (rendermode->occluded(rm_data, &state)) { continue; } - - /* note that this version of alpha_over has a different signature than the - version in _composite.c */ - texture_alpha_over(state.img, t, state.imgx, state.imgy ); + } + + // everything stored here will be a borrowed ref + PyObject *t = NULL; + /* get the texture and mask from block type / ancil. data */ + if (!PySequence_Contains(special_blocks, blockid)) { + /* t = textures.blockmap[blockid] */ + t = PyList_GetItem(blockmap, state.block); } else { - PyObject *tmp, *t; + PyObject *tmp; /* this should be a pointer to a unsigned char */ void* ancilData_p = PyArray_GETPTR3(blockdata_expanded, state.x, state.y, state.z); @@ -234,19 +179,21 @@ chunk_render(PyObject *self, PyObject *args) { /* this is a borrowed reference. no need to decref */ t = PyDict_GetItem(specialblockmap, tmp); Py_DECREF(tmp); - if (t != NULL) - texture_alpha_over(state.img, t, state.imgx, state.imgy ); } - if (lighting) { - // FIXME whole-block shading for transparent blocks - do_shading_for_face(state.self, state.x, state.y, state.z+1, facemasks[0], black_color, - state.img, state.imgx, state.imgy); - do_shading_for_face(state.self, state.x-1, state.y, state.z, facemasks[1], black_color, - state.img, state.imgx, state.imgy); - do_shading_for_face(state.self, state.x, state.y+1, state.z, facemasks[2], black_color, - state.img, state.imgx, state.imgy); - } + /* if we found a proper texture, render it! */ + if (t != NULL && t != Py_None) + { + PyObject *src, *mask; + src = PyTuple_GetItem(t, 0); + mask = PyTuple_GetItem(t, 1); + + if (mask == Py_None) + mask = src; + + if (rendermode->draw) + rendermode->draw(rm_data, &state, src, mask); + } } if (blockid) { @@ -256,8 +203,11 @@ chunk_render(PyObject *self, PyObject *args) { } } - Py_DECREF(black_color); - Py_DECREF(facemasks_py); + /* free up the rendermode info */ + if (rendermode->finish) + rendermode->finish(rm_data, &state); + free(rm_data); + Py_DECREF(blocks_py); return Py_BuildValue("i",2); diff --git a/src/main.c b/src/main.c index 8bdd91d..32ca491 100644 --- a/src/main.c +++ b/src/main.c @@ -17,8 +17,6 @@ #include "overviewer.h" -#include - static PyMethodDef COverviewerMethods[] = { {"alpha_over", alpha_over_wrap, METH_VARARGS, "alpha over composite function"}, diff --git a/src/overviewer.h b/src/overviewer.h index 00dc0ae..6c6fe02 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -24,9 +24,13 @@ #ifndef __OVERVIEWER_H_INCLUDED__ #define __OVERVIEWER_H_INCLUDED__ -/* Python and PIL headers */ +/* Python PIL, and numpy headers */ #include #include +#include + +/* macro for getting a value out of a 3D numpy byte array */ +#define getArrayByte3D(array, x,y,z) (*(unsigned char *)(PyArray_GETPTR3((array), (x), (y), (z)))) /* in composite.c */ Imaging imaging_python_to_c(PyObject *obj); @@ -44,16 +48,31 @@ typedef struct { PyObject *textures; PyObject *chunk; + /* the rest only make sense for occluded() and draw() !! */ + /* the tile image and destination */ PyObject *img; int imgx, imgy; - /* the block position and type */ + /* the block position and type, and the block array */ int x, y, z; unsigned char block; + PyObject *blocks; } RenderState; -int is_transparent(PyObject* tup, unsigned char b); -PyObject *chunk_render(PyObject *self, PyObject *args); +typedef struct { + /* the size of the local storage for this rendermode */ + unsigned int data_size; + + /* may return non-zero on error */ + int (*start)(void *, RenderState *); + void (*finish)(void *, RenderState *); + /* returns non-zero to skip rendering this block */ + int (*occluded)(void *, RenderState *); + /* last two arguments are img and mask, from texture lookup */ + void (*draw)(void *, RenderState *, PyObject *, PyObject *); +} RenderModeInterface; int init_chunk_render(void); +int is_transparent(unsigned char b); +PyObject *chunk_render(PyObject *self, PyObject *args); #endif /* __OVERVIEWER_H_INCLUDED__ */ diff --git a/src/rendermodes.c b/src/rendermodes.c new file mode 100644 index 0000000..dd08ed0 --- /dev/null +++ b/src/rendermodes.c @@ -0,0 +1,130 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + +#include "overviewer.h" + +/* shades the drawn block with the given facemask/black_color, based on the + lighting results from (x, y, z) */ +static inline void +do_shading_for_face(PyObject *chunk, int x, int y, int z, PyObject *facemask, PyObject *black_color, + PyObject *img, int imgx, int imgy) +{ + // returns new references + PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x, y, z); + PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); + double black_coeff = PyFloat_AsDouble(black_coeff_py); + Py_DECREF(black_coeff_py); + + PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); + int face_occlude = PyInt_AsLong(face_occlude_py); + Py_DECREF(face_occlude_py); + + + if (!face_occlude) { + //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) + + PyObject *mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref + //printf("black_coeff: %f\n", black_coeff); + brightness(mask, black_coeff); + //printf("done with brightness\n"); + alpha_over(img, black_color, mask, imgx, imgy, 0, 0); + //printf("done with alpha_over\n"); + Py_DECREF(mask); + + } +} + +typedef struct { + int lighting; + PyObject *black_color, *facemasks_py; + PyObject *facemasks[3]; +} RenderModeNormal; + +static int +rendermode_normal_start(void *data, RenderState *state) { + RenderModeNormal* self = (RenderModeNormal *)data; + + PyObject *quadtree = PyObject_GetAttrString(state->self, "quadtree"); + PyObject *lighting_py = PyObject_GetAttrString(quadtree, "lighting"); + self->lighting = PyObject_IsTrue(lighting_py); + Py_DECREF(lighting_py); + Py_DECREF(quadtree); + + self->black_color = PyObject_GetAttrString(state->chunk, "black_color"); + self->facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks"); + // borrowed references, don't need to be decref'd + self->facemasks[0] = PyTuple_GetItem(self->facemasks_py, 0); + self->facemasks[1] = PyTuple_GetItem(self->facemasks_py, 1); + self->facemasks[2] = PyTuple_GetItem(self->facemasks_py, 2); + + return 0; +} + +static void +rendermode_normal_finish(void *data, RenderState *state) { + RenderModeNormal *self = (RenderModeNormal *)data; + + Py_DECREF(self->black_color); + Py_DECREF(self->facemasks_py); +} + +static int +rendermode_normal_occluded(void *data, RenderState *state) { + int x = state->x, y = state->y, z = state->z; + + if ( (x != 0) && (y != 15) && (z != 127) && + !is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) && + !is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) && + !is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) { + return 1; + } + + return 0; +} + +static void +rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) +{ + RenderModeNormal* self = (RenderModeNormal *)data; + + PyObject *chunk = state->self; + int x = state->x, y = state->y, z = state->z; + PyObject **facemasks = self->facemasks; + PyObject *black_color = self->black_color, *img = state->img; + int imgx = state->imgx, imgy = state->imgy; + + alpha_over(img, src, mask, imgx, imgy, 0, 0); + + if (self->lighting) { + // FIXME whole-block shading for transparent blocks + do_shading_for_face(chunk, x, y, z+1, facemasks[0], black_color, + img, imgx, imgy); + do_shading_for_face(chunk, x-1, y, z, facemasks[1], black_color, + img, imgx, imgy); + do_shading_for_face(chunk, x, y+1, z, facemasks[2], black_color, + img, imgx, imgy); + } + +} + +RenderModeInterface rendermode_normal = { + sizeof(RenderModeNormal), + rendermode_normal_start, + rendermode_normal_finish, + rendermode_normal_occluded, + rendermode_normal_draw, +}; From 1872ccd690b5de0b01e8e85363cb5449b26b9109 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Mon, 21 Mar 2011 09:00:08 -0400 Subject: [PATCH 064/213] remove swap files from repo, updated .gitingore to ignore them how did they get there :/ --- .gitignore | 3 +++ src/.composite.c.swp | Bin 24576 -> 0 bytes src/.iterate.c.swp | Bin 32768 -> 0 bytes 3 files changed, 3 insertions(+) delete mode 100644 src/.composite.c.swp delete mode 100644 src/.iterate.c.swp diff --git a/.gitignore b/.gitignore index 621c845..5871f26 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ build terrain.png cachedir* +# vim swap files +.*.swp + # user-provided settings file settings.py diff --git a/src/.composite.c.swp b/src/.composite.c.swp deleted file mode 100644 index a7256a664bf54db729c9af0a7a72cf37da4bd72d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 24576 zcmYc?2=nw+FxN9;U|?VnU|=xLYW1~jkYcE`Vqi#2&dAIIi4(xZmBgrGV5kEdsGpKr zmYSoVUzS=_mYG_fTBMtnUzDw%m|m2b7GIQ_m=d3#ms6=Cs7$O-M7#N|Lg@J*=fPsO*0m^4(U|^_ZU|`6B@|hVJ z7Zl1X!+mxJhKuYB3>VlL7&fvqFic`+ zV3^3xz>v?*z+lhLz`)ARz`(-Jz;KU^fng&X1H)7{28Ika28K{J1_p071_oC)1_l#0 z1_om`NLaf;#kAQN7#P_Y7;dvNFsx@~U|7n^z_6B;fnf$K149`r14B3~1A_xA1A{#) z1A_u90|O^30|N&u1H(ra28RF43=9vL85q7XGBBKBWMEK$f`CfbqN4cV)RN$mqRhN> zjettmisbmP#GKMp*P^2QA|0^G%rpg!%-rCjWL>+8;>@a41w~th%v|5Z;%t~eO^pIn zK_$8Zuz;pQHP|G5Err~~>{Nx~(xOy_+{EH+1+enm#FFHU)MAC=qGX7GmOjLdwP0N! z#}=iQlosVF_=WoTSi=QD63AF7J+mw|4`Og;Zen_>LP~yWu|i&ci9$wVS*k*AeoCrB zj8c%hlVgmMLVl4#jFL}`l9DFEMB)RaxTGjKw?G3H4j`j-6u?F+X@UaCKtWAS0lOj} zs0vW1fc-+AH;eO2i;`0ni1cWXyAxio*4Cm2Il@(-Fvcp4RS={ag7x*a@=Hs=@ocM* zm{X9EXbrIhBCVi6gcUHQ3JOT35os@x%8IiUP@M_xa4!9vY9h#*I1AsK;`$Bu16Y~^6Zb?ZXyI3->yx4zX3x&;S{jnU@apk!EJDCNzSfzK56!N*Yj;3Di{J#G#N_Qc{$e zR9cd%pq`nlu8;|-s}dEG^NUi!p2*Bg2Rol|9TiepfLv#TT%xa~kXfP*F;iDpp(ru4 zI8`ArPazdlwSxmPwIZ_w9OMcL3ZPmZUSH|jUh)`hs9@Rt~En711L_= z6FAHn3R?L|S(*$`Qy3U(VbKFI0+NKFve@j1_i%M|VE~0_N@^OYT~L}_qEOAiz^PG^ zTL3Bv5;YaH6f}}F6|@x$jVyE&G&D3oQkn{Ob_y0CQ7{k6)nrgkNlnYlOI7d<^>OhG zGcq;RNYqhC(orbMEzsnIf*4R}rWNJqDg=1?s23~5Lp`XM42leWErp!S>{JDfL~My95kItodpB?`Hv$r%c1iNz(UMGE3At^CATcIQ$9OEgeX^Evd zC17Jft-fMC1y4}APf0D#OwUVA0jWz$RVXb6F*AztOVcwH5DSJ2Ds>dfGcuDiK>9;K z&P>b90X3@%a#C|s^GZNYN-ixf$hp%AP zQ&8|s12sr>6pB+*6>Ks}N(!v>^~=l4_0sc7_413-^>Z?lQ}c>bi}mfm0TY>D3M<>d zZP=pJ#&!rd=a!96uEwJ0%1A)quVCo@^W2V#&yVorWuI)j21 z$bV>3Ajju|%9@nalEloMVz7LLqHaS(owi)h=+e@h=QYEq(Zo3P>`cvNTjs_ zD0D!zU@EA|4$8Qhxdl0ysVNHOiA6<;c_kpPflTnt%u7u!N=z$JKm;Qw{iI|TLkrIo zh0Hv#-5L1>AV(yYC}fr>lxODTfU1tt;?%U#935x?ROXi!DdZP`l9Q%FVqT>}P9i9I zm!%eg(k0mcNtIy3+=^0D6@v5AO3D+9QWe}lDK!zKO-CU$vm_(6NCB!x!59_?&;Zu~ zg_uSnI6da(r(~v8f}B&Dmy%ipHn=3UD7P4<2VcAuD-?iYvN$6(1z$j zWqzqba$=rBQ7R&YKn_UEOVQ6Sf(8RpIxkizNGvLWxd&Tl>w!}>sQ(|s$H1_J57Pe! z&FNp}XJF{$XJ9DcXJAO$H1_MkAYzu9|OZOsNTgO z{ljKdWYi<0Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0s|2OkWpCZ=weZ731nCf zA_^LJ45*Cv^aGFarlb~^Ku38&68c&S0pMY6g}l_%lwyU_V$h&betDh(XuJSwU>Rs$ z6g;>E9WE_OEm5$wRREg>o#6xvAq^C1fFyN5Gn+c#c}X3GlnNb%lu8}Q%z_SN@E$gX zj&Lc01#%wL0rqF)&Q$V_>M_ zV_*p9V_-1lV_;z6V_x@CD641+T08bSGgD% z4nXJocXBZ>WN|StIB+pAyyIkGxXH=DaG8^V;SwhU!(vVbhDc7RKL#R^j@mgI0;3@? z8UmvsFd71*AuwD+0Mw=eVeIXy(Is|7tT}|QMu03e9P&%-@GP%{dlkIOl7LUq)F7?J z08RKKt*;`g+W_q{fch!0&H`wi0N%Be$ejn`6FA(HkY!4kz67rTQczIP*HTE%NCi#s zXQV2WB^G7omli`7Re+ap7K6G{uvMfb8L5@fMWvu=b5QpT915Vkhv9$dG82&f3No~?gi~RLbTh0orb<}q_`*_ z3k<=lIE=s~bm5r3mO@EtZb5!gVo{|+a$-($X%3`Y2MV0Tq@2`ZkiS7*F3p3kc>oz! zp`%c#qmT*d1%p=ur554P2U1>Kp`%b-30DnWUkW}~4Yc|Kv@{FU@y#m%b!X!<5{u)( zYdS%(QJfv0pO#jfS_0*Orsh*p;YKAVCTFCkC_uWU1x5J!6VqOZ=(~yP5Ag@E+243CX>43Z4AZ~GKa&l^Mv7Q2G z2}er4f}ei~cys|YvXGLRT$Bn~xdAan5fp)-C`v;a7*GJMJ;+N-Fx2ueFy!zsFl6&EFu3tBFevdbF#O|YV7SH2z_5**fnh5* z149*b4*_WJfGjrygE%(>gBUjh!xt_FhHYF73a49Z*#45v657*0aR3z|6@7y>vM z7<4!p7_>PV82C9E7(Q_@FkItcVA#pQz%ZACfuVtefuV?lfx(x9fq|EUf#EYd1H&zL z28J{23=BKj85pLqGcdTZGcd@oGcbH*V_?|D#=wxs#=zji#=s!W#=!84m4V>}D+9xG zRtAQntPBj>Ss56%u`)0$W@TV#VP#;*V`X3nVP#-2WMyDbXJueegs$`FXJugEWo2OC zVP#-A!@|H&$-=;p$HKs1$HKs1!@|HI&ceX(fti8f1TzD}DrN?T8fFHDOlAg#3}yxf zRb~bT0cHjUer5)S*Gvoy%b6G$a+nwxBAFN%+?W^`1eh2Y_(AcFrIiNFa-hXs8j1mx zjz#J50f|M$sUf8WIjI^2`Ng1hdYMW(3TmJnq?S^t2`k<~I>BpH!7E$gr8PL?q6&e^ zy`oBm#Pr0>Jjj|f@H(=R%-mFklA=mbP6C|+30gFk56YvkA}UP*kCQTy9i^k72DVp6 zK@IFAxFhwoz!@1QaYQ2M|q zX+a^RucZKTMKRJqtwM1=Xq6Od@t2Yb8aK_Ugp`9&pMfGDGCT{7dQjM4ECYt907V4| zXXb%IOF`QfJO~_*Ts?xMK^V4ELfcls1QM(uQ5eq5Lth&YN@m4H$%uLwvUnDz0lJzV zp1VNesBQv@fY!`|U52^=0we{)n3_N`VBq)1u_+~2>|4Ja8t=hQwL;=g06xVB(%Yz#?VF>%rF=YGC;u=Y5A-c zsDwe*X{f27jnz&1S_*XF-z%HoqBS@f!ihrREf;LT4&qF2Ic6%sfy+g*H85%0M(|xu&)) z@@@u@Ck5z}OjqJffzasH%FM$|Y_!*ik!(QD0VPyKB%?YD&G$y8rl3v!KsPu<7gs81D}cAL z6jvyKHt-c^LklnkZAftjY5F6)1T`l!57kg`zZKjxMK=?+y|5s&A~gq80U@PqsA152 zfNCPBy8v@$3f!3~AZMD8zU>Cx1c-;gfr)<;4kB@))Ci!c0AYPCc+M!vS4hd%fz@8n zS{4-h;6{jojje(KDCWUU5ik#y50JXViQs(>MX3r!skw=ndBxym%;2sSDDRczgBCrf z#PGIS{_CLP`ZB zw7}+6C}=}^F-Ycs6++Ab`6VZ_Kp`bF7qT6oSO?UK1kJprrhtl5u&coqE9lz7yQE+f zzyeUaKv^KQM4_|*R@xv8gqwnhnqo*Cg3SgOIgp+sLO}|W0#LMpHRz%RkPRd!f=xka zErw(^uqbx3kraT<2Jv;#0u5?5NM2t{0n*1+fOY;L&Aozrct%I~9@Mpimbb8Gst#ly zEvP*QYMo%TVnJ;fkUa=5K!O_-vEXb9=0c(rqz)4Ha9J=HA`6OeFc+L$aVB0g-{@DR z7UhH8Sgep(0BYV9WhR!S=2XHX4pdyiiW!i-prHU=J9q&Bu^bdk`Q-|^poRQNl_ja* zS^MJb%mPr$54<4=(l^dattf%CJrqF7K$d`f1-1iHc!A0lSg{AU1XL55DA+3)D_AKQ zK#L@(m1&7Z3YmE&`OpS9C~`cc`@5Eu=C(GVC7fzc2c4S~@R7!85Z z5Eu=CVHW}=1#Are85kIt7#J9`85kHCp_r9{fgyl_fguOVXMqe4FdT>S*%%lY+!z=b z4nq0t3=9l03=9nSpnPViycr|J%wZRBqaGR!fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c z4S~@R7!83T6atAU3=H}V3=Ho<9e)AH7{DKX28L7o3=GHl85rvL85oTC85o}NF)$qF zV_-PI$G}j^$G~9B$H4H6mx19BF9XAVUIvB|UIqqzUIqqjUIqp&UIqpsUIqq1UIvCw zJPZsUc^DXW@h~ui@-Q%j@Gvk4@h~vFGh8vs=42L-x7#46cF!XRTFjR6fFqCjIFgS8DFsO1eFeq>` zFtBqnFfeg4FwEv)V3@|iz|h3Oz);A+z)--!z#ziG!0?)#f#DT91H&zL28QMA3=GTI z85l~~85o?{85p$K85lI#85sVvF)&WvM@0GVrF1i z!OXzW#LU1D!OXy*$jrdN!_2_I#>~L5gNcD5n~8zJpNWA%g$WX8??CZJe0oz*P*6xq zOis;BEY2>DHi%WQRS2jIDJ{rJjdxEi@hnNr)qqRK7gXvf7-(8EfHdjrCl(i{7L{ng zlsjjnCTF84)`ThzsPs?DN=+_N(1IHkZ5#_R28&#LK_%D~5KSOwIF^(Y1(y_M=A~;S zXO!k;$LHp!=qM<`O;f_KC@Ci~IXgZ%KPSJ4IJ1y6z)Y(J`xE5XqSTVoqC5q^P#+(N zt3iV3SgE8aF|W8Fu_!gKBt9u8KRLTtA+s2)31%WlOF*S-QBizwY6;Xs0hO*5$?;){ zIi;ztMMe2VI$&Ef(-btYm}hGXHcwNb8mtPN3I$k*;t4oReJ~dm7o;XQj2vIl+g5`83hXpP`bhzqnIW^wZIG_$uTg+FwOc}3L*Y3{tC&7c?u;N zsl}-ZNvR6yIr&M6ISR!ki6xoI_6ksml>9vP5{2Z9#Ju!Wg_KfIa#JWM%1A z2m=(-Q%gYMuK<=%NJ%V7R7lAzN=+`wsZ>ZS%Fk8EE6pvav;v1aG$`U-T%Chl-83?D z(~C2!QVrt^D&YkHnw$Yb4yGCs0eL0yj>SItdFfb8fvPmXp%Q8)cE>;+0yP5UG_Wbb zsfDGfdC7=E6{``lynVYIzc6pVnC&1QF?qpVo`A_xQ@_BEJ_Cz0sj7( znf^*T3JPk_gr^3!TCE~KElo#3tr9|kqZky!@u?LBiFqlhDVmy)1eBRqqEMluP^qI( z1u0-b0-3q#6*>x;x#^VKn(s1m(;?y@ zYaqc03Pp$*!g#R5P=eH20nH?^cdZ#1z;t|3YFx&qGTNfP&)z~8qlcIQ80kC8NjZB z7ywFhAax41AUzN-fsz+Q3{!fVFw%mBhOh<>!J6$0SW_G|z#(UmSvH>uA6X(E>SIN1-&YI5RyjH3d|Y7AYiw zVv#{PB{dBcGU=%$PN0^N2H3igjLf`r9fb;=N}VcA1r03?RIOT?8ldu~D6tY0qX8j7 z#wgk}Dl~NzG%7(vm8PbqCMc|P6O)Vb71HvHK-EeKC`7>)dAdNVl0*e?CQ!&vQ^-xt z%`d717lX=~dC57YDX9uJ;HpMHv8X7qG9TPx)yuGBV1Uajff74piLzdX5(5KBw}O^J zd1grlxC--!uVB_wQ1DDs$jb-iiqupEn~ai@0xNy}^73-M^t@8N{GxRIoXq6ZyyDbi zeLFn{1ucch{8EMDjQrA^6orh$vQ&kl)a2C6veXoXM1|!1f=ZB+OEOXw-2Flo+*9*X zixP7b0!ou|GLsd2AOdPBGX z90Ia1skB5P+%v?(KQu(a(JxXV+%YJ~(Jv&@8XP+Lr6mffWvSp6m_lZ5K~83Bib6Rk zobyURUIUrno0*rIT$GqrqJRiSQ2RM0v$&)vGpV#BHANv4VnRlK0mu=FB?_4(3gwwO zIiNOOX>n>=X^svw04noKixlz;KrJ>+g~Yr{g`C8a)FOql)S}|d{5-J#lPbX;aVttq zRS3>cD=AMbN>y;nFU?Cy1ZmSzNX;zCKWK_OX?SX2UY4>n8mKsks3)c@CEVPJ>=P528y=JXHnGcYXT zXJDAX&%n^n&%jW?&%ofs&%nUL&%p4CkAdL~9|OZKJ_d%Bd<+b+d<+a0d<+cid<+b1 zd<+b%d<+aHc^Me;co`Ukc^MdX@GvmU;$dLu;9+2B=V4%I<6&TM^^R89s4 zaZUz?4;%~(^Ent8YB?Af(m5Cyf;bo$G&mR-)HxU!96=PdF%yi0^BUsvHf0(GVD+ApmLm7C}amFuPhtn%0nZ9->_b?aF{# zFu92Z@Fp9q-3OJ2c6BhMaq0t0fI2fUD{CQL22fiY;z|$;gi(3eML?p-`FSOod8Mh4 zmMKUC9YdS7@rbrBsNb!l0BTR?!n%11P(Pq+2K5a}6ck}&U*Mhux*9~VmE;!SFbC2s zcF9aG0ga7;$2Os12o925PzMsXZhb9<5>R!TS*(z#kd$9klwY2jqEM8YmRbbr+3P9f ze`YEZ&MX71v_941M6%-JmjVuT2-ULHOT}yHcbQBCh9Ws;#d1792W{yi@ zNg`};71<0B8>=}6&}M&T3XY%&sEqgY0}rZzg94;ZL0?NDDK$BL`WclGMbU zVvxVEMnXw$0k{hR>4y2GmTQ2LHON)y7^`;7FQb1A%4oXK*X9TtX1M&fU1O?Q60*{+$LOcF2$H8b= zvW0dol2R2C6$;z-0+f~%Y++**#d=`1xrqhQ zU`A$2EH=AAzEcdS#2k%)mc7vW3sFLWvKwXqV-H*c1%qNH%18y~XrB&fpa5w|0#q$R zvXr)=jsj?00Y}AzAw#IxjRwVof<^_X9yI{jt)Ni} zVjG%5I8|VdkvWLRfEoPI7zU>tlnMzffjzyXmZcU|D!`%$TiSrl&ZK3gmlmZefW`?j z^2y0;$>h+;bmYj;bmYj=7pG3 z$-}@2pn%EQ1A&cndq%frCn#>2qi%EQ2*!Nb77$HTzzm79U# z8aD&OHf{!ntytCrcyKc?*l{y3@N+XT@NqLR{N!R_xW>i6aEOb6VLcZE!zwNYhH@?j zhG;GZhA=J$23sx$1`93*hKrmG3}-kQ7qBGt%9KuW_H3*jHuYa`7<*GQx9rO4Xg}R5om-|qe{V6 z!N6LfO2J0K(8$7Cp-NjDlp_%CQ~+fl(BcHpRE&bILWP!@g0?~>m^3u9&@wbq&{i-s z)iOghTwe=h9!OSKS0O(wtvIy=+ybqF&U9wx=jnj8DWJ+%pck2-X$J*c1<-Vmg0?~h z$V8C!$e{%G2*`;B)(Vv%kC<62R3iKVmab5+RWLNQR;W<0Q_wZER;bX`h4~X4xQMBO(0gUz(Nf)D~r0)LJ1nesEs0Ub&GXs z7t(gaf7+rWsZ*kFJ_1h6M?tltEeufjfS{IY&o92}v=`q|sFqh++gf zDGp8iP#$Pf92~OnJ_Bqi8dL#d;uk6anp7vC13nQ9)dE(IHEV&FE+~O!^ARxyn%c}s z1(nC(LJm~gL5sJ5(Nz=4*~O3w4;(g#i~=rIKs_oXS@85RWZlk?TQvcn=~mDpyjTKO zgq9R#7UZNVB!gB*Lb@|0`3kv-*{KS}pe_%5{7@k|zZ5iznhu((OU^GU0xy`<2Q9h) z2P@9x1{z`mt3c!`Xe9y;e_D+kgR%g^M)*=p$a*WVd+-b^V{|Z~iyT0PA~3>u(8>^K z8G(=nvGnx|iZb&`(lnG(^7B%`lg&u$D`N6sIT)lCff0i}pzaT7}~6weA4gnk$c zVmoFZ4J40nJSc49^OM1A3?RV+l1Im&jDf5YTt0z%+R$Zc$a1(%L1aEO^A3<8in|EKvG7^d+vF!b^>F!b;vkqz~IEkz#z-V!0?Ngf#D@D1H)Nf28KPn z3=B2A3=DC+3=BTJ3=BNH3=Hph7#QyGFfiQaVPM$9!@$tT!@v;1!@yw1!@wZH!@$4> z9TQl|&A`yY&A<@M&A<@E&A<@I&A=cE+BX1Q|Ifn3!0?Qdf#C`#1H)xb28L~%3=FZH z3=A=x3=Gkn3=HO+3=E$*7#MbPFff#HFfdqfFff>NFfd$aXJA;$&cKiXT_2#s&cGlJ z>ff_5FjTQIFzB!`FtD;QFl>RY*$1ryNM~hWuw`Xnuwi9j_|C$>aFB(8p^}AxA%%s3 zL5hWe;SOkhKD5uioSA`P88ZVz6*B{a1+?G(l8J#~9}@#ZITHhe783)*E=C5135*O3 z{pkI5^aKUsBl0BDd=*B5!l@D@frQDN7o)0uR5FYNW3cBotRagORv;$U@FKxxkQoRJ zH;oh%FdR>cW`x}e3JP%R;j;=FXw3&51*AlVqE`VlM+lxt8J!eG=^G%2(&(h<$ek30 zbbOFg!-$#?9YGV8$Z-r}!_|%*jCQFAy(Ui*2ZJ)=Yp0`kIton18@kAz&*Ik-1O0THBc!xr03Njg)pLb z0iGWqZDtKLq5~QTD~>iIyiFT4DhNt>2u+5BHG#%3K=c3Uj0_BuK?eXr*Z*JVXJGK* zXJGJ#?)zWF$H0)y$H1V^$H4HAmw{m^v`=5n%fOJz%fJxK%fJxC%fJu_Jp+J|mw};| zhk?NcwD+Hzf#DE01H(aX28MiY1_n*&{QVCu28Qok3=HSE7#N(Pd-fM`GBC7pGBC74 z=jTE5@=G}w7&18+7^FEE81Az(Fl=LIVAu+shwo))VCZ3IVCZINV5necVDMsRV7SJ{ zz|hLZz+lD3z`)JMz`(`Ez_5drfuV?%fx(HDfx(fLfx&^5fkB*=fuVC$9nHd<~F)=V4V`5-f$i%=<$Hc&p z%f!GC$i%?l0E#ar28KtB3=9{L#t=Y>02$*cgOHV>u(6KLDkvaQGbmYNueTH+W29I{ zXi;oXP*A{a38qSP5fC4TevlMV4h1R1=~(bSM_j|nAe{BGwEnlLVNg+ih$YtnQUtbxq-V=Q=AGApia`J-?cy$wWBdVs3f@f}G zda7$)Mq*xas-6=(FcD`~L>t6v>ZL*yAP-S%Vm7crP6AcWiXhLx+U%fJ6xdyjNP)=e zaSwfu&IW)27zHDe7E*y*F+>2<4%#`9U!;(iU#_E2 zo~i)a1)GytRjH7emjd3O4kz@BO`(vv?do+vVlr{+%}CC+lk4k zu*w2=;g2ZkKtYP69$cT|E9yWEc&x#XsDDrkPaMsG5jTkoUph~Du?@>tPRbB>$3%m>rnb7?N0lW+h_Ph)X zcDxJ>k9ZgucJe^Z5?H~*z);G=z)-@&z~IEgz#zcG!0>~cf#Ewh1H)Nv28PAZGX=7_ z85pv-85lCT85qpD85sU?F);k)Vqo~j#lWzJi-92ldVXLC7XyP57X!m5P6mdLoD2*z zq3Z{PIT;vEK-Ucfa4;~aLH7cH&H}u`&cJY*oq^#LI|IW;b_Ry&>Se=3ilDZM|#xTqaiRF z0*DZRHX+~?p0PWuyYE2*6rjPV(fNZRKYsv91BgaE8M6n7v;$p944IP#nTrjNo@Sb# zoSmCk09!x|T@wY}WCQMqK?gS7Q%jJ}r-iQ81RaWqss&~cc=C62Uk>*gK z`%*xgU!c8mM1F_Lfws9o<)9*l;FJhjL;zhT4iiU=E Date: Mon, 21 Mar 2011 19:11:10 -0400 Subject: [PATCH 065/213] separated normal and lighting render modes --- src/iterate.c | 6 +- src/overviewer.h | 10 ++- src/rendermodes.c | 157 +++++++++++++++++++++++++++++++--------------- 3 files changed, 115 insertions(+), 58 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index f0e962f..6a3e989 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -17,9 +17,6 @@ #include "overviewer.h" -/* available render modes */ -extern RenderModeInterface rendermode_normal; - static PyObject *textures = NULL; static PyObject *chunk_mod = NULL; static PyObject *blockmap = NULL; @@ -82,8 +79,7 @@ chunk_render(PyObject *self, PyObject *args) { state.chunk = chunk_mod; /* set up the render mode */ - /* FIXME deciding on correct rendermode */ - RenderModeInterface *rendermode = &rendermode_normal; + RenderModeInterface *rendermode = get_render_mode(&state); void* rm_data = malloc(rendermode->data_size); if (rendermode->start) { if (rendermode->start(rm_data, &state)) { diff --git a/src/overviewer.h b/src/overviewer.h index 6c6fe02..4c6ec39 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -59,6 +59,11 @@ typedef struct { unsigned char block; PyObject *blocks; } RenderState; +int init_chunk_render(void); +int is_transparent(unsigned char b); +PyObject *chunk_render(PyObject *self, PyObject *args); + +/* in rendermode.c */ typedef struct { /* the size of the local storage for this rendermode */ unsigned int data_size; @@ -71,8 +76,7 @@ typedef struct { /* last two arguments are img and mask, from texture lookup */ void (*draw)(void *, RenderState *, PyObject *, PyObject *); } RenderModeInterface; -int init_chunk_render(void); -int is_transparent(unsigned char b); -PyObject *chunk_render(PyObject *self, PyObject *args); +/* figures out the render mode to use from the given ChunkRenderer */ +RenderModeInterface *get_render_mode(RenderState *state); #endif /* __OVERVIEWER_H_INCLUDED__ */ diff --git a/src/rendermodes.c b/src/rendermodes.c index dd08ed0..d90968a 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -17,12 +17,75 @@ #include "overviewer.h" +/* + * ======================== + * == NORMAL rendermode === + * ======================== + */ + +typedef struct { + /* normal mode does not have any special data, so just use a dummy int + this way, normal mode is just like any other type of render mode */ + int dummy; +} RenderModeNormal; + +static int +rendermode_normal_start(void *data, RenderState *state) { + /* do nothing */ + return 0; +} + +static void +rendermode_normal_finish(void *data, RenderState *state) { + /* do nothing */ +} + +static int +rendermode_normal_occluded(void *data, RenderState *state) { + int x = state->x, y = state->y, z = state->z; + + if ( (x != 0) && (y != 15) && (z != 127) && + !is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) && + !is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) && + !is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) { + return 1; + } + + return 0; +} + +static void +rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { + alpha_over(state->img, src, mask, state->imgx, state->imgy, 0, 0); +} + +RenderModeInterface rendermode_normal = { + sizeof(RenderModeNormal), + rendermode_normal_start, + rendermode_normal_finish, + rendermode_normal_occluded, + rendermode_normal_draw, +}; + +/* + * =========================== + * === LIGHTING rendermode === + * =========================== + */ + +typedef struct { + /* inherits from normal render mode */ + RenderModeNormal parent; + + PyObject *black_color, *facemasks_py; + PyObject *facemasks[3]; +} RenderModeLighting; + /* shades the drawn block with the given facemask/black_color, based on the lighting results from (x, y, z) */ static inline void do_shading_for_face(PyObject *chunk, int x, int y, int z, PyObject *facemask, PyObject *black_color, - PyObject *img, int imgx, int imgy) -{ + PyObject *img, int imgx, int imgy) { // returns new references PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x, y, z); PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); @@ -48,21 +111,14 @@ do_shading_for_face(PyObject *chunk, int x, int y, int z, PyObject *facemask, Py } } -typedef struct { - int lighting; - PyObject *black_color, *facemasks_py; - PyObject *facemasks[3]; -} RenderModeNormal; - static int -rendermode_normal_start(void *data, RenderState *state) { - RenderModeNormal* self = (RenderModeNormal *)data; +rendermode_lighting_start(void *data, RenderState *state) { + /* first, chain up */ + int ret = rendermode_normal_start(data, state); + if (ret != 0) + return ret; - PyObject *quadtree = PyObject_GetAttrString(state->self, "quadtree"); - PyObject *lighting_py = PyObject_GetAttrString(quadtree, "lighting"); - self->lighting = PyObject_IsTrue(lighting_py); - Py_DECREF(lighting_py); - Py_DECREF(quadtree); + RenderModeLighting* self = (RenderModeLighting *)data; self->black_color = PyObject_GetAttrString(state->chunk, "black_color"); self->facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks"); @@ -75,31 +131,22 @@ rendermode_normal_start(void *data, RenderState *state) { } static void -rendermode_normal_finish(void *data, RenderState *state) { - RenderModeNormal *self = (RenderModeNormal *)data; +rendermode_lighting_finish(void *data, RenderState *state) { + RenderModeLighting *self = (RenderModeLighting *)data; Py_DECREF(self->black_color); Py_DECREF(self->facemasks_py); -} - -static int -rendermode_normal_occluded(void *data, RenderState *state) { - int x = state->x, y = state->y, z = state->z; - if ( (x != 0) && (y != 15) && (z != 127) && - !is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) && - !is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) && - !is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) { - return 1; - } - - return 0; + /* now chain up */ + rendermode_normal_finish(data, state); } static void -rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) -{ - RenderModeNormal* self = (RenderModeNormal *)data; +rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { + /* first, chain up */ + rendermode_normal_draw(data, state, src, mask); + + RenderModeLighting* self = (RenderModeLighting *)data; PyObject *chunk = state->self; int x = state->x, y = state->y, z = state->z; @@ -107,24 +154,34 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * PyObject *black_color = self->black_color, *img = state->img; int imgx = state->imgx, imgy = state->imgy; - alpha_over(img, src, mask, imgx, imgy, 0, 0); - - if (self->lighting) { - // FIXME whole-block shading for transparent blocks - do_shading_for_face(chunk, x, y, z+1, facemasks[0], black_color, - img, imgx, imgy); - do_shading_for_face(chunk, x-1, y, z, facemasks[1], black_color, - img, imgx, imgy); - do_shading_for_face(chunk, x, y+1, z, facemasks[2], black_color, - img, imgx, imgy); - } - + // FIXME whole-block shading for transparent blocks + do_shading_for_face(chunk, x, y, z+1, facemasks[0], black_color, + img, imgx, imgy); + do_shading_for_face(chunk, x-1, y, z, facemasks[1], black_color, + img, imgx, imgy); + do_shading_for_face(chunk, x, y+1, z, facemasks[2], black_color, + img, imgx, imgy); } -RenderModeInterface rendermode_normal = { - sizeof(RenderModeNormal), - rendermode_normal_start, - rendermode_normal_finish, +RenderModeInterface rendermode_lighting = { + sizeof(RenderModeLighting), + rendermode_lighting_start, + rendermode_lighting_finish, + /* no special occlusion for lighting */ rendermode_normal_occluded, - rendermode_normal_draw, + rendermode_lighting_draw, }; + +/* putting it all together */ +RenderModeInterface *get_render_mode(RenderState *state) +{ + /* default: normal */ + RenderModeInterface *iface = &rendermode_normal; + PyObject *quadtree = PyObject_GetAttrString(state->self, "quadtree"); + PyObject *lighting = PyObject_GetAttrString(quadtree, "lighting"); + + if (PyObject_IsTrue(lighting)) + iface = &rendermode_lighting; + + return iface; +} From b312cef52ebaa6932cf3530f3cd527fdc251e5c6 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 21 Mar 2011 19:30:34 -0400 Subject: [PATCH 066/213] put lighting render mode in its own file; it's about to get *way* more complicated --- setup.py | 2 +- src/overviewer.h | 17 +----- src/rendermode-lighting.c | 114 ++++++++++++++++++++++++++++++++++++ src/rendermodes.c | 120 +------------------------------------- src/rendermodes.h | 76 ++++++++++++++++++++++++ 5 files changed, 194 insertions(+), 135 deletions(-) create mode 100644 src/rendermode-lighting.c create mode 100644 src/rendermodes.h diff --git a/setup.py b/setup.py index d918bd7..5777aa8 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,7 @@ except AttributeError: numpy_include = numpy.get_numpy_include() -c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/rendermodes.c'] +c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/rendermodes.c', 'src/rendermode-lighting.c'] setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) # tell build_ext to build the extension in-place # (NOT in build/) diff --git a/src/overviewer.h b/src/overviewer.h index 4c6ec39..f85636e 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -63,20 +63,7 @@ int init_chunk_render(void); int is_transparent(unsigned char b); PyObject *chunk_render(PyObject *self, PyObject *args); -/* in rendermode.c */ -typedef struct { - /* the size of the local storage for this rendermode */ - unsigned int data_size; - - /* may return non-zero on error */ - int (*start)(void *, RenderState *); - void (*finish)(void *, RenderState *); - /* returns non-zero to skip rendering this block */ - int (*occluded)(void *, RenderState *); - /* last two arguments are img and mask, from texture lookup */ - void (*draw)(void *, RenderState *, PyObject *, PyObject *); -} RenderModeInterface; -/* figures out the render mode to use from the given ChunkRenderer */ -RenderModeInterface *get_render_mode(RenderState *state); +/* pull in the rendermode info */ +#include "rendermodes.h" #endif /* __OVERVIEWER_H_INCLUDED__ */ diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c new file mode 100644 index 0000000..d6a123e --- /dev/null +++ b/src/rendermode-lighting.c @@ -0,0 +1,114 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + +#include "overviewer.h" + +/* shades the drawn block with the given facemask/black_color, based on the + lighting results from (x, y, z) */ +static inline void +do_shading_for_face(PyObject *chunk, int x, int y, int z, PyObject *facemask, PyObject *black_color, + PyObject *img, int imgx, int imgy) { + // returns new references + PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x, y, z); + PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); + double black_coeff = PyFloat_AsDouble(black_coeff_py); + Py_DECREF(black_coeff_py); + + PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); + int face_occlude = PyInt_AsLong(face_occlude_py); + Py_DECREF(face_occlude_py); + + + if (!face_occlude) { + //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) + + PyObject *mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref + //printf("black_coeff: %f\n", black_coeff); + brightness(mask, black_coeff); + //printf("done with brightness\n"); + alpha_over(img, black_color, mask, imgx, imgy, 0, 0); + //printf("done with alpha_over\n"); + Py_DECREF(mask); + + } +} + +static int +rendermode_lighting_start(void *data, RenderState *state) { + /* first, chain up */ + int ret = rendermode_normal.start(data, state); + if (ret != 0) + return ret; + + RenderModeLighting* self = (RenderModeLighting *)data; + + self->black_color = PyObject_GetAttrString(state->chunk, "black_color"); + self->facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks"); + // borrowed references, don't need to be decref'd + self->facemasks[0] = PyTuple_GetItem(self->facemasks_py, 0); + self->facemasks[1] = PyTuple_GetItem(self->facemasks_py, 1); + self->facemasks[2] = PyTuple_GetItem(self->facemasks_py, 2); + + return 0; +} + +static void +rendermode_lighting_finish(void *data, RenderState *state) { + RenderModeLighting *self = (RenderModeLighting *)data; + + Py_DECREF(self->black_color); + Py_DECREF(self->facemasks_py); + + /* now chain up */ + rendermode_normal.finish(data, state); +} + +static int +rendermode_lighting_occluded(void *data, RenderState *state) { + /* no special occlusion here */ + return rendermode_normal.occluded(data, state); +} + +static void +rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { + /* first, chain up */ + rendermode_normal.draw(data, state, src, mask); + + RenderModeLighting* self = (RenderModeLighting *)data; + + PyObject *chunk = state->self; + int x = state->x, y = state->y, z = state->z; + PyObject **facemasks = self->facemasks; + PyObject *black_color = self->black_color, *img = state->img; + int imgx = state->imgx, imgy = state->imgy; + + // FIXME whole-block shading for transparent blocks + do_shading_for_face(chunk, x, y, z+1, facemasks[0], black_color, + img, imgx, imgy); + do_shading_for_face(chunk, x-1, y, z, facemasks[1], black_color, + img, imgx, imgy); + do_shading_for_face(chunk, x, y+1, z, facemasks[2], black_color, + img, imgx, imgy); +} + +RenderModeInterface rendermode_lighting = { + sizeof(RenderModeLighting), + rendermode_lighting_start, + rendermode_lighting_finish, + rendermode_lighting_occluded, + rendermode_lighting_draw, +}; diff --git a/src/rendermodes.c b/src/rendermodes.c index d90968a..c4675e5 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -17,18 +17,6 @@ #include "overviewer.h" -/* - * ======================== - * == NORMAL rendermode === - * ======================== - */ - -typedef struct { - /* normal mode does not have any special data, so just use a dummy int - this way, normal mode is just like any other type of render mode */ - int dummy; -} RenderModeNormal; - static int rendermode_normal_start(void *data, RenderState *state) { /* do nothing */ @@ -67,114 +55,8 @@ RenderModeInterface rendermode_normal = { rendermode_normal_draw, }; -/* - * =========================== - * === LIGHTING rendermode === - * =========================== - */ - -typedef struct { - /* inherits from normal render mode */ - RenderModeNormal parent; - - PyObject *black_color, *facemasks_py; - PyObject *facemasks[3]; -} RenderModeLighting; - -/* shades the drawn block with the given facemask/black_color, based on the - lighting results from (x, y, z) */ -static inline void -do_shading_for_face(PyObject *chunk, int x, int y, int z, PyObject *facemask, PyObject *black_color, - PyObject *img, int imgx, int imgy) { - // returns new references - PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x, y, z); - PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); - double black_coeff = PyFloat_AsDouble(black_coeff_py); - Py_DECREF(black_coeff_py); - - PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); - int face_occlude = PyInt_AsLong(face_occlude_py); - Py_DECREF(face_occlude_py); - - - if (!face_occlude) { - //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) - - PyObject *mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref - //printf("black_coeff: %f\n", black_coeff); - brightness(mask, black_coeff); - //printf("done with brightness\n"); - alpha_over(img, black_color, mask, imgx, imgy, 0, 0); - //printf("done with alpha_over\n"); - Py_DECREF(mask); - - } -} - -static int -rendermode_lighting_start(void *data, RenderState *state) { - /* first, chain up */ - int ret = rendermode_normal_start(data, state); - if (ret != 0) - return ret; - - RenderModeLighting* self = (RenderModeLighting *)data; - - self->black_color = PyObject_GetAttrString(state->chunk, "black_color"); - self->facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks"); - // borrowed references, don't need to be decref'd - self->facemasks[0] = PyTuple_GetItem(self->facemasks_py, 0); - self->facemasks[1] = PyTuple_GetItem(self->facemasks_py, 1); - self->facemasks[2] = PyTuple_GetItem(self->facemasks_py, 2); - - return 0; -} - -static void -rendermode_lighting_finish(void *data, RenderState *state) { - RenderModeLighting *self = (RenderModeLighting *)data; - - Py_DECREF(self->black_color); - Py_DECREF(self->facemasks_py); - - /* now chain up */ - rendermode_normal_finish(data, state); -} - -static void -rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { - /* first, chain up */ - rendermode_normal_draw(data, state, src, mask); - - RenderModeLighting* self = (RenderModeLighting *)data; - - PyObject *chunk = state->self; - int x = state->x, y = state->y, z = state->z; - PyObject **facemasks = self->facemasks; - PyObject *black_color = self->black_color, *img = state->img; - int imgx = state->imgx, imgy = state->imgy; - - // FIXME whole-block shading for transparent blocks - do_shading_for_face(chunk, x, y, z+1, facemasks[0], black_color, - img, imgx, imgy); - do_shading_for_face(chunk, x-1, y, z, facemasks[1], black_color, - img, imgx, imgy); - do_shading_for_face(chunk, x, y+1, z, facemasks[2], black_color, - img, imgx, imgy); -} - -RenderModeInterface rendermode_lighting = { - sizeof(RenderModeLighting), - rendermode_lighting_start, - rendermode_lighting_finish, - /* no special occlusion for lighting */ - rendermode_normal_occluded, - rendermode_lighting_draw, -}; - /* putting it all together */ -RenderModeInterface *get_render_mode(RenderState *state) -{ +RenderModeInterface *get_render_mode(RenderState *state) { /* default: normal */ RenderModeInterface *iface = &rendermode_normal; PyObject *quadtree = PyObject_GetAttrString(state->self, "quadtree"); diff --git a/src/rendermodes.h b/src/rendermodes.h new file mode 100644 index 0000000..89f12f1 --- /dev/null +++ b/src/rendermodes.h @@ -0,0 +1,76 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + +/* + * To make a new render mode (the C part, at least): + * + * * add a data struct and extern'd interface declaration below + * + * * fill in this interface struct in rendermode-(yourmode).c + * (see rendermodes.c for an example: the "normal" mode) + * + * * if you want to derive from (say) the "normal" mode, put + * a RenderModeNormal entry at the top of your data struct, and + * be sure to call your parent's functions in your own! + * (see rendermode-lighting.c for an example) + * + * * add a condition to get_render_mode() in rendermodes.c + */ + +#ifndef __RENDERMODES_H_INCLUDED__ +#define __RENDERMODES_H_INCLUDED__ + +#include + +/* rendermode interface */ +typedef struct { + /* the size of the local storage for this rendermode */ + unsigned int data_size; + + /* may return non-zero on error */ + int (*start)(void *, RenderState *); + void (*finish)(void *, RenderState *); + /* returns non-zero to skip rendering this block */ + int (*occluded)(void *, RenderState *); + /* last two arguments are img and mask, from texture lookup */ + void (*draw)(void *, RenderState *, PyObject *, PyObject *); +} RenderModeInterface; + +/* figures out the render mode to use from the given ChunkRenderer */ +RenderModeInterface *get_render_mode(RenderState *state); + +/* individual rendermode interface declarations follow */ + +/* NORMAL */ +typedef struct { + /* normal mode does not have any special data, so just use a dummy int + this way, normal mode is just like any other type of render mode */ + int dummy; +} RenderModeNormal; +extern RenderModeInterface rendermode_normal; + +/* LIGHTING */ +typedef struct { + /* inherits from normal render mode */ + RenderModeNormal parent; + + PyObject *black_color, *facemasks_py; + PyObject *facemasks[3]; +} RenderModeLighting; +extern RenderModeInterface rendermode_lighting; + +#endif /* __RENDERMODES_H_INCLUDED__ */ From e9b30cf780ae682597467f78e0d17cce37ecc27d Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 21 Mar 2011 20:34:48 -0400 Subject: [PATCH 067/213] changed PyArray_GETPTR3 to getByteArray3D --- src/iterate.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index 6a3e989..76fa417 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -158,9 +158,7 @@ chunk_render(PyObject *self, PyObject *args) { } else { PyObject *tmp; - /* this should be a pointer to a unsigned char */ - void* ancilData_p = PyArray_GETPTR3(blockdata_expanded, state.x, state.y, state.z); - unsigned char ancilData = *((unsigned char*)ancilData_p); + unsigned char ancilData = getByteArray3D(blockdata_expanded, state.x, state.y, state.z); if (state.block == 85) { /* fence. skip the generate_pseudo_ancildata for now */ continue; From 0e85f87fa7a93ab0f9e8f1b38e5f7e33365fc7df Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 21 Mar 2011 20:46:30 -0400 Subject: [PATCH 068/213] removed NULL checks on render mode interface all the functions are required to exist, now --- src/iterate.c | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index 76fa417..3e67cc3 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -81,11 +81,9 @@ chunk_render(PyObject *self, PyObject *args) { /* set up the render mode */ RenderModeInterface *rendermode = get_render_mode(&state); void* rm_data = malloc(rendermode->data_size); - if (rendermode->start) { - if (rendermode->start(rm_data, &state)) { - free(rm_data); - return Py_BuildValue("i", "-1"); - } + if (rendermode->start(rm_data, &state)) { + free(rm_data); + return Py_BuildValue("i", "-1"); } /* get the image size */ @@ -143,10 +141,8 @@ chunk_render(PyObject *self, PyObject *args) { blockid = PyInt_FromLong(state.block); // check for occlusion - if (rendermode->occluded) { - if (rendermode->occluded(rm_data, &state)) { - continue; - } + if (rendermode->occluded(rm_data, &state)) { + continue; } // everything stored here will be a borrowed ref @@ -185,8 +181,7 @@ chunk_render(PyObject *self, PyObject *args) { if (mask == Py_None) mask = src; - if (rendermode->draw) - rendermode->draw(rm_data, &state, src, mask); + rendermode->draw(rm_data, &state, src, mask); } } @@ -198,8 +193,7 @@ chunk_render(PyObject *self, PyObject *args) { } /* free up the rendermode info */ - if (rendermode->finish) - rendermode->finish(rm_data, &state); + rendermode->finish(rm_data, &state); free(rm_data); Py_DECREF(blocks_py); From 97d708106ac41491deb2dcaaa7710c8cff62675b Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Mon, 21 Mar 2011 23:18:34 -0400 Subject: [PATCH 069/213] Fixed typo preventing build --- src/iterate.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/iterate.c b/src/iterate.c index 3e67cc3..3997f5b 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -154,7 +154,7 @@ chunk_render(PyObject *self, PyObject *args) { } else { PyObject *tmp; - unsigned char ancilData = getByteArray3D(blockdata_expanded, state.x, state.y, state.z); + unsigned char ancilData = getArrayByte3D(blockdata_expanded, state.x, state.y, state.z); if (state.block == 85) { /* fence. skip the generate_pseudo_ancildata for now */ continue; From a885568d4edcf7c1e6c14aa19e274c7973ca438f Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Mon, 21 Mar 2011 23:23:35 -0400 Subject: [PATCH 070/213] Added missing DECREF memory usage is stable and constant over a 7 minute execution --- src/rendermode-lighting.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index d6a123e..5229cb7 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -31,6 +31,8 @@ do_shading_for_face(PyObject *chunk, int x, int y, int z, PyObject *facemask, Py PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); int face_occlude = PyInt_AsLong(face_occlude_py); Py_DECREF(face_occlude_py); + + Py_DECREF(light_tup); if (!face_occlude) { From 24950f60242f983fb8b2340d0ea8e5578befc9fb Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 22 Mar 2011 23:09:12 -0400 Subject: [PATCH 071/213] moved rendermode_normal into a separate file, like rendermode_lighting --- setup.py | 3 ++- src/rendermode-normal.c | 56 +++++++++++++++++++++++++++++++++++++++++ src/rendermodes.c | 40 +---------------------------- 3 files changed, 59 insertions(+), 40 deletions(-) create mode 100644 src/rendermode-normal.c diff --git a/setup.py b/setup.py index 5777aa8..e8f0503 100644 --- a/setup.py +++ b/setup.py @@ -44,7 +44,8 @@ except AttributeError: numpy_include = numpy.get_numpy_include() -c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/rendermodes.c', 'src/rendermode-lighting.c'] +c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c'] +c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c'] setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) # tell build_ext to build the extension in-place # (NOT in build/) diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c new file mode 100644 index 0000000..ca1b9c5 --- /dev/null +++ b/src/rendermode-normal.c @@ -0,0 +1,56 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + +#include "overviewer.h" + +static int +rendermode_normal_start(void *data, RenderState *state) { + /* do nothing */ + return 0; +} + +static void +rendermode_normal_finish(void *data, RenderState *state) { + /* do nothing */ +} + +static int +rendermode_normal_occluded(void *data, RenderState *state) { + int x = state->x, y = state->y, z = state->z; + + if ( (x != 0) && (y != 15) && (z != 127) && + !is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) && + !is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) && + !is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) { + return 1; + } + + return 0; +} + +static void +rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { + alpha_over(state->img, src, mask, state->imgx, state->imgy, 0, 0); +} + +RenderModeInterface rendermode_normal = { + sizeof(RenderModeNormal), + rendermode_normal_start, + rendermode_normal_finish, + rendermode_normal_occluded, + rendermode_normal_draw, +}; diff --git a/src/rendermodes.c b/src/rendermodes.c index c4675e5..2141d99 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -17,45 +17,7 @@ #include "overviewer.h" -static int -rendermode_normal_start(void *data, RenderState *state) { - /* do nothing */ - return 0; -} - -static void -rendermode_normal_finish(void *data, RenderState *state) { - /* do nothing */ -} - -static int -rendermode_normal_occluded(void *data, RenderState *state) { - int x = state->x, y = state->y, z = state->z; - - if ( (x != 0) && (y != 15) && (z != 127) && - !is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) && - !is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) && - !is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) { - return 1; - } - - return 0; -} - -static void -rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { - alpha_over(state->img, src, mask, state->imgx, state->imgy, 0, 0); -} - -RenderModeInterface rendermode_normal = { - sizeof(RenderModeNormal), - rendermode_normal_start, - rendermode_normal_finish, - rendermode_normal_occluded, - rendermode_normal_draw, -}; - -/* putting it all together */ +/* decides which render mode to use */ RenderModeInterface *get_render_mode(RenderState *state) { /* default: normal */ RenderModeInterface *iface = &rendermode_normal; From f5264e9306994d9024ba100aa2f4cb49f14ccb5c Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 23 Mar 2011 00:03:26 -0400 Subject: [PATCH 072/213] rewrote get_lighting_coefficient in C --- chunk.py | 102 -------------------------- src/overviewer.h | 4 + src/rendermode-lighting.c | 149 ++++++++++++++++++++++++++++---------- src/rendermodes.h | 9 +++ 4 files changed, 124 insertions(+), 140 deletions(-) diff --git a/chunk.py b/chunk.py index 1155f40..1076edb 100644 --- a/chunk.py +++ b/chunk.py @@ -398,108 +398,6 @@ class ChunkRenderer(object): return pseudo_data - def calculate_darkness(self, skylight, blocklight): - """Takes a raw blocklight and skylight, and returns a value - between 0.0 (fully lit) and 1.0 (fully black) that can be used as - an alpha value for a blend with a black source image. It mimics - Minecraft lighting calculations.""" - if not self.quadtree.night: - # Daytime - return 1.0 - pow(0.8, 15 - max(blocklight, skylight)) - else: - # Nighttime - return 1.0 - pow(0.8, 15 - max(blocklight, skylight - 11)) - - def get_lighting_coefficient(self, x, y, z, norecurse=False): - """Calculates the lighting coefficient for the given - coordinate, using default lighting and peeking into - neighboring chunks, if needed. A lighting coefficient of 1.0 - means fully black. - - Returns a tuple (coefficient, occluded), where occluded is - True if the given coordinate is filled with a solid block, and - therefore the returned coefficient is just the default.""" - - # placeholders for later data arrays, coordinates - blocks = None - skylight = None - blocklight = None - local_x = x - local_y = y - local_z = z - is_local_chunk = False - - # find out what chunk we're in, and translate accordingly - if x >= 0 and y < 16: - blocks = self.blocks - skylight = self.skylight - blocklight = self.blocklight - is_local_chunk = True - elif x < 0: - local_x += 16 - blocks = self.left_blocks - skylight = self.left_skylight - blocklight = self.left_blocklight - elif y >= 16: - local_y -= 16 - blocks = self.right_blocks - skylight = self.right_skylight - blocklight = self.right_blocklight - - # make sure we have a correctly-ranged coordinates and enough - # info about the chunk - if not (blocks is not None and skylight is not None and blocklight is not None and - local_x >= 0 and local_x < 16 and local_y >= 0 and local_y < 16 and - local_z >= 0 and local_z < 128): - # we have no useful info, return default - return (self.calculate_darkness(15, 0), False) - - blocktype = blocks[local_x, local_y, local_z] - - # special handling for half-blocks - # (don't recurse more than once!) - if blocktype == 44 and not norecurse: - # average gathering variables - averagegather = 0.0 - averagecount = 0 - - # how bright we need before we consider a side "lit" - threshold = self.calculate_darkness(0, 0) - # iterate through all the sides of the block - sides = [(x-1, y, z), (x+1, y, z), (x, y, z-1), (x, y, z+1), (x, y-1, z), (x, y+1, z)] - - for side in sides: - val, occ = self.get_lighting_coefficient(*side, norecurse=True) - if (not occ) and (val < threshold): - averagegather += val - averagecount += 1 - - # if at least one side was lit, return the average - if averagecount > 0: - return (averagegather / averagecount, False) - - # calculate the return... - occluded = not (blocktype in transparent_blocks) - - # only calculate the non-default coefficient if we're not occluded - if (blocktype == 10) or (blocktype == 11): - # lava blocks should always be lit! - coefficient = 0.0 - elif occluded: - coefficient = self.calculate_darkness(15, 0) - else: - coefficient = self.calculate_darkness(skylight[local_x, local_y, local_z], blocklight[local_x, local_y, local_z]) - - # only say we're occluded if the point is in the CURRENT - # chunk, so that we don't get obvious inter-chunk dependencies - # (we want this here so we still have the default coefficient - # for occluded blocks, even when we don't report them as - # occluded) - if not is_local_chunk: - occluded = False - - return (coefficient, occluded) - def chunk_render(self, img=None, xoff=0, yoff=0, cave=False): """Renders a chunk with the given parameters, and returns the image. If img is given, the chunk is rendered to that image object. Otherwise, diff --git a/src/overviewer.h b/src/overviewer.h index f85636e..5ae9079 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -32,6 +32,10 @@ /* macro for getting a value out of a 3D numpy byte array */ #define getArrayByte3D(array, x,y,z) (*(unsigned char *)(PyArray_GETPTR3((array), (x), (y), (z)))) +/* generally useful MAX / MIN macros */ +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + /* in composite.c */ Imaging imaging_python_to_c(PyObject *obj); PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index 5229cb7..b00c3aa 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -16,37 +16,94 @@ */ #include "overviewer.h" +#include + +/* figures out the black_coeff from a given skylight and blocklight, + used in lighting calculations */ +static float calculate_darkness(unsigned char skylight, unsigned char blocklight) { + return 1.0f - powf(0.8f, 15.0 - MAX(blocklight, skylight)); +} + +/* loads the appropriate light data for the given (possibly non-local) + coordinates, and returns a black_coeff */ +static inline float +get_lighting_coefficient(RenderModeLighting *self, RenderState *state, int x, int y, int z) { + /* placeholders for later data arrays, coordinates */ + PyObject *blocks = NULL; + PyObject *skylight = NULL; + PyObject *blocklight = NULL; + int local_x = x, local_y = y, local_z = z; + + /* find out what chunk we're in, and translate accordingly */ + if (x >= 0 && y < 16) { + blocks = state->blocks; + skylight = self->skylight; + blocklight = self->blocklight; + } else if (x < 0) { + local_x += 16; + blocks = self->left_blocks; + skylight = self->left_skylight; + blocklight = self->left_blocklight; + } else if (y >= 16) { + local_y -= 16; + blocks = self->right_blocks; + skylight = self->right_skylight; + blocklight = self->right_blocklight; + } + + /* make sure we have correctly-ranged coordinates */ + if (!(local_x >= 0 && local_x < 16 && + local_y >= 0 && local_y < 16 && + local_z >= 0 && local_z < 128)) { + + return self->calculate_darkness(15, 0); + } + + /* also, make sure we have enough info to correctly calculate lighting */ + if (blocks == Py_None || blocks == NULL || + skylight == Py_None || skylight == NULL || + blocklight == Py_None || blocklight == NULL) { + + return self->calculate_darkness(15, 0); + } + + unsigned char block = getArrayByte3D(blocks, local_x, local_y, local_z); + + if (block == 44) { + /* TODO special handling for half-blocks! */ + } + + if (block == 10 || block == 11) { + /* lava blocks should always be lit! */ + return 0.0f; + } + + unsigned char skylevel = getArrayByte3D(skylight, local_x, local_y, local_z); + unsigned char blocklevel = getArrayByte3D(blocklight, local_x, local_y, local_z); + + return self->calculate_darkness(skylevel, blocklevel); +} /* shades the drawn block with the given facemask/black_color, based on the lighting results from (x, y, z) */ static inline void -do_shading_for_face(PyObject *chunk, int x, int y, int z, PyObject *facemask, PyObject *black_color, - PyObject *img, int imgx, int imgy) { - // returns new references - PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x, y, z); - PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); - double black_coeff = PyFloat_AsDouble(black_coeff_py); - Py_DECREF(black_coeff_py); - - PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); - int face_occlude = PyInt_AsLong(face_occlude_py); - Py_DECREF(face_occlude_py); - - Py_DECREF(light_tup); - - - if (!face_occlude) { - //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) - - PyObject *mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref - //printf("black_coeff: %f\n", black_coeff); - brightness(mask, black_coeff); - //printf("done with brightness\n"); - alpha_over(img, black_color, mask, imgx, imgy, 0, 0); - //printf("done with alpha_over\n"); - Py_DECREF(mask); - +do_shading_for_face(RenderModeLighting *self, RenderState *state, + int x, int y, int z, PyObject *facemask) { + /* first, check for occlusion if the block is in the local chunk */ + if (x >= 0 && x < 16 && y >= 0 && y < 16 && z >= 0 && z < 128) { + unsigned char block = getArrayByte3D(state->blocks, x, y, z); + if (!is_transparent(block)) { + /* this face isn't visible, so don't draw anything */ + return; + } } + + float black_coeff = get_lighting_coefficient(self, state, x, y, z); + + PyObject *mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref + brightness(mask, black_coeff); + alpha_over(state->img, self->black_color, mask, state->imgx, state->imgy, 0, 0); + Py_DECREF(mask); } static int @@ -65,6 +122,19 @@ rendermode_lighting_start(void *data, RenderState *state) { self->facemasks[1] = PyTuple_GetItem(self->facemasks_py, 1); self->facemasks[2] = PyTuple_GetItem(self->facemasks_py, 2); + self->skylight = PyObject_GetAttrString(state->self, "skylight"); + self->blocklight = PyObject_GetAttrString(state->self, "blocklight"); + + self->left_blocks = PyObject_GetAttrString(state->self, "left_blocks"); + self->left_skylight = PyObject_GetAttrString(state->self, "left_skylight"); + self->left_blocklight = PyObject_GetAttrString(state->self, "left_blocklight"); + + self->right_blocks = PyObject_GetAttrString(state->self, "right_blocks"); + self->right_skylight = PyObject_GetAttrString(state->self, "right_skylight"); + self->right_blocklight = PyObject_GetAttrString(state->self, "right_blocklight"); + + self->calculate_darkness = calculate_darkness; + return 0; } @@ -75,6 +145,17 @@ rendermode_lighting_finish(void *data, RenderState *state) { Py_DECREF(self->black_color); Py_DECREF(self->facemasks_py); + Py_DECREF(self->skylight); + Py_DECREF(self->blocklight); + + Py_DECREF(self->left_blocks); + Py_DECREF(self->left_skylight); + Py_DECREF(self->left_blocklight); + + Py_DECREF(self->right_blocks); + Py_DECREF(self->right_skylight); + Py_DECREF(self->right_blocklight); + /* now chain up */ rendermode_normal.finish(data, state); } @@ -91,20 +172,12 @@ rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject rendermode_normal.draw(data, state, src, mask); RenderModeLighting* self = (RenderModeLighting *)data; - - PyObject *chunk = state->self; int x = state->x, y = state->y, z = state->z; - PyObject **facemasks = self->facemasks; - PyObject *black_color = self->black_color, *img = state->img; - int imgx = state->imgx, imgy = state->imgy; - // FIXME whole-block shading for transparent blocks - do_shading_for_face(chunk, x, y, z+1, facemasks[0], black_color, - img, imgx, imgy); - do_shading_for_face(chunk, x-1, y, z, facemasks[1], black_color, - img, imgx, imgy); - do_shading_for_face(chunk, x, y+1, z, facemasks[2], black_color, - img, imgx, imgy); + // TODO whole-block shading for transparent blocks + do_shading_for_face(self, state, x, y, z+1, self->facemasks[0]); + do_shading_for_face(self, state, x-1, y, z, self->facemasks[1]); + do_shading_for_face(self, state, x, y+1, z, self->facemasks[2]); } RenderModeInterface rendermode_lighting = { diff --git a/src/rendermodes.h b/src/rendermodes.h index 89f12f1..1a693da 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -70,6 +70,15 @@ typedef struct { PyObject *black_color, *facemasks_py; PyObject *facemasks[3]; + + /* extra block data, loaded off the chunk class */ + PyObject *skylight, *blocklight; + PyObject *left_blocks, *left_skylight, *left_blocklight; + PyObject *right_blocks, *right_skylight, *right_blocklight; + + /* can be overridden in derived rendermodes to control lighting + arguments are skylight, blocklight */ + float (*calculate_darkness)(unsigned char, unsigned char); } RenderModeLighting; extern RenderModeInterface rendermode_lighting; From ff464bde3c832d51b2634a2fa17b4de6e51772eb Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 23 Mar 2011 00:13:17 -0400 Subject: [PATCH 073/213] removed lighting/night/spawn bools, now we just reference rendermode directly --- chunk.py | 6 +++--- quadtree.py | 3 --- src/rendermodes.c | 9 ++++++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/chunk.py b/chunk.py index 1076edb..3644345 100644 --- a/chunk.py +++ b/chunk.py @@ -134,7 +134,7 @@ def render_to_image(chunkcoords, img, imgcoords, quadtreeobj, cave=False, queue= If the chunk doesn't exist, return False. Else, returns True.""" - a = ChunkRenderer(chunkcoords, quadtreeobj.world, quadtreeobj, queue) + a = ChunkRenderer(chunkcoords, quadtreeobj.world, quadtreeobj.rendermode, queue) try: a.chunk_render(img, imgcoords[0], imgcoords[1], cave) return True @@ -162,7 +162,7 @@ class NoSuchChunk(Exception): pass class ChunkRenderer(object): - def __init__(self, chunkcoords, worldobj, quadtreeobj, queue): + def __init__(self, chunkcoords, worldobj, rendermode, queue): """Make a new chunk renderer for the given chunk coordinates. chunkcoors should be a tuple: (chunkX, chunkY) @@ -189,7 +189,7 @@ class ChunkRenderer(object): self.chunkY = chunkcoords[1] self.world = worldobj - self.quadtree = quadtreeobj + self.rendermode = rendermode if self.world.useBiomeData: # make sure we've at least *tried* to load the color arrays in this process... diff --git a/quadtree.py b/quadtree.py index 7583b84..5e2ad13 100644 --- a/quadtree.py +++ b/quadtree.py @@ -84,9 +84,6 @@ class QuadtreeGen(object): self.imgformat = imgformat self.optimizeimg = optimizeimg - self.lighting = rendermode in ("lighting", "night", "spawn") - self.night = rendermode in ("night", "spawn") - self.spawn = rendermode in ("spawn",) self.rendermode = rendermode # Make the destination dir diff --git a/src/rendermodes.c b/src/rendermodes.c index 2141d99..0642d9d 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -16,16 +16,19 @@ */ #include "overviewer.h" +#include /* decides which render mode to use */ RenderModeInterface *get_render_mode(RenderState *state) { /* default: normal */ RenderModeInterface *iface = &rendermode_normal; - PyObject *quadtree = PyObject_GetAttrString(state->self, "quadtree"); - PyObject *lighting = PyObject_GetAttrString(quadtree, "lighting"); + PyObject *rendermode_py = PyObject_GetAttrString(state->self, "rendermode"); + const char *rendermode = PyString_AsString(rendermode_py); - if (PyObject_IsTrue(lighting)) + if (strcmp(rendermode, "lighting") == 0) { iface = &rendermode_lighting; + } + Py_DECREF(rendermode_py); return iface; } From 19a2eafcd54a12b493fc6291f0eeb48989a75b39 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 23 Mar 2011 00:19:54 -0400 Subject: [PATCH 074/213] fixed black lines showing up on lit maps, at chunk boundaries --- src/rendermode-lighting.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index b00c3aa..314ea53 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -69,6 +69,12 @@ get_lighting_coefficient(RenderModeLighting *self, RenderState *state, int x, in unsigned char block = getArrayByte3D(blocks, local_x, local_y, local_z); + /* if this block is opaque, use a fully-lit coeff instead + to prevent stippled lines along chunk boundaries! */ + if (!is_transparent(block)) { + return self->calculate_darkness(15, 0); + } + if (block == 44) { /* TODO special handling for half-blocks! */ } From 69bb56dcb419c1348608b7e05a230c95d4a92d8b Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 23 Mar 2011 00:33:59 -0400 Subject: [PATCH 075/213] added night rendermode --- setup.py | 2 +- src/rendermode-night.c | 66 ++++++++++++++++++++++++++++++++++++++++++ src/rendermodes.c | 2 ++ src/rendermodes.h | 12 ++++++-- 4 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 src/rendermode-night.c diff --git a/setup.py b/setup.py index e8f0503..2b306ad 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ except AttributeError: c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c'] -c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c'] +c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c', 'src/rendermode-night.c'] setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) # tell build_ext to build the extension in-place # (NOT in build/) diff --git a/src/rendermode-night.c b/src/rendermode-night.c new file mode 100644 index 0000000..5943b6a --- /dev/null +++ b/src/rendermode-night.c @@ -0,0 +1,66 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + +#include "overviewer.h" +#include + +/* figures out the black_coeff from a given skylight and blocklight, used in + lighting calculations -- note this is *different* from the one in + rendermode-lighting.c (the "skylight - 11" part) */ +static float calculate_darkness(unsigned char skylight, unsigned char blocklight) { + return 1.0f - powf(0.8f, 15.0 - MAX(blocklight, skylight - 11)); +} + +static int +rendermode_night_start(void *data, RenderState *state) { + /* first, chain up */ + int ret = rendermode_lighting.start(data, state); + if (ret != 0) + return ret; + + /* override the darkness function with our night version! */ + RenderModeNight* self = (RenderModeNight *)data; + self->parent.calculate_darkness = calculate_darkness; + + return 0; +} + +static void +rendermode_night_finish(void *data, RenderState *state) { + /* nothing special to do */ + rendermode_lighting.finish(data, state); +} + +static int +rendermode_night_occluded(void *data, RenderState *state) { + /* no special occlusion here */ + return rendermode_lighting.occluded(data, state); +} + +static void +rendermode_night_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { + /* nothing special to do */ + rendermode_lighting.draw(data, state, src, mask); +} + +RenderModeInterface rendermode_night = { + sizeof(RenderModeNight), + rendermode_night_start, + rendermode_night_finish, + rendermode_night_occluded, + rendermode_night_draw, +}; diff --git a/src/rendermodes.c b/src/rendermodes.c index 0642d9d..a5b57cb 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -27,6 +27,8 @@ RenderModeInterface *get_render_mode(RenderState *state) { if (strcmp(rendermode, "lighting") == 0) { iface = &rendermode_lighting; + } else if (strcmp(rendermode, "night") == 0) { + iface = &rendermode_night; } Py_DECREF(rendermode_py); diff --git a/src/rendermodes.h b/src/rendermodes.h index 1a693da..5cf185b 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -21,12 +21,13 @@ * * add a data struct and extern'd interface declaration below * * * fill in this interface struct in rendermode-(yourmode).c - * (see rendermodes.c for an example: the "normal" mode) + * (see rendermodes-normal.c for an example: the "normal" mode) * * * if you want to derive from (say) the "normal" mode, put * a RenderModeNormal entry at the top of your data struct, and * be sure to call your parent's functions in your own! - * (see rendermode-lighting.c for an example) + * (see rendermode-night.c for a simple example derived from + * the "lighting" mode) * * * add a condition to get_render_mode() in rendermodes.c */ @@ -82,4 +83,11 @@ typedef struct { } RenderModeLighting; extern RenderModeInterface rendermode_lighting; +/* NIGHT */ +typedef struct { + /* inherits from lighting */ + RenderModeLighting parent; +} RenderModeNight; +extern RenderModeInterface rendermode_night; + #endif /* __RENDERMODES_H_INCLUDED__ */ From ccddba2d817734c111b9e6ee4f19ff457f385913 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 23 Mar 2011 02:31:09 -0400 Subject: [PATCH 076/213] ported spawn render mode to C --- setup.py | 2 +- src/rendermode-lighting.c | 5 +- src/rendermode-spawn.c | 124 ++++++++++++++++++++++++++++++++++++++ src/rendermodes.c | 2 + src/rendermodes.h | 13 ++++ 5 files changed, 143 insertions(+), 3 deletions(-) create mode 100644 src/rendermode-spawn.c diff --git a/setup.py b/setup.py index 2b306ad..829ed7b 100644 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ except AttributeError: c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c'] -c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c', 'src/rendermode-night.c'] +c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c', 'src/rendermode-night.c', 'src/rendermode-spawn.c'] setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) # tell build_ext to build the extension in-place # (NOT in build/) diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index 314ea53..a8f2bc2 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -25,8 +25,9 @@ static float calculate_darkness(unsigned char skylight, unsigned char blocklight } /* loads the appropriate light data for the given (possibly non-local) - coordinates, and returns a black_coeff */ -static inline float + coordinates, and returns a black_coeff + this is exposed, so other (derived) rendermodes can use it */ +inline float get_lighting_coefficient(RenderModeLighting *self, RenderState *state, int x, int y, int z) { /* placeholders for later data arrays, coordinates */ PyObject *blocks = NULL; diff --git a/src/rendermode-spawn.c b/src/rendermode-spawn.c new file mode 100644 index 0000000..08e26de --- /dev/null +++ b/src/rendermode-spawn.c @@ -0,0 +1,124 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + +#include "overviewer.h" +#include + +static int +rendermode_spawn_start(void *data, RenderState *state) { + /* first, chain up */ + int ret = rendermode_night.start(data, state); + if (ret != 0) + return ret; + + /* now do custom initializations */ + RenderModeSpawn* self = (RenderModeSpawn *)data; + self->solid_blocks = PyObject_GetAttrString(state->chunk, "solid_blocks"); + self->nospawn_blocks = PyObject_GetAttrString(state->chunk, "nospawn_blocks"); + self->fluid_blocks = PyObject_GetAttrString(state->chunk, "fluid_blocks"); + self->red_color = PyObject_GetAttrString(state->chunk, "red_color"); + + return 0; +} + +static void +rendermode_spawn_finish(void *data, RenderState *state) { + /* first free all *our* stuff */ + RenderModeSpawn* self = (RenderModeSpawn *)data; + + Py_DECREF(self->solid_blocks); + Py_DECREF(self->nospawn_blocks); + Py_DECREF(self->fluid_blocks); + + /* now, chain up */ + rendermode_night.finish(data, state); +} + +static int +rendermode_spawn_occluded(void *data, RenderState *state) { + /* no special occlusion here */ + return rendermode_night.occluded(data, state); +} + +static void +rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { + /* different versions of self (spawn, lighting) */ + RenderModeSpawn* self = (RenderModeSpawn *)data; + RenderModeLighting *lighting = (RenderModeLighting *)self; + + int x = state->x, y = state->y, z = state->z; + PyObject *old_black_color = NULL; + + /* figure out the appropriate darkness: + this block for transparents, the block above for non-transparent */ + float darkness = 0.0; + if (is_transparent(state->block)) { + darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z); + } else { + darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z+1); + } + + /* if it's dark enough... */ + if (darkness > 0.8) { + PyObject *block_py = PyInt_FromLong(state->block); + + /* make sure it's solid */ + if (PySequence_Contains(self->solid_blocks, block_py)) { + int spawnable = 1; + + /* not spawnable if its in the nospawn list */ + if (PySequence_Contains(self->nospawn_blocks, block_py)) + spawnable = 0; + + /* check the block above for solid or fluid */ + if (spawnable && z != 127) { + PyObject *top_block_py = PyInt_FromLong(getArrayByte3D(state->blocks, x, y, z+1)); + if (PySequence_Contains(self->solid_blocks, top_block_py) || + PySequence_Contains(self->fluid_blocks, top_block_py)) { + + spawnable = 0; + } + + Py_DECREF(top_block_py); + } + + /* if we passed all the checks, replace black_color with red_color */ + if (spawnable) { + old_black_color = lighting->black_color; + lighting->black_color = self->red_color; + } + } + + Py_DECREF(block_py); + } + + /* draw normally */ + rendermode_night.draw(data, state, src, mask); + + /* reset black_color, if needed */ + if (old_black_color != NULL) { + lighting->black_color = old_black_color; + } +} + +RenderModeInterface rendermode_spawn = { + sizeof(RenderModeSpawn), + rendermode_spawn_start, + rendermode_spawn_finish, + rendermode_spawn_occluded, + rendermode_spawn_draw, +}; diff --git a/src/rendermodes.c b/src/rendermodes.c index a5b57cb..f4fbe89 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -29,6 +29,8 @@ RenderModeInterface *get_render_mode(RenderState *state) { iface = &rendermode_lighting; } else if (strcmp(rendermode, "night") == 0) { iface = &rendermode_night; + } else if (strcmp(rendermode, "spawn") == 0) { + iface = &rendermode_spawn; } Py_DECREF(rendermode_py); diff --git a/src/rendermodes.h b/src/rendermodes.h index 5cf185b..c8916e0 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -82,6 +82,7 @@ typedef struct { float (*calculate_darkness)(unsigned char, unsigned char); } RenderModeLighting; extern RenderModeInterface rendermode_lighting; +inline float get_lighting_coefficient(RenderModeLighting *self, RenderState *state, int x, int y, int z); /* NIGHT */ typedef struct { @@ -90,4 +91,16 @@ typedef struct { } RenderModeNight; extern RenderModeInterface rendermode_night; +/* SPAWN */ +typedef struct { + /* inherits from night */ + RenderModeNight parent; + + /* used to figure out which blocks are spawnable */ + PyObject *solid_blocks, *nospawn_blocks, *fluid_blocks; + /* replacement for black_color */ + PyObject *red_color; +} RenderModeSpawn; +extern RenderModeInterface rendermode_spawn; + #endif /* __RENDERMODES_H_INCLUDED__ */ From d36bd20e6a3151401fc45ff4af5ccd39ba29ec99 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 23 Mar 2011 02:52:19 -0400 Subject: [PATCH 077/213] fixed lighting for half-step blocks --- src/rendermode-lighting.c | 51 ++++++++++++++++++++++++++++++++++----- src/rendermode-spawn.c | 4 +-- src/rendermodes.h | 3 ++- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index a8f2bc2..e9b5aae 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -25,16 +25,28 @@ static float calculate_darkness(unsigned char skylight, unsigned char blocklight } /* loads the appropriate light data for the given (possibly non-local) - coordinates, and returns a black_coeff - this is exposed, so other (derived) rendermodes can use it */ + * coordinates, and returns a black_coeff this is exposed, so other (derived) + * rendermodes can use it + * + * authoratative is a return slot for whether or not this lighting calculation + * is true, or a guess. If we guessed, *authoratative will be false, but if it + * was calculated correctly from available light data, it will be true. You + * may (and probably should) pass NULL. + */ inline float -get_lighting_coefficient(RenderModeLighting *self, RenderState *state, int x, int y, int z) { +get_lighting_coefficient(RenderModeLighting *self, RenderState *state, + int x, int y, int z, int *authoratative) { + /* placeholders for later data arrays, coordinates */ PyObject *blocks = NULL; PyObject *skylight = NULL; PyObject *blocklight = NULL; int local_x = x, local_y = y, local_z = z; + /* defaults to "guess" until told otherwise */ + if (authoratative) + *authoratative = 0; + /* find out what chunk we're in, and translate accordingly */ if (x >= 0 && y < 16) { blocks = state->blocks; @@ -76,8 +88,31 @@ get_lighting_coefficient(RenderModeLighting *self, RenderState *state, int x, in return self->calculate_darkness(15, 0); } - if (block == 44) { - /* TODO special handling for half-blocks! */ + /* only do special half-step handling if no authoratative pointer was + passed in, which is a sign that we're recursing */ + if (block == 44 && authoratative == NULL) { + float average_gather = 0.0f; + unsigned int average_count = 0; + int auth; + float coeff; + + /* iterate through all surrounding blocks to take an average */ + int dx, dy, dz; + for (dx = -1; dx <= 1; dx += 2) { + for (dy = -1; dy <= 1; dy += 2) { + for (dz = -1; dz <= 1; dz += 2) { + coeff = get_lighting_coefficient(self, state, x+dx, y+dy, z+dz, &auth); + if (auth) { + average_gather += coeff; + average_count++; + } + } + } + } + + /* only return the average if at least one was authoratative */ + if (average_count > 0) + return average_gather / average_count; } if (block == 10 || block == 11) { @@ -88,6 +123,10 @@ get_lighting_coefficient(RenderModeLighting *self, RenderState *state, int x, in unsigned char skylevel = getArrayByte3D(skylight, local_x, local_y, local_z); unsigned char blocklevel = getArrayByte3D(blocklight, local_x, local_y, local_z); + /* no longer a guess */ + if (authoratative) + *authoratative = 1; + return self->calculate_darkness(skylevel, blocklevel); } @@ -105,7 +144,7 @@ do_shading_for_face(RenderModeLighting *self, RenderState *state, } } - float black_coeff = get_lighting_coefficient(self, state, x, y, z); + float black_coeff = get_lighting_coefficient(self, state, x, y, z, NULL); PyObject *mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref brightness(mask, black_coeff); diff --git a/src/rendermode-spawn.c b/src/rendermode-spawn.c index 08e26de..aa7a8c6 100644 --- a/src/rendermode-spawn.c +++ b/src/rendermode-spawn.c @@ -67,9 +67,9 @@ rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *m this block for transparents, the block above for non-transparent */ float darkness = 0.0; if (is_transparent(state->block)) { - darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z); + darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z, NULL); } else { - darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z+1); + darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z+1, NULL); } /* if it's dark enough... */ diff --git a/src/rendermodes.h b/src/rendermodes.h index c8916e0..da94343 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -82,7 +82,8 @@ typedef struct { float (*calculate_darkness)(unsigned char, unsigned char); } RenderModeLighting; extern RenderModeInterface rendermode_lighting; -inline float get_lighting_coefficient(RenderModeLighting *self, RenderState *state, int x, int y, int z); +inline float get_lighting_coefficient(RenderModeLighting *self, RenderState *state, + int x, int y, int z, int *authoratative); /* NIGHT */ typedef struct { From c18d6924eac04aa2190024dc1154033859a1f89e Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 23 Mar 2011 03:27:42 -0400 Subject: [PATCH 078/213] fixed transparent block lighting --- src/composite.c | 27 ++++++++++++++++++--------- src/overviewer.h | 2 +- src/rendermode-lighting.c | 17 +++++++++++------ 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/composite.c b/src/composite.c index b7454c8..3400453 100644 --- a/src/composite.c +++ b/src/composite.c @@ -59,33 +59,42 @@ imaging_python_to_c(PyObject *obj) } /** - * img should be an Image object, type 'L' + * img should be an Image object, type 'L' or 'RGBA' + * for RGBA images, operates on the alpha only * factor should be between 0 and 1, inclusive */ -PyObject *brightness(PyObject *img, double factor) { +PyObject *brightness(PyObject *img, float factor) { Imaging imDest; imDest = imaging_python_to_c(img); - assert(imDest); + if (!imDest) + return NULL; + if (strcmp(imDest->mode, "RGBA") != 0 && strcmp(imDest->mode, "L") != 0) { + PyErr_SetString(PyExc_ValueError, + "given image does not have mode \"RGBA\" or \"L\""); + return NULL; + } + + /* how far into image the first alpha byte resides */ + int offset = (imDest->pixelsize == 4 ? 3 : 0); + /* how many bytes to skip to get to the next alpha byte */ + int stride = imDest->pixelsize; + int x, y; int xsize = imDest->xsize; int ysize = imDest->ysize; for (y = 0; y < ysize; y++) { - UINT8 *out = (UINT8 *)imDest->image[y]; - //UINT8 *outmask = (UINT8 *)imDest->image[y] + 3; + UINT8 *out = (UINT8 *)imDest->image[y] + offset; for (x = 0; x < xsize; x++) { - //printf("old out: %d\n", *out); *out *= factor; - //printf("new out: %d\n", *out); - out++; + out += stride; } } return NULL; - } /* the alpha_over function, in a form that can be called from C */ diff --git a/src/overviewer.h b/src/overviewer.h index 5ae9079..d704a83 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -41,7 +41,7 @@ Imaging imaging_python_to_c(PyObject *obj); PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, int dy, int xsize, int ysize); PyObject *alpha_over_wrap(PyObject *self, PyObject *args); -PyObject *brightness(PyObject *img, double factor); +PyObject *brightness(PyObject *img, float factor); /* in iterate.c */ typedef struct { diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index e9b5aae..10d5bcd 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -133,8 +133,8 @@ get_lighting_coefficient(RenderModeLighting *self, RenderState *state, /* shades the drawn block with the given facemask/black_color, based on the lighting results from (x, y, z) */ static inline void -do_shading_for_face(RenderModeLighting *self, RenderState *state, - int x, int y, int z, PyObject *facemask) { +do_shading_with_mask(RenderModeLighting *self, RenderState *state, + int x, int y, int z, PyObject *facemask) { /* first, check for occlusion if the block is in the local chunk */ if (x >= 0 && x < 16 && y >= 0 && y < 16 && z >= 0 && z < 128) { unsigned char block = getArrayByte3D(state->blocks, x, y, z); @@ -220,10 +220,15 @@ rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject RenderModeLighting* self = (RenderModeLighting *)data; int x = state->x, y = state->y, z = state->z; - // TODO whole-block shading for transparent blocks - do_shading_for_face(self, state, x, y, z+1, self->facemasks[0]); - do_shading_for_face(self, state, x-1, y, z, self->facemasks[1]); - do_shading_for_face(self, state, x, y+1, z, self->facemasks[2]); + if (is_transparent(state->block)) { + /* transparent: do shading on whole block */ + do_shading_with_mask(self, state, x, y, z, mask); + } else { + /* opaque: do per-face shading */ + do_shading_with_mask(self, state, x, y, z+1, self->facemasks[0]); + do_shading_with_mask(self, state, x-1, y, z, self->facemasks[1]); + do_shading_with_mask(self, state, x, y+1, z, self->facemasks[2]); + } } RenderModeInterface rendermode_lighting = { From 977bf09a1240d41edc5b4ec6a3e3c7bf7850dbb1 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Wed, 23 Mar 2011 20:34:49 -0400 Subject: [PATCH 079/213] Bunch of changes to make VS happy Mostly variable declarations moved to the top of blocks. is_transparent can't be inline, since it's needed in several places --- src/composite.c | 12 +++++++----- src/iterate.c | 14 ++++++++++---- src/rendermode-lighting.c | 25 +++++++++++++++++-------- src/rendermode-night.c | 4 +++- src/rendermode-spawn.c | 4 +++- 5 files changed, 40 insertions(+), 19 deletions(-) diff --git a/src/composite.c b/src/composite.c index 3400453..162c29b 100644 --- a/src/composite.c +++ b/src/composite.c @@ -66,6 +66,8 @@ imaging_python_to_c(PyObject *obj) PyObject *brightness(PyObject *img, float factor) { Imaging imDest; + int offset, stride, x, y, xsize, ysize; + imDest = imaging_python_to_c(img); if (!imDest) return NULL; @@ -77,14 +79,14 @@ PyObject *brightness(PyObject *img, float factor) { } /* how far into image the first alpha byte resides */ - int offset = (imDest->pixelsize == 4 ? 3 : 0); + offset = (imDest->pixelsize == 4 ? 3 : 0); /* how many bytes to skip to get to the next alpha byte */ - int stride = imDest->pixelsize; + stride = imDest->pixelsize; - int x, y; + x, y; - int xsize = imDest->xsize; - int ysize = imDest->ysize; + xsize = imDest->xsize; + ysize = imDest->ysize; for (y = 0; y < ysize; y++) { UINT8 *out = (UINT8 *)imDest->image[y] + offset; diff --git a/src/iterate.c b/src/iterate.c index 3997f5b..d9f4d52 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -49,7 +49,7 @@ int init_chunk_render(void) { } -inline int +int is_transparent(unsigned char b) { PyObject *block = PyInt_FromLong(b); int ret = PySequence_Contains(transparent_blocks, block); @@ -70,6 +70,12 @@ chunk_render(PyObject *self, PyObject *args) { int imgsize0, imgsize1; PyObject *blocks_py; + + RenderModeInterface *rendermode; + + void *rm_data; + + PyObject *t = NULL; if (!PyArg_ParseTuple(args, "OOiiO", &state.self, &state.img, &xoff, &yoff, &blockdata_expanded)) return Py_BuildValue("i", "-1"); @@ -79,8 +85,8 @@ chunk_render(PyObject *self, PyObject *args) { state.chunk = chunk_mod; /* set up the render mode */ - RenderModeInterface *rendermode = get_render_mode(&state); - void* rm_data = malloc(rendermode->data_size); + rendermode = get_render_mode(&state); + rm_data = malloc(rendermode->data_size); if (rendermode->start(rm_data, &state)) { free(rm_data); return Py_BuildValue("i", "-1"); @@ -146,7 +152,7 @@ chunk_render(PyObject *self, PyObject *args) { } // everything stored here will be a borrowed ref - PyObject *t = NULL; + /* get the texture and mask from block type / ancil. data */ if (!PySequence_Contains(special_blocks, blockid)) { /* t = textures.blockmap[blockid] */ diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index 10d5bcd..7646f70 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -42,6 +42,7 @@ get_lighting_coefficient(RenderModeLighting *self, RenderState *state, PyObject *skylight = NULL; PyObject *blocklight = NULL; int local_x = x, local_y = y, local_z = z; + unsigned char block, skylevel, blocklevel; /* defaults to "guess" until told otherwise */ if (authoratative) @@ -80,7 +81,7 @@ get_lighting_coefficient(RenderModeLighting *self, RenderState *state, return self->calculate_darkness(15, 0); } - unsigned char block = getArrayByte3D(blocks, local_x, local_y, local_z); + block = getArrayByte3D(blocks, local_x, local_y, local_z); /* if this block is opaque, use a fully-lit coeff instead to prevent stippled lines along chunk boundaries! */ @@ -120,8 +121,8 @@ get_lighting_coefficient(RenderModeLighting *self, RenderState *state, return 0.0f; } - unsigned char skylevel = getArrayByte3D(skylight, local_x, local_y, local_z); - unsigned char blocklevel = getArrayByte3D(blocklight, local_x, local_y, local_z); + skylevel = getArrayByte3D(skylight, local_x, local_y, local_z); + blocklevel = getArrayByte3D(blocklight, local_x, local_y, local_z); /* no longer a guess */ if (authoratative) @@ -135,6 +136,9 @@ get_lighting_coefficient(RenderModeLighting *self, RenderState *state, static inline void do_shading_with_mask(RenderModeLighting *self, RenderState *state, int x, int y, int z, PyObject *facemask) { + float black_coeff; + PyObject *mask; + /* first, check for occlusion if the block is in the local chunk */ if (x >= 0 && x < 16 && y >= 0 && y < 16 && z >= 0 && z < 128) { unsigned char block = getArrayByte3D(state->blocks, x, y, z); @@ -144,9 +148,9 @@ do_shading_with_mask(RenderModeLighting *self, RenderState *state, } } - float black_coeff = get_lighting_coefficient(self, state, x, y, z, NULL); + black_coeff = get_lighting_coefficient(self, state, x, y, z, NULL); - PyObject *mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref + mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref brightness(mask, black_coeff); alpha_over(state->img, self->black_color, mask, state->imgx, state->imgy, 0, 0); Py_DECREF(mask); @@ -154,12 +158,14 @@ do_shading_with_mask(RenderModeLighting *self, RenderState *state, static int rendermode_lighting_start(void *data, RenderState *state) { + RenderModeLighting* self; + /* first, chain up */ int ret = rendermode_normal.start(data, state); if (ret != 0) return ret; - RenderModeLighting* self = (RenderModeLighting *)data; + self = (RenderModeLighting *)data; self->black_color = PyObject_GetAttrString(state->chunk, "black_color"); self->facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks"); @@ -214,11 +220,14 @@ rendermode_lighting_occluded(void *data, RenderState *state) { static void rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { + RenderModeLighting* self; + int x, y, z; + /* first, chain up */ rendermode_normal.draw(data, state, src, mask); - RenderModeLighting* self = (RenderModeLighting *)data; - int x = state->x, y = state->y, z = state->z; + self = (RenderModeLighting *)data; + x = state->x, y = state->y, z = state->z; if (is_transparent(state->block)) { /* transparent: do shading on whole block */ diff --git a/src/rendermode-night.c b/src/rendermode-night.c index 5943b6a..d4b1eb4 100644 --- a/src/rendermode-night.c +++ b/src/rendermode-night.c @@ -27,13 +27,15 @@ static float calculate_darkness(unsigned char skylight, unsigned char blocklight static int rendermode_night_start(void *data, RenderState *state) { + RenderModeNight* self; + /* first, chain up */ int ret = rendermode_lighting.start(data, state); if (ret != 0) return ret; /* override the darkness function with our night version! */ - RenderModeNight* self = (RenderModeNight *)data; + self = (RenderModeNight *)data; self->parent.calculate_darkness = calculate_darkness; return 0; diff --git a/src/rendermode-spawn.c b/src/rendermode-spawn.c index aa7a8c6..1a7c96a 100644 --- a/src/rendermode-spawn.c +++ b/src/rendermode-spawn.c @@ -20,13 +20,15 @@ static int rendermode_spawn_start(void *data, RenderState *state) { + RenderModeSpawn* self; + /* first, chain up */ int ret = rendermode_night.start(data, state); if (ret != 0) return ret; /* now do custom initializations */ - RenderModeSpawn* self = (RenderModeSpawn *)data; + self = (RenderModeSpawn *)data; self->solid_blocks = PyObject_GetAttrString(state->chunk, "solid_blocks"); self->nospawn_blocks = PyObject_GetAttrString(state->chunk, "nospawn_blocks"); self->fluid_blocks = PyObject_GetAttrString(state->chunk, "fluid_blocks"); From 57e99725df79a893b40a4698b865c3e3d10d83c0 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Wed, 23 Mar 2011 21:31:51 -0400 Subject: [PATCH 080/213] Removed noise leftover from last commit --- src/composite.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/composite.c b/src/composite.c index 162c29b..8b507bc 100644 --- a/src/composite.c +++ b/src/composite.c @@ -83,8 +83,6 @@ PyObject *brightness(PyObject *img, float factor) { /* how many bytes to skip to get to the next alpha byte */ stride = imDest->pixelsize; - x, y; - xsize = imDest->xsize; ysize = imDest->ysize; From 7daa1bac9d3c3647124ec7fb0183e00206f3c319 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Wed, 23 Mar 2011 22:52:41 -0400 Subject: [PATCH 081/213] Now supports both MSVC and mingw32 --- setup.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 829ed7b..b24532a 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,7 @@ from distutils.core import setup, Extension from distutils.command.build import build from distutils.command.clean import clean +from distutils.command.build_ext import build_ext from distutils.dir_util import remove_tree from distutils import log import os, os.path @@ -46,7 +47,7 @@ except AttributeError: c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c'] c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c', 'src/rendermode-night.c', 'src/rendermode-spawn.c'] -setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], extra_link_args=["/MANIFEST"] if platform.system() == "Windows" else [])) +setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], extra_link_args=[])) # tell build_ext to build the extension in-place # (NOT in build/) setup_kwargs['options']['build_ext'] = {'inplace' : 1} @@ -75,8 +76,25 @@ class CustomClean(clean): else: log.debug("'%s' does not exist -- can't clean it", pretty_fname) + +class CustomBuild(build_ext): + def build_extensions(self): + c = self.compiler.compiler_type + if c == "msvc": + # customize the build options for this compilier + for e in self.extensions: + e.extra_link_args.append("/MANIFEST") + + build_ext.build_extensions(self) + + + setup_kwargs['cmdclass']['clean'] = CustomClean +setup_kwargs['cmdclass']['build_ext'] = CustomBuild ### setup(**setup_kwargs) + + +print "\nBuild Complete" From 4da53b4f9cdbee51fe835e2baf20e6ba07c385ca Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Thu, 24 Mar 2011 23:40:01 +0100 Subject: [PATCH 082/213] Load the 4 adjacent blocks in iterate.c --- src/iterate.c | 23 +++++++++++++++++++---- src/overviewer.h | 4 ++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index d9f4d52..c434de1 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -70,6 +70,10 @@ chunk_render(PyObject *self, PyObject *args) { int imgsize0, imgsize1; PyObject *blocks_py; + PyObject *left_blocks_py; + PyObject *right_blocks_py; + PyObject *up_left_blocks_py; + PyObject *up_right_blocks_py; RenderModeInterface *rendermode; @@ -109,10 +113,17 @@ chunk_render(PyObject *self, PyObject *args) { blocks_py = PyObject_GetAttrString(state.self, "blocks"); state.blocks = blocks_py; - /* - PyObject *left_blocks = PyObject_GetAttrString(chunk, "left_blocks"); - PyObject *right_blocks = PyObject_GetAttrString(chunk, "right_blocks"); - */ + left_blocks_py = PyObject_GetAttrString(state.self, "left_blocks"); + state.left_blocks = left_blocks_py; + + right_blocks_py = PyObject_GetAttrString(state.self, "right_blocks"); + state.right_blocks = right_blocks_py; + + up_left_blocks_py = PyObject_GetAttrString(state.self, "up_left_blocks"); + state.up_left_blocks = up_left_blocks_py; + + up_right_blocks_py = PyObject_GetAttrString(state.self, "up_right_blocks"); + state.up_right_blocks = up_right_blocks_py; for (state.x = 15; state.x > -1; state.x--) { for (state.y = 0; state.y < 16; state.y++) { @@ -203,6 +214,10 @@ chunk_render(PyObject *self, PyObject *args) { free(rm_data); Py_DECREF(blocks_py); + Py_XDECREF(left_blocks_py); + Py_XDECREF(right_blocks_py); + Py_XDECREF(up_left_blocks_py); + Py_XDECREF(up_right_blocks_py); return Py_BuildValue("i",2); } diff --git a/src/overviewer.h b/src/overviewer.h index d704a83..b11ba03 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -62,6 +62,10 @@ typedef struct { int x, y, z; unsigned char block; PyObject *blocks; + PyObject *up_left_blocks; + PyObject *up_right_blocks; + PyObject *left_blocks; + PyObject *right_blocks; } RenderState; int init_chunk_render(void); int is_transparent(unsigned char b); From d04bea2b66969ac38c544bbb658d300eef94a287 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Thu, 24 Mar 2011 23:43:11 +0100 Subject: [PATCH 083/213] Add functions generate_pseudo_data and check_adjacent_blocks to iterate.c Fences working. --- src/iterate.c | 91 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 2 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index c434de1..12c9523 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -58,6 +58,94 @@ is_transparent(unsigned char b) { } + +unsigned char + check_adjacent_blocks(RenderState *state, unsigned char blockid) { + /* + * Generates a pseudo ancillary data for blocks that depend of + * what are surrounded and don't have ancillary data. This + * function is through generate_pseudo_data. + * + * This uses a binary number of 4 digits to encode the info. + * The encode is: + * + * Bit: 1 2 3 4 + * Side: +x +y -x -y + * Values: bit = 0 -> The corresponding side block has different blockid + * bit = 1 -> The corresponding side block has same blockid + * Example: if the bit1 is 1 that means that there is a block with + * blockid in the side of the +x direction. + */ + + unsigned char pdata=0; + + if (state->x == 15) { /* +x direction */ + if (state->up_right_blocks != NULL) { /* just in case we are in the end of the world */ + if (getArrayByte3D(state->up_right_blocks, 0, state->y, state->z) == blockid) { + pdata = pdata|0b1000; + } + } + } else { + if (getArrayByte3D(state->blocks, state->x + 1, state->y, state->z) == blockid) { + pdata = pdata|0b1000; + } + } + + if (state->y == 15) { /* +y direction*/ + if (state->right_blocks != NULL) { + if (getArrayByte3D(state->right_blocks, state->x, 0, state->z) == blockid) { + pdata = pdata|0b0100; + } + } + } else { + if (getArrayByte3D(state->blocks, state->x, state->y + 1, state->z) == blockid) { + pdata = pdata|0b0100; + } + } + + if (state->x == 0) { /* -x direction*/ + if (state->left_blocks != NULL) { + if (getArrayByte3D(state->left_blocks, 15, state->y, state->z) == blockid) { + pdata = pdata|0b0010; + } + } + } else { + if (getArrayByte3D(state->blocks, state->x - 1, state->y, state->z) == blockid) { + pdata = pdata|0b0010; + } + } + + if (state->y == 0) { /* -y direction */ + if (state->up_left_blocks != NULL) { + if (getArrayByte3D(state->up_left_blocks, state->x, 15, state->z) == blockid) { + pdata = pdata|0b0001; + } + } + } else { + if (getArrayByte3D(state->blocks, state->x, state->y - 1, state->z) == blockid) { + pdata = pdata|0b0001; + } + } + + return pdata; +} + + +unsigned char +generate_pseudo_data(RenderState *state) { + /* + * Generates a fake ancillary data for blocks that are drawn + * depending on what are surrounded. + */ + + if (state->block == 85) { /* fences */ + return check_adjacent_blocks(state, state->block); + } + return 0; + +} + + /* TODO triple check this to make sure reference counting is correct */ PyObject* chunk_render(PyObject *self, PyObject *args) { @@ -173,8 +261,7 @@ chunk_render(PyObject *self, PyObject *args) { unsigned char ancilData = getArrayByte3D(blockdata_expanded, state.x, state.y, state.z); if (state.block == 85) { - /* fence. skip the generate_pseudo_ancildata for now */ - continue; + ancilData = generate_pseudo_data(&state); } tmp = PyTuple_New(2); From 3fdf70b61d3a45754a70d726ee4e59b8fd794883 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Thu, 24 Mar 2011 23:47:17 +0100 Subject: [PATCH 084/213] Fix problem with get_lvldata in chunk.py: filename = None doesn't mean chunk corrupt. --- chunk.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chunk.py b/chunk.py index 3644345..63de064 100644 --- a/chunk.py +++ b/chunk.py @@ -50,6 +50,10 @@ def get_lvldata(world, filename, x, y, retries=2): """Takes a filename and chunkcoords and returns the Level struct, which contains all the level info""" + # non existent region file doesn't mean corrupt chunk. + if filename == None: + return None + try: d = world.load_from_region(filename, x, y) except Exception, e: From 4f5bf93b49f2730fff645abcb3bcef0e7039690e Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Thu, 24 Mar 2011 23:54:58 +0100 Subject: [PATCH 085/213] Fix "that NULL should be Py_None" --- src/iterate.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index 12c9523..6b35405 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -80,7 +80,7 @@ unsigned char unsigned char pdata=0; if (state->x == 15) { /* +x direction */ - if (state->up_right_blocks != NULL) { /* just in case we are in the end of the world */ + if (state->up_right_blocks != Py_None) { /* just in case we are in the end of the world */ if (getArrayByte3D(state->up_right_blocks, 0, state->y, state->z) == blockid) { pdata = pdata|0b1000; } @@ -92,7 +92,7 @@ unsigned char } if (state->y == 15) { /* +y direction*/ - if (state->right_blocks != NULL) { + if (state->right_blocks != Py_None) { if (getArrayByte3D(state->right_blocks, state->x, 0, state->z) == blockid) { pdata = pdata|0b0100; } @@ -104,7 +104,7 @@ unsigned char } if (state->x == 0) { /* -x direction*/ - if (state->left_blocks != NULL) { + if (state->left_blocks != Py_None) { if (getArrayByte3D(state->left_blocks, 15, state->y, state->z) == blockid) { pdata = pdata|0b0010; } @@ -116,7 +116,7 @@ unsigned char } if (state->y == 0) { /* -y direction */ - if (state->up_left_blocks != NULL) { + if (state->up_left_blocks != Py_None) { if (getArrayByte3D(state->up_left_blocks, state->x, 15, state->z) == blockid) { pdata = pdata|0b0001; } From 2d64d2d84db00c6b05b055fae2f8f4f16fd0bc0d Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Thu, 24 Mar 2011 19:41:08 -0400 Subject: [PATCH 086/213] changed 0b(...) constants into (1 << n) equivalents 0b(...) isn't supported by GCC :( --- src/iterate.c | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index 6b35405..8b93cd8 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -69,6 +69,7 @@ unsigned char * This uses a binary number of 4 digits to encode the info. * The encode is: * + * 0b1234: * Bit: 1 2 3 4 * Side: +x +y -x -y * Values: bit = 0 -> The corresponding side block has different blockid @@ -82,48 +83,48 @@ unsigned char if (state->x == 15) { /* +x direction */ if (state->up_right_blocks != Py_None) { /* just in case we are in the end of the world */ if (getArrayByte3D(state->up_right_blocks, 0, state->y, state->z) == blockid) { - pdata = pdata|0b1000; + pdata = pdata|(1 << 3); } } } else { if (getArrayByte3D(state->blocks, state->x + 1, state->y, state->z) == blockid) { - pdata = pdata|0b1000; + pdata = pdata|(1 << 3); } } if (state->y == 15) { /* +y direction*/ if (state->right_blocks != Py_None) { if (getArrayByte3D(state->right_blocks, state->x, 0, state->z) == blockid) { - pdata = pdata|0b0100; + pdata = pdata|(1 << 2); } } } else { if (getArrayByte3D(state->blocks, state->x, state->y + 1, state->z) == blockid) { - pdata = pdata|0b0100; + pdata = pdata|(1 << 2); } } if (state->x == 0) { /* -x direction*/ if (state->left_blocks != Py_None) { if (getArrayByte3D(state->left_blocks, 15, state->y, state->z) == blockid) { - pdata = pdata|0b0010; + pdata = pdata|(1 << 1); } } } else { if (getArrayByte3D(state->blocks, state->x - 1, state->y, state->z) == blockid) { - pdata = pdata|0b0010; + pdata = pdata|(1 << 1); } } if (state->y == 0) { /* -y direction */ if (state->up_left_blocks != Py_None) { if (getArrayByte3D(state->up_left_blocks, state->x, 15, state->z) == blockid) { - pdata = pdata|0b0001; + pdata = pdata|(1 << 0); } } } else { if (getArrayByte3D(state->blocks, state->x, state->y - 1, state->z) == blockid) { - pdata = pdata|0b0001; + pdata = pdata|(1 << 0); } } From d84af9fa4beae76d1a26cee25b88b13cc1f5fe8c Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Thu, 24 Mar 2011 19:44:21 -0400 Subject: [PATCH 087/213] changed lighting rendermode to use new state variables --- src/rendermode-lighting.c | 12 ++---------- src/rendermodes.h | 6 +++--- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index 7646f70..d83522e 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -55,12 +55,12 @@ get_lighting_coefficient(RenderModeLighting *self, RenderState *state, blocklight = self->blocklight; } else if (x < 0) { local_x += 16; - blocks = self->left_blocks; + blocks = state->left_blocks; skylight = self->left_skylight; blocklight = self->left_blocklight; } else if (y >= 16) { local_y -= 16; - blocks = self->right_blocks; + blocks = state->right_blocks; skylight = self->right_skylight; blocklight = self->right_blocklight; } @@ -176,12 +176,8 @@ rendermode_lighting_start(void *data, RenderState *state) { self->skylight = PyObject_GetAttrString(state->self, "skylight"); self->blocklight = PyObject_GetAttrString(state->self, "blocklight"); - - self->left_blocks = PyObject_GetAttrString(state->self, "left_blocks"); self->left_skylight = PyObject_GetAttrString(state->self, "left_skylight"); self->left_blocklight = PyObject_GetAttrString(state->self, "left_blocklight"); - - self->right_blocks = PyObject_GetAttrString(state->self, "right_blocks"); self->right_skylight = PyObject_GetAttrString(state->self, "right_skylight"); self->right_blocklight = PyObject_GetAttrString(state->self, "right_blocklight"); @@ -199,12 +195,8 @@ rendermode_lighting_finish(void *data, RenderState *state) { Py_DECREF(self->skylight); Py_DECREF(self->blocklight); - - Py_DECREF(self->left_blocks); Py_DECREF(self->left_skylight); Py_DECREF(self->left_blocklight); - - Py_DECREF(self->right_blocks); Py_DECREF(self->right_skylight); Py_DECREF(self->right_blocklight); diff --git a/src/rendermodes.h b/src/rendermodes.h index da94343..5ebd18b 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -72,10 +72,10 @@ typedef struct { PyObject *black_color, *facemasks_py; PyObject *facemasks[3]; - /* extra block data, loaded off the chunk class */ + /* extra data, loaded off the chunk class */ PyObject *skylight, *blocklight; - PyObject *left_blocks, *left_skylight, *left_blocklight; - PyObject *right_blocks, *right_skylight, *right_blocklight; + PyObject *left_skylight, *left_blocklight; + PyObject *right_skylight, *right_blocklight; /* can be overridden in derived rendermodes to control lighting arguments are skylight, blocklight */ From df6124b425912a6a29eea7354491f5225cf3a08a Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Thu, 24 Mar 2011 19:51:33 -0400 Subject: [PATCH 088/213] Remove windows-style newlines --- nbt.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/nbt.py b/nbt.py index 52d7545..73730a0 100644 --- a/nbt.py +++ b/nbt.py @@ -34,16 +34,16 @@ def _file_loader(func): def load(fileobj): return NBTFileReader(fileobj).read_all() -def load_from_region(filename, x, y): - nbt = load_region(filename).load_chunk(x, y) - if nbt is None: +def load_from_region(filename, x, y): + nbt = load_region(filename).load_chunk(x, y) + if nbt is None: return None ## return none. I think this is who we should indicate missing chunks - #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) - return nbt.read_all() - -def load_region(filename): - return MCRFileReader(filename) - + #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) + return nbt.read_all() + +def load_region(filename): + return MCRFileReader(filename) + class NBTFileReader(object): def __init__(self, fileobj, is_gzip=True): if is_gzip: @@ -180,9 +180,9 @@ class MCRFileReader(object): chunks (as instances of NBTFileReader), getting chunk timestamps, and for listing chunks contained in the file.""" - def __init__(self, filename): - self._file = None - self._filename = filename + def __init__(self, filename): + self._file = None + self._filename = filename # cache used when the entire header tables are read in get_chunks() self._locations = None self._timestamps = None @@ -252,7 +252,7 @@ class MCRFileReader(object): return timestamp - def get_chunk_info(self,closeFile = True): + def get_chunk_info(self,closeFile = True): """Return a list of all chunks contained in this region file, as a list of (x, y) coordinate tuples. To load these chunks, provide these coordinates to load_chunk().""" @@ -260,7 +260,7 @@ class MCRFileReader(object): if self._chunks: return self._chunks - if self._file is None: + if self._file is None: self._file = open(self._filename,'rb'); self._chunks = [] @@ -283,11 +283,11 @@ class MCRFileReader(object): for x in xrange(32): timestamp = self._read_chunk_timestamp() self._timestamps.append(timestamp) - - if closeFile: - #free the file object since it isn't safe to be reused in child processes (seek point goes wonky!) - self._file.close() - self._file = None + + if closeFile: + #free the file object since it isn't safe to be reused in child processes (seek point goes wonky!) + self._file.close() + self._file = None return self._chunks def get_chunk_timestamp(self, x, y): @@ -320,13 +320,13 @@ class MCRFileReader(object): x = x % 32 y = y % 32 if self._locations is None: - self.get_chunk_info() + self.get_chunk_info() location = self._locations[x + y * 32] if location is None: return None - if self._file is None: + if self._file is None: self._file = open(self._filename,'rb'); # seek to the data self._file.seek(location[0]) From c25e2cce0f3af95eb3b288e09100547bb3211804 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Thu, 24 Mar 2011 21:58:25 -0400 Subject: [PATCH 089/213] Implement a --version option to overviewer Should also work with py2exe binary kits --- overviewer.py | 30 ++++++++++++++++++++++++++++++ setup.py | 19 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/overviewer.py b/overviewer.py index 4be9d92..8357212 100755 --- a/overviewer.py +++ b/overviewer.py @@ -49,6 +49,23 @@ helptext = """ %prog [OPTIONS] %prog -d [tiles dest dir]""" + +def findGitVersion(): + if os.path.exists(".git"): + with open(os.path.join(".git","HEAD")) as f: + data = f.read().strip() + if data.startswith("ref: "): + with open(os.path.join(".git", data[5:])) as g: + return g.read().strip() + else: + return data + else: + try: + import overviewer_version + return overviewer_version.VERSION + except: + return "unknown" + def main(): try: cpus = multiprocessing.cpu_count() @@ -56,6 +73,7 @@ def main(): cpus = 1 parser = ConfigOptionParser(usage=helptext, config="settings.py") + parser.add_option("-V", "--version", dest="version", help="Displays version information and then exits", action="store_true") parser.add_option("-p", "--processes", dest="procs", help="How many worker processes to start. Default %s" % cpus, default=cpus, action="store", type="int") parser.add_option("-z", "--zoom", dest="zoom", help="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", action="store", type="int", configFileOnly=True) parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true", commandLineOnly=True) @@ -72,6 +90,18 @@ def main(): options, args = parser.parse_args() + if options.version: + print "Minecraft-Overviewer" + print "Git version: %s" % findGitVersion() + try: + import overviewer_version + if hasattr(sys, "frozen"): + print "py2exe version build on %s" % overviewer_version.BUILD_DATE + print "Build machine: %s %s" % (overviewer_version.BUILD_PLATFORM, overviewer_version.BUILD_OS) + except: + pass + sys.exit(0) + if len(args) < 1: print "You need to give me your world number or directory" parser.print_help() diff --git a/setup.py b/setup.py index b24532a..5e5f809 100644 --- a/setup.py +++ b/setup.py @@ -7,6 +7,7 @@ from distutils import log import os, os.path import glob import platform +import time try: import py2exe @@ -88,10 +89,26 @@ class CustomBuild(build_ext): build_ext.build_extensions(self) +if py2exe is not None: +# define a subclass of py2exe to build our version file on the fly + class CustomPy2exe(py2exe.build_exe.py2exe): + def run(self): + try: + import overviewer + f = open("overviewer_version.py", "w") + f.write("VERSION=%r\n" % overviewer.findGitVersion()) + f.write("BUILD_DATE=%r\n" % time.asctime()) + f.write("BUILD_PLATFORM=%r\n" % platform.processor()) + f.write("BUILD_OS=%r\n" % platform.platform()) + f.close() + setup_kwargs['data_files'].append(('.', ['overviewer_version.py'])) + except: + print "WARNING: failed to build overview_version file" + py2exe.build_exe.py2exe.run(self) + setup_kwargs['cmdclass']['py2exe'] = CustomPy2exe setup_kwargs['cmdclass']['clean'] = CustomClean setup_kwargs['cmdclass']['build_ext'] = CustomBuild - ### setup(**setup_kwargs) From 172198558ae75cc681901eda5c6230cd37041842 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Thu, 24 Mar 2011 22:45:26 -0400 Subject: [PATCH 090/213] Moved helper function out of overviewer.py and into util.py Solves a dependency problem where you can't import overviewer unless the extension is built --- overviewer.py | 18 ++---------------- setup.py | 4 ++-- util.py | 18 ++++++++++++++++++ 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/overviewer.py b/overviewer.py index 8357212..ffc3882 100755 --- a/overviewer.py +++ b/overviewer.py @@ -28,6 +28,7 @@ import subprocess import multiprocessing import time import logging +import util logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s") @@ -50,21 +51,6 @@ helptext = """ %prog -d [tiles dest dir]""" -def findGitVersion(): - if os.path.exists(".git"): - with open(os.path.join(".git","HEAD")) as f: - data = f.read().strip() - if data.startswith("ref: "): - with open(os.path.join(".git", data[5:])) as g: - return g.read().strip() - else: - return data - else: - try: - import overviewer_version - return overviewer_version.VERSION - except: - return "unknown" def main(): try: @@ -92,7 +78,7 @@ def main(): if options.version: print "Minecraft-Overviewer" - print "Git version: %s" % findGitVersion() + print "Git version: %s" % util.findGitVersion() try: import overviewer_version if hasattr(sys, "frozen"): diff --git a/setup.py b/setup.py index 5e5f809..9d2f7aa 100644 --- a/setup.py +++ b/setup.py @@ -94,9 +94,9 @@ if py2exe is not None: class CustomPy2exe(py2exe.build_exe.py2exe): def run(self): try: - import overviewer + import util f = open("overviewer_version.py", "w") - f.write("VERSION=%r\n" % overviewer.findGitVersion()) + f.write("VERSION=%r\n" % util.findGitVersion()) f.write("BUILD_DATE=%r\n" % time.asctime()) f.write("BUILD_PLATFORM=%r\n" % platform.processor()) f.write("BUILD_OS=%r\n" % platform.platform()) diff --git a/util.py b/util.py index 280bcab..f39798a 100644 --- a/util.py +++ b/util.py @@ -30,3 +30,21 @@ def get_program_path(): return os.path.dirname(__file__) except NameError: return os.path.dirname(sys.argv[0]) + + + +def findGitVersion(): + if os.path.exists(".git"): + with open(os.path.join(".git","HEAD")) as f: + data = f.read().strip() + if data.startswith("ref: "): + with open(os.path.join(".git", data[5:])) as g: + return g.read().strip() + else: + return data + else: + try: + import overviewer_version + return overviewer_version.VERSION + except: + return "unknown" From 059492b3a19e0745cf875fb9df873f2808808acd Mon Sep 17 00:00:00 2001 From: Xon Date: Mon, 21 Mar 2011 06:54:15 +0800 Subject: [PATCH 091/213] Drain the processing queue every second by ~1000 to give more consistant feedback and reduce stalls when draining the queue from 10000->500 --- quadtree.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/quadtree.py b/quadtree.py index 5e2ad13..6f9091b 100644 --- a/quadtree.py +++ b/quadtree.py @@ -27,6 +27,7 @@ import util import cPickle import stat import errno +import time from time import gmtime, strftime, sleep from PIL import Image @@ -300,13 +301,24 @@ class QuadtreeGen(object): logging.info("Don't worry, each level has only 25% as many tiles as the last.") logging.info("The others will go faster") count = 0 - batch_size = 10 + batch_size = 8 + timestamp = time.time() for result in self._apply_render_worldtiles(pool,batch_size): - results.append(result) - if len(results) > (10000/batch_size): + results.append(result) + # every second drain some of the queue + timestamp2 = time.time() + if timestamp2 >= timestamp + 1: + timestamp = timestamp2 + count_to_remove = (1000//batch_size) + if count_to_remove < len(results): + while count_to_remove > 0: + count_to_remove -= 1 + complete += results.popleft().get() + self.print_statusline(complete, total, 1) + if len(results) > (10000//batch_size): # Empty the queue before adding any more, so that memory # required has an upper bound - while len(results) > (500/batch_size): + while len(results) > (500//batch_size): complete += results.popleft().get() self.print_statusline(complete, total, 1) @@ -324,8 +336,19 @@ class QuadtreeGen(object): complete = 0 total = 4**zoom logging.info("Starting level {0}".format(level)) + timestamp = time.time() for result in self._apply_render_inntertile(pool, zoom,batch_size): results.append(result) + # every second drain some of the queue + timestamp2 = time.time() + if timestamp2 >= timestamp + 1: + timestamp = timestamp2 + count_to_remove = (1000//batch_size) + if count_to_remove < len(results): + while count_to_remove > 0: + count_to_remove -= 1 + complete += results.popleft().get() + self.print_statusline(complete, total, 1) if len(results) > (10000/batch_size): while len(results) > (500/batch_size): complete += results.popleft().get() From c1b7b12592b6af957c4fb6bafb6c8ba0bec5eb0b Mon Sep 17 00:00:00 2001 From: Xon Date: Mon, 21 Mar 2011 08:03:18 +0800 Subject: [PATCH 092/213] Add reporting of scanning/indexing regions. Shortened paths being sent to the worker processes, and removed os.path.join from _apply_render_worldtiles's inner loop. --- quadtree.py | 35 ++++++++++++++++++++++------------- world.py | 4 +++- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/quadtree.py b/quadtree.py index 6f9091b..ce2dab1 100644 --- a/quadtree.py +++ b/quadtree.py @@ -90,8 +90,8 @@ class QuadtreeGen(object): # Make the destination dir if not os.path.exists(destdir): os.mkdir(destdir) - self.tiledir = tiledir - + self.tiledir = tiledir + if depth is None: # Determine quadtree depth (midpoint is always 0,0) for p in xrange(15): @@ -123,6 +123,9 @@ class QuadtreeGen(object): self.world = worldobj self.destdir = destdir + self.full_tiledir = os.path.join(destdir, tiledir) + + def print_statusline(self, complete, total, level, unconditional=False): if unconditional: @@ -227,12 +230,13 @@ class QuadtreeGen(object): colend = colstart + 2 rowend = rowstart + 4 - # This image is rendered at: - dest = os.path.join(self.destdir, self.tiledir, *(str(x) for x in path)) + # This image is rendered at(relative to the worker's destdir): + tilepath = [str(x) for x in path] + tilepath = os.sep.join(tilepath) #logging.debug("this is rendered at %s", dest) # Put this in the batch to be submited to the pool - batch.append((colstart, colend, rowstart, rowend, dest)) + batch.append((colstart, colend, rowstart, rowend, tilepath)) tiles += 1 if tiles >= batch_size: tiles = 0 @@ -251,11 +255,14 @@ class QuadtreeGen(object): batch = [] tiles = 0 for path in iterate_base4(zoom): - # This image is rendered at: - dest = os.path.join(self.destdir, self.tiledir, *(str(x) for x in path[:-1])) + # This image is rendered at(relative to the worker's destdir): + tilepath = [str(x) for x in path[:-1]] + tilepath = os.sep.join(tilepath) name = str(path[-1]) + - batch.append((dest, name, self.imgformat, self.optimizeimg)) + self.full_tiledir + batch.append((tilepath, name, self.imgformat, self.optimizeimg)) tiles += 1 if tiles >= batch_size: tiles = 0 @@ -410,11 +417,14 @@ class QuadtreeGen(object): @catch_keyboardinterrupt def render_innertile_batch(batch): + global child_quadtree + quadtree = child_quadtree count = 0 #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) for job in batch: count += 1 - render_innertile(job[0],job[1],job[2],job[3]) + dest = quadtree.full_tiledir+os.sep+job[0] + render_innertile(dest,job[1],job[2],job[3]) return count def render_innertile(dest, name, imgformat, optimizeimg): @@ -481,11 +491,9 @@ def render_innertile(dest, name, imgformat, optimizeimg): optimize_image(imgpath, imgformat, optimizeimg) @catch_keyboardinterrupt -def render_worldtile_batch(batch): +def render_worldtile_batch(batch): global child_quadtree - return render_worldtile_batch_(child_quadtree, batch) - -def render_worldtile_batch_(quadtree, batch): + quadtree = child_quadtree count = 0 _get_chunks_in_range = quadtree._get_chunks_in_range #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) @@ -496,6 +504,7 @@ def render_worldtile_batch_(quadtree, batch): rowstart = job[2] rowend = job[3] path = job[4] + path = quadtree.full_tiledir+os.sep+path # (even if tilechunks is empty, render_worldtile will delete # existing images if appropriate) # And uses these chunks diff --git a/world.py b/world.py index ce869cb..bd4d9a6 100644 --- a/world.py +++ b/world.py @@ -70,9 +70,10 @@ class World(object): def __init__(self, worlddir, useBiomeData=False,regionlist=None): self.worlddir = worlddir self.useBiomeData = useBiomeData - + #find region files, or load the region list #this also caches all the region file header info + logging.info("Scanning regions") regionfiles = {} regions = {} for x, y, regionfile in self._iterate_regionfiles(): @@ -82,6 +83,7 @@ class World(object): regionfiles[(x,y)] = (x,y,regionfile) self.regionfiles = regionfiles self.regions = regions + logging.debug("Done scanning regions") # figure out chunk format is in use # if not mcregion, error out early From dbdd5d0fc87356a42a486128d54fd3900ee9e2cf Mon Sep 17 00:00:00 2001 From: Xon Date: Wed, 23 Mar 2011 16:44:27 +0800 Subject: [PATCH 093/213] Switched from struct.unpack (module) -> Struct.unpack (class), it compiles the format string and reduces parsing costs. Coalesced a few unpack calls into a compound unpack call. Moved the functionality to get a list of valid chunks into get_chunks out from get_chunk_info. --- nbt.py | 741 ++++++++++++++++++++++++++++++--------------------------- 1 file changed, 385 insertions(+), 356 deletions(-) diff --git a/nbt.py b/nbt.py index 73730a0..5258ffb 100644 --- a/nbt.py +++ b/nbt.py @@ -1,356 +1,385 @@ -# This file is part of the Minecraft Overviewer. -# -# Minecraft Overviewer 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. -# -# Minecraft Overviewer 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 the Overviewer. If not, see . - -import gzip, zlib -import struct -import StringIO -import os - -# decorator to handle filename or object as first parameter -def _file_loader(func): - def wrapper(fileobj, *args): - if isinstance(fileobj, basestring): - if not os.path.isfile(fileobj): - return None - - # Is actually a filename - fileobj = open(fileobj, 'rb') - return func(fileobj, *args) - return wrapper - -@_file_loader -def load(fileobj): - return NBTFileReader(fileobj).read_all() - -def load_from_region(filename, x, y): - nbt = load_region(filename).load_chunk(x, y) - if nbt is None: - return None ## return none. I think this is who we should indicate missing chunks - #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) - return nbt.read_all() - -def load_region(filename): - return MCRFileReader(filename) - -class NBTFileReader(object): - def __init__(self, fileobj, is_gzip=True): - if is_gzip: - self._file = gzip.GzipFile(fileobj=fileobj, mode='rb') - else: - # pure zlib stream -- maybe later replace this with - # a custom zlib file object? - data = zlib.decompress(fileobj.read()) - self._file = StringIO.StringIO(data) - - # These private methods read the payload only of the following types - def _read_tag_end(self): - # Nothing to read - return 0 - - def _read_tag_byte(self): - byte = self._file.read(1) - return struct.unpack("b", byte)[0] - - def _read_tag_short(self): - bytes = self._file.read(2) - return struct.unpack(">h", bytes)[0] - - def _read_tag_int(self): - bytes = self._file.read(4) - return struct.unpack(">i", bytes)[0] - - def _read_tag_long(self): - bytes = self._file.read(8) - return struct.unpack(">q", bytes)[0] - - def _read_tag_float(self): - bytes = self._file.read(4) - return struct.unpack(">f", bytes)[0] - - def _read_tag_double(self): - bytes = self._file.read(8) - return struct.unpack(">d", bytes)[0] - - def _read_tag_byte_array(self): - length = self._read_tag_int() - bytes = self._file.read(length) - return bytes - - def _read_tag_string(self): - length = self._read_tag_short() - - # Read the string - string = self._file.read(length) - - # decode it and return - return string.decode("UTF-8") - - def _read_tag_list(self): - tagid = self._read_tag_byte() - length = self._read_tag_int() - - read_tagmap = { - 0: self._read_tag_end, - 1: self._read_tag_byte, - 2: self._read_tag_short, - 3: self._read_tag_int, - 4: self._read_tag_long, - 5: self._read_tag_float, - 6: self._read_tag_double, - 7: self._read_tag_byte_array, - 8: self._read_tag_string, - 9: self._read_tag_list, - 10:self._read_tag_compound, - } - - read_method = read_tagmap[tagid] - l = [] - for _ in xrange(length): - l.append(read_method()) - return l - - def _read_tag_compound(self): - # Build a dictionary of all the tag names mapping to their payloads - tags = {} - while True: - # Read a tag - tagtype = ord(self._file.read(1)) - - if tagtype == 0: - break - - name = self._read_tag_string() - read_tagmap = { - 0: self._read_tag_end, - 1: self._read_tag_byte, - 2: self._read_tag_short, - 3: self._read_tag_int, - 4: self._read_tag_long, - 5: self._read_tag_float, - 6: self._read_tag_double, - 7: self._read_tag_byte_array, - 8: self._read_tag_string, - 9: self._read_tag_list, - 10:self._read_tag_compound, - } - payload = read_tagmap[tagtype]() - - tags[name] = payload - - return tags - - - - def read_all(self): - """Reads the entire file and returns (name, payload) - name is the name of the root tag, and payload is a dictionary mapping - names to their payloads - - """ - # Read tag type - tagtype = ord(self._file.read(1)) - if tagtype != 10: - raise Exception("Expected a tag compound") - - # Read the tag name - name = self._read_tag_string() - - payload = self._read_tag_compound() - - return name, payload - - -# For reference, the MCR format is outlined at -# -class MCRFileReader(object): - """A class for reading chunk region files, as introduced in the - Beta 1.3 update. It provides functions for opening individual - chunks (as instances of NBTFileReader), getting chunk timestamps, - and for listing chunks contained in the file.""" - - def __init__(self, filename): - self._file = None - self._filename = filename - # cache used when the entire header tables are read in get_chunks() - self._locations = None - self._timestamps = None - self._chunks = None - - def _read_24bit_int(self): - """Read in a 24-bit, big-endian int, used in the chunk - location table.""" - - ret = 0 - bytes = self._file.read(3) - for i in xrange(3): - ret = ret << 8 - ret += struct.unpack("B", bytes[i])[0] - - return ret - - def _read_chunk_location(self, x=None, y=None): - """Read and return the (offset, length) of the given chunk - coordinate, or None if the requested chunk doesn't exist. x - and y must be between 0 and 31, or None. If they are None, - then there will be no file seek before doing the read.""" - - if x is not None and y is not None: - if (not x >= 0) or (not x < 32) or (not y >= 0) or (not y < 32): - raise ValueError("Chunk location out of range.") - - # check for a cached value - if self._locations: - return self._locations[x + y * 32] - - # go to the correct entry in the chunk location table - self._file.seek(4 * (x + y * 32)) - - # 3-byte offset in 4KiB sectors - offset_sectors = self._read_24bit_int() - - # 1-byte length in 4KiB sectors, rounded up - byte = self._file.read(1) - length_sectors = struct.unpack("B", byte)[0] - - # check for empty chunks - if offset_sectors == 0 or length_sectors == 0: - return None - - return (offset_sectors * 4096, length_sectors * 4096) - - def _read_chunk_timestamp(self, x=None, y=None): - """Read and return the last modification time of the given - chunk coordinate. x and y must be between 0 and 31, or - None. If they are, None, then there will be no file seek - before doing the read.""" - - if x is not None and y is not None: - if (not x >= 0) or (not x < 32) or (not y >= 0) or (not y < 32): - raise ValueError("Chunk location out of range.") - - # check for a cached value - if self._timestamps: - return self._timestamps[x + y * 32] - - # go to the correct entry in the chunk timestamp table - self._file.seek(4 * (x + y * 32) + 4096) - - bytes = self._file.read(4) - timestamp = struct.unpack(">I", bytes)[0] - - return timestamp - - def get_chunk_info(self,closeFile = True): - """Return a list of all chunks contained in this region file, - as a list of (x, y) coordinate tuples. To load these chunks, - provide these coordinates to load_chunk().""" - - if self._chunks: - return self._chunks - - if self._file is None: - self._file = open(self._filename,'rb'); - - self._chunks = [] - self._locations = [] - self._timestamps = [] - - # go to the beginning of the file - self._file.seek(0) - - # read chunk location table - for y in xrange(32): - for x in xrange(32): - location = self._read_chunk_location() - self._locations.append(location) - if location: - self._chunks.append((x, y)) - - # read chunk timestamp table - for y in xrange(32): - for x in xrange(32): - timestamp = self._read_chunk_timestamp() - self._timestamps.append(timestamp) - - if closeFile: - #free the file object since it isn't safe to be reused in child processes (seek point goes wonky!) - self._file.close() - self._file = None - return self._chunks - - def get_chunk_timestamp(self, x, y): - """Return the given chunk's modification time. If the given - chunk doesn't exist, this number may be nonsense. Like - load_chunk(), this will wrap x and y into the range [0, 31]. - """ - x = x % 32 - y = y % 32 - if self._timestamps is None: - self.get_chunk_info() - return self._timestamps[x + y * 32] - - def chunkExists(self, x, y): - """Determines if a chunk exists without triggering loading of the backend data""" - x = x % 32 - y = y % 32 - if self._locations is None: - self.get_chunk_info() - location = self._locations[x + y * 32] - return location is not None - - def load_chunk(self, x, y): - """Return a NBTFileReader instance for the given chunk, or - None if the given chunk doesn't exist in this region file. If - you provide an x or y not between 0 and 31, it will be - modulo'd into this range (x % 32, etc.) This is so you can - provide chunk coordinates in global coordinates, and still - have the chunks load out of regions properly.""" - x = x % 32 - y = y % 32 - if self._locations is None: - self.get_chunk_info() - - location = self._locations[x + y * 32] - if location is None: - return None - - if self._file is None: - self._file = open(self._filename,'rb'); - # seek to the data - self._file.seek(location[0]) - - # read in the chunk data header - bytes = self._file.read(4) - data_length = struct.unpack(">I", bytes)[0] - bytes = self._file.read(1) - compression = struct.unpack("B", bytes)[0] - - # figure out the compression - is_gzip = True - if compression == 1: - # gzip -- not used by the official client, but trivial to support here so... - is_gzip = True - elif compression == 2: - # deflate -- pure zlib stream - is_gzip = False - else: - # unsupported! - raise Exception("Unsupported chunk compression type: %i" % (compression)) - # turn the rest of the data into a StringIO object - # (using data_length - 1, as we already read 1 byte for compression) - data = self._file.read(data_length - 1) - data = StringIO.StringIO(data) - - return NBTFileReader(data, is_gzip=is_gzip) +# This file is part of the Minecraft Overviewer. +# +# Minecraft Overviewer 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. +# +# Minecraft Overviewer 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 the Overviewer. If not, see . + +import gzip, zlib +import struct +import StringIO +import os + +# decorator to handle filename or object as first parameter +def _file_loader(func): + def wrapper(fileobj, *args): + if isinstance(fileobj, basestring): + if not os.path.isfile(fileobj): + return None + + # Is actually a filename + fileobj = open(fileobj, 'rb',4096) + return func(fileobj, *args) + return wrapper + +@_file_loader +def load(fileobj): + return NBTFileReader(fileobj).read_all() + +def load_from_region(filename, x, y): + nbt = load_region(filename).load_chunk(x, y) + if nbt is None: + return None ## return none. I think this is who we should indicate missing chunks + #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) + return nbt.read_all() + +def load_region(filename): + return MCRFileReader(filename) + + +# compile the unpacker's into a classes +_byte = struct.Struct("b") +_short = struct.Struct(">h") +_int = struct.Struct(">i") +_long = struct.Struct(">q") +_float = struct.Struct(">f") +_double = struct.Struct(">d") + +_24bit_int = struct.Struct("B B B") +_unsigned_byte = struct.Struct("B") +_unsigned_int = struct.Struct(">I") +_chunk_header = struct.Struct(">I B") + +class NBTFileReader(object): + def __init__(self, fileobj, is_gzip=True): + if is_gzip: + self._file = gzip.GzipFile(fileobj=fileobj, mode='rb') + else: + # pure zlib stream -- maybe later replace this with + # a custom zlib file object? + data = zlib.decompress(fileobj.read()) + self._file = StringIO.StringIO(data) + + # These private methods read the payload only of the following types + def _read_tag_end(self): + # Nothing to read + return 0 + + def _read_tag_byte(self): + byte = self._file.read(1) + return _byte.unpack(byte)[0] + + def _read_tag_short(self): + bytes = self._file.read(2) + global _short + return _short.unpack(bytes)[0] + + def _read_tag_int(self): + bytes = self._file.read(4) + global _int + return _int.unpack(bytes)[0] + + def _read_tag_long(self): + bytes = self._file.read(8) + global _long + return _long.unpack(bytes)[0] + + def _read_tag_float(self): + bytes = self._file.read(4) + global _float + return _float.unpack(bytes)[0] + + def _read_tag_double(self): + bytes = self._file.read(8) + global _double + return _double.unpack(bytes)[0] + + def _read_tag_byte_array(self): + length = self._read_tag_int() + bytes = self._file.read(length) + return bytes + + def _read_tag_string(self): + length = self._read_tag_short() + + # Read the string + string = self._file.read(length) + + # decode it and return + return string.decode("UTF-8") + + def _read_tag_list(self): + tagid = self._read_tag_byte() + length = self._read_tag_int() + + read_tagmap = { + 0: self._read_tag_end, + 1: self._read_tag_byte, + 2: self._read_tag_short, + 3: self._read_tag_int, + 4: self._read_tag_long, + 5: self._read_tag_float, + 6: self._read_tag_double, + 7: self._read_tag_byte_array, + 8: self._read_tag_string, + 9: self._read_tag_list, + 10:self._read_tag_compound, + } + + read_method = read_tagmap[tagid] + l = [] + for _ in xrange(length): + l.append(read_method()) + return l + + def _read_tag_compound(self): + # Build a dictionary of all the tag names mapping to their payloads + tags = {} + while True: + # Read a tag + tagtype = ord(self._file.read(1)) + + if tagtype == 0: + break + + name = self._read_tag_string() + read_tagmap = { + 0: self._read_tag_end, + 1: self._read_tag_byte, + 2: self._read_tag_short, + 3: self._read_tag_int, + 4: self._read_tag_long, + 5: self._read_tag_float, + 6: self._read_tag_double, + 7: self._read_tag_byte_array, + 8: self._read_tag_string, + 9: self._read_tag_list, + 10:self._read_tag_compound, + } + payload = read_tagmap[tagtype]() + + tags[name] = payload + + return tags + + + + def read_all(self): + """Reads the entire file and returns (name, payload) + name is the name of the root tag, and payload is a dictionary mapping + names to their payloads + + """ + # Read tag type + tagtype = ord(self._file.read(1)) + if tagtype != 10: + raise Exception("Expected a tag compound") + + # Read the tag name + name = self._read_tag_string() + + payload = self._read_tag_compound() + + return name, payload + + +# For reference, the MCR format is outlined at +# +class MCRFileReader(object): + """A class for reading chunk region files, as introduced in the + Beta 1.3 update. It provides functions for opening individual + chunks (as instances of NBTFileReader), getting chunk timestamps, + and for listing chunks contained in the file.""" + + def __init__(self, filename): + self._file = None + self._filename = filename + # cache used when the entire header tables are read in get_chunks() + self._locations = None + self._timestamps = None + self._chunks = None + + def _read_24bit_int(self): + """Read in a 24-bit, big-endian int, used in the chunk + location table.""" + + ret = 0 + bytes = self._file.read(3) + global _24bit_int + bytes = _24bit_int.unpack(bytes) + for i in xrange(3): + ret = ret << 8 + ret += bytes[i] + + return ret + + def _read_chunk_location(self, x=None, y=None): + """Read and return the (offset, length) of the given chunk + coordinate, or None if the requested chunk doesn't exist. x + and y must be between 0 and 31, or None. If they are None, + then there will be no file seek before doing the read.""" + + if x is not None and y is not None: + if (not x >= 0) or (not x < 32) or (not y >= 0) or (not y < 32): + raise ValueError("Chunk location out of range.") + + # check for a cached value + if self._locations: + return self._locations[x + y * 32] + + # go to the correct entry in the chunk location table + self._file.seek(4 * (x + y * 32)) + + + # 3-byte offset in 4KiB sectors + offset_sectors = self._read_24bit_int() + global _unsigned_byte + # 1-byte length in 4KiB sectors, rounded up + byte = self._file.read(1) + length_sectors = _unsigned_byte.unpack(byte)[0] + + # check for empty chunks + if offset_sectors == 0 or length_sectors == 0: + return None + + return (offset_sectors * 4096, length_sectors * 4096) + + def _read_chunk_timestamp(self, x=None, y=None): + """Read and return the last modification time of the given + chunk coordinate. x and y must be between 0 and 31, or + None. If they are, None, then there will be no file seek + before doing the read.""" + + if x is not None and y is not None: + if (not x >= 0) or (not x < 32) or (not y >= 0) or (not y < 32): + raise ValueError("Chunk location out of range.") + + # check for a cached value + if self._timestamps: + return self._timestamps[x + y * 32] + + # go to the correct entry in the chunk timestamp table + self._file.seek(4 * (x + y * 32) + 4096) + + bytes = self._file.read(4) + + global _unsigned_int + timestamp = _unsigned_int.unpack(bytes)[0] + + return timestamp + + def get_chunks(self): + """Return a list of all chunks contained in this region file, + as a list of (x, y) coordinate tuples. To load these chunks, + provide these coordinates to load_chunk().""" + + if self._chunks: + return self._chunks + if self._locations is None: + self.get_chunk_info() + self._chunks = filter(None,self._locations) + + return self._chunks + + def get_chunk_info(self,closeFile = True): + """Preloads region header information.""" + + if self._locations: + return + + if self._file is None: + self._file = open(self._filename,'rb'); + + self._chunks = None + self._locations = [] + self._timestamps = [] + + # go to the beginning of the file + self._file.seek(0) + + # read chunk location table + locations_append = self._locations.append + for x, y in [(x,y) for x in xrange(32) for y in xrange(32)]: + locations_append(self._read_chunk_location()) + + # read chunk timestamp table + timestamp_append = self._timestamps.append + for x, y in [(x,y) for x in xrange(32) for y in xrange(32)]: + timestamp_append(self._read_chunk_timestamp()) + + if closeFile: + #free the file object since it isn't safe to be reused in child processes (seek point goes wonky!) + self._file.close() + self._file = None + return + + def get_chunk_timestamp(self, x, y): + """Return the given chunk's modification time. If the given + chunk doesn't exist, this number may be nonsense. Like + load_chunk(), this will wrap x and y into the range [0, 31]. + """ + x = x % 32 + y = y % 32 + if self._timestamps is None: + self.get_chunk_info() + return self._timestamps[x + y * 32] + + def chunkExists(self, x, y): + """Determines if a chunk exists without triggering loading of the backend data""" + x = x % 32 + y = y % 32 + if self._locations is None: + self.get_chunk_info() + location = self._locations[x + y * 32] + return location is not None + + def load_chunk(self, x, y): + """Return a NBTFileReader instance for the given chunk, or + None if the given chunk doesn't exist in this region file. If + you provide an x or y not between 0 and 31, it will be + modulo'd into this range (x % 32, etc.) This is so you can + provide chunk coordinates in global coordinates, and still + have the chunks load out of regions properly.""" + x = x % 32 + y = y % 32 + if self._locations is None: + self.get_chunk_info() + + location = self._locations[x + y * 32] + if location is None: + return None + + if self._file is None: + self._file = open(self._filename,'rb'); + # seek to the data + self._file.seek(location[0]) + + # read in the chunk data header + bytes = self._file.read(5) + data_length,compression = _chunk_header.unpack(bytes) + + # figure out the compression + is_gzip = True + if compression == 1: + # gzip -- not used by the official client, but trivial to support here so... + is_gzip = True + elif compression == 2: + # deflate -- pure zlib stream + is_gzip = False + else: + # unsupported! + raise Exception("Unsupported chunk compression type: %i" % (compression)) + # turn the rest of the data into a StringIO object + # (using data_length - 1, as we already read 1 byte for compression) + data = self._file.read(data_length - 1) + data = StringIO.StringIO(data) + + return NBTFileReader(data, is_gzip=is_gzip) From 8cfa50087aeb4e266fef7d67f145f5106fe63101 Mon Sep 17 00:00:00 2001 From: Xon Date: Wed, 23 Mar 2011 17:22:57 +0800 Subject: [PATCH 094/213] Removed filting chunks from render_worldtile since _get_chunks_in_range can do it trivially before constructing the list --- quadtree.py | 19 ++++++------------- world.py | 6 +++--- 2 files changed, 9 insertions(+), 16 deletions(-) diff --git a/quadtree.py b/quadtree.py index ce2dab1..c32b757 100644 --- a/quadtree.py +++ b/quadtree.py @@ -85,6 +85,9 @@ class QuadtreeGen(object): self.imgformat = imgformat self.optimizeimg = optimizeimg + self.lighting = rendermode in ("lighting", "night", "spawn") + self.night = rendermode in ("night", "spawn") + self.spawn = rendermode in ("spawn",) self.rendermode = rendermode # Make the destination dir @@ -410,8 +413,8 @@ class QuadtreeGen(object): # return (col, row, chunkx, chunky, regionpath) chunkx, chunky = unconvert_coords(col, row) #c = get_region_path(chunkx, chunky) - _, _, c = get_region((chunkx//32, chunky//32),(None,None,None)); - if c is not None: + _, _, c, mcr = get_region((chunkx//32, chunky//32),(None,None,None,None)); + if c is not None and mcr.chunkExists(chunkx,chunky): chunklist.append((col, row, chunkx, chunky, c)) return chunklist @@ -561,16 +564,7 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) # anyways just in case). "chunks" should include up to rowstart-16 imgpath = path + "." + quadtree.imgformat - world = quadtree.world - # first, remove chunks from `chunks` that don't actually exist in - # their region files - def chunk_exists(chunk): - _, _, chunkx, chunky, region = chunk - r = world.load_region(region) - return r.chunkExists(chunkx, chunky) - chunks = filter(chunk_exists, chunks) - #stat the file, we need to know if it exists or it's mtime try: tile_mtime = os.stat(imgpath)[stat.ST_MTIME]; @@ -602,12 +596,11 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path) needs_rerender = False for col, row, chunkx, chunky, regionfile in chunks: # check region file mtime first. - regionMtime = world.get_region_mtime(regionfile) + region,regionMtime = world.get_region_mtime(regionfile) if regionMtime <= tile_mtime: continue # checking chunk mtime - region = world.load_region(regionfile) if region.get_chunk_timestamp(chunkx, chunky) > tile_mtime: needs_rerender = True break diff --git a/world.py b/world.py index bd4d9a6..077a17e 100644 --- a/world.py +++ b/world.py @@ -80,7 +80,7 @@ class World(object): mcr = nbt.MCRFileReader(regionfile) mcr.get_chunk_info() regions[regionfile] = (mcr,os.path.getmtime(regionfile)) - regionfiles[(x,y)] = (x,y,regionfile) + regionfiles[(x,y)] = (x,y,regionfile,mcr) self.regionfiles = regionfiles self.regions = regions logging.debug("Done scanning regions") @@ -116,7 +116,7 @@ class World(object): def get_region_path(self, chunkX, chunkY): """Returns the path to the region that contains chunk (chunkX, chunkY) """ - _, _, regionfile = self.regionfiles.get((chunkX//32, chunkY//32),(None,None,None)); + _, _, regionfile,_ = self.regionfiles.get((chunkX//32, chunkY//32),(None,None,None,None)); return regionfile def load_from_region(self,filename, x, y): @@ -135,7 +135,7 @@ class World(object): return self.regions[filename][0] def get_region_mtime(self,filename): - return self.regions[filename][1] + return self.regions[filename] def convert_coords(self, chunkx, chunky): """Takes a coordinate (chunkx, chunky) where chunkx and chunky are From 1d5b338d562e74afcf670ad1ffc9107c8319e497 Mon Sep 17 00:00:00 2001 From: Xon Date: Fri, 25 Mar 2011 21:34:06 +0800 Subject: [PATCH 095/213] Check PyImport_ImportModule return result --- overviewer.py | 2 ++ src/iterate.c | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/overviewer.py b/overviewer.py index ffc3882..ffaed15 100755 --- a/overviewer.py +++ b/overviewer.py @@ -32,6 +32,8 @@ import util logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s") +#import this before to ensure it doesn't have an errors or c_overviewer will eat them +import chunk # make sure the c_overviewer extension is available try: import c_overviewer diff --git a/src/iterate.c b/src/iterate.c index 8b93cd8..1dcce1a 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -34,6 +34,12 @@ int init_chunk_render(void) { textures = PyImport_ImportModule("textures"); chunk_mod = PyImport_ImportModule("chunk"); + /* ensure none of these pointers are NULL */ + if ((!textures) || (!chunk_mod)) { + fprintf(stderr, "\ninit_chunk_render failed\n"); + return 1; + } + blockmap = PyObject_GetAttrString(textures, "blockmap"); special_blocks = PyObject_GetAttrString(textures, "special_blocks"); specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); From 6c9bbc25da9adbccff4d4f887f2a7c0c4b1414f4 Mon Sep 17 00:00:00 2001 From: Xon Date: Fri, 25 Mar 2011 21:42:18 +0800 Subject: [PATCH 096/213] Better error messages, removed import which triggered a failure --- overviewer.py | 2 -- src/iterate.c | 13 +++++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/overviewer.py b/overviewer.py index ffaed15..ffc3882 100755 --- a/overviewer.py +++ b/overviewer.py @@ -32,8 +32,6 @@ import util logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s") -#import this before to ensure it doesn't have an errors or c_overviewer will eat them -import chunk # make sure the c_overviewer extension is available try: import c_overviewer diff --git a/src/iterate.c b/src/iterate.c index 1dcce1a..f535f39 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -32,11 +32,16 @@ int init_chunk_render(void) { if (blockmap) return 1; textures = PyImport_ImportModule("textures"); - chunk_mod = PyImport_ImportModule("chunk"); - /* ensure none of these pointers are NULL */ - if ((!textures) || (!chunk_mod)) { - fprintf(stderr, "\ninit_chunk_render failed\n"); + if ((!textures)) { + fprintf(stderr, "\ninit_chunk_render failed to load; textures\n"); + return 1; + } + + chunk_mod = PyImport_ImportModule("chunk"); + /* ensure none of these pointers are NULL */ + if ((!chunk_mod)) { + fprintf(stderr, "\ninit_chunk_render failed to load; chunk\n"); return 1; } From 593c475e9808aaea1c162e9f58120df29e07f0f3 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Fri, 25 Mar 2011 17:46:52 +0100 Subject: [PATCH 097/213] Fix problem loading adjacent chunks in some maps. --- chunk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chunk.py b/chunk.py index 63de064..28f17d8 100644 --- a/chunk.py +++ b/chunk.py @@ -52,7 +52,7 @@ def get_lvldata(world, filename, x, y, retries=2): # non existent region file doesn't mean corrupt chunk. if filename == None: - return None + raise NoSuchChunk try: d = world.load_from_region(filename, x, y) From c700afb012b9bfb810f721c631110a409c78d309 Mon Sep 17 00:00:00 2001 From: Xon Date: Wed, 23 Mar 2011 18:49:26 +0800 Subject: [PATCH 098/213] Fixed get_chunks, simplified get_chunk_info --- nbt.py | 773 +++++++++++++++++++++++++++++---------------------------- 1 file changed, 388 insertions(+), 385 deletions(-) diff --git a/nbt.py b/nbt.py index 5258ffb..8a96a62 100644 --- a/nbt.py +++ b/nbt.py @@ -1,385 +1,388 @@ -# This file is part of the Minecraft Overviewer. -# -# Minecraft Overviewer 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. -# -# Minecraft Overviewer 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 the Overviewer. If not, see . - -import gzip, zlib -import struct -import StringIO -import os - -# decorator to handle filename or object as first parameter -def _file_loader(func): - def wrapper(fileobj, *args): - if isinstance(fileobj, basestring): - if not os.path.isfile(fileobj): - return None - - # Is actually a filename - fileobj = open(fileobj, 'rb',4096) - return func(fileobj, *args) - return wrapper - -@_file_loader -def load(fileobj): - return NBTFileReader(fileobj).read_all() - -def load_from_region(filename, x, y): - nbt = load_region(filename).load_chunk(x, y) - if nbt is None: - return None ## return none. I think this is who we should indicate missing chunks - #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) - return nbt.read_all() - -def load_region(filename): - return MCRFileReader(filename) - - -# compile the unpacker's into a classes -_byte = struct.Struct("b") -_short = struct.Struct(">h") -_int = struct.Struct(">i") -_long = struct.Struct(">q") -_float = struct.Struct(">f") -_double = struct.Struct(">d") - -_24bit_int = struct.Struct("B B B") -_unsigned_byte = struct.Struct("B") -_unsigned_int = struct.Struct(">I") -_chunk_header = struct.Struct(">I B") - -class NBTFileReader(object): - def __init__(self, fileobj, is_gzip=True): - if is_gzip: - self._file = gzip.GzipFile(fileobj=fileobj, mode='rb') - else: - # pure zlib stream -- maybe later replace this with - # a custom zlib file object? - data = zlib.decompress(fileobj.read()) - self._file = StringIO.StringIO(data) - - # These private methods read the payload only of the following types - def _read_tag_end(self): - # Nothing to read - return 0 - - def _read_tag_byte(self): - byte = self._file.read(1) - return _byte.unpack(byte)[0] - - def _read_tag_short(self): - bytes = self._file.read(2) - global _short - return _short.unpack(bytes)[0] - - def _read_tag_int(self): - bytes = self._file.read(4) - global _int - return _int.unpack(bytes)[0] - - def _read_tag_long(self): - bytes = self._file.read(8) - global _long - return _long.unpack(bytes)[0] - - def _read_tag_float(self): - bytes = self._file.read(4) - global _float - return _float.unpack(bytes)[0] - - def _read_tag_double(self): - bytes = self._file.read(8) - global _double - return _double.unpack(bytes)[0] - - def _read_tag_byte_array(self): - length = self._read_tag_int() - bytes = self._file.read(length) - return bytes - - def _read_tag_string(self): - length = self._read_tag_short() - - # Read the string - string = self._file.read(length) - - # decode it and return - return string.decode("UTF-8") - - def _read_tag_list(self): - tagid = self._read_tag_byte() - length = self._read_tag_int() - - read_tagmap = { - 0: self._read_tag_end, - 1: self._read_tag_byte, - 2: self._read_tag_short, - 3: self._read_tag_int, - 4: self._read_tag_long, - 5: self._read_tag_float, - 6: self._read_tag_double, - 7: self._read_tag_byte_array, - 8: self._read_tag_string, - 9: self._read_tag_list, - 10:self._read_tag_compound, - } - - read_method = read_tagmap[tagid] - l = [] - for _ in xrange(length): - l.append(read_method()) - return l - - def _read_tag_compound(self): - # Build a dictionary of all the tag names mapping to their payloads - tags = {} - while True: - # Read a tag - tagtype = ord(self._file.read(1)) - - if tagtype == 0: - break - - name = self._read_tag_string() - read_tagmap = { - 0: self._read_tag_end, - 1: self._read_tag_byte, - 2: self._read_tag_short, - 3: self._read_tag_int, - 4: self._read_tag_long, - 5: self._read_tag_float, - 6: self._read_tag_double, - 7: self._read_tag_byte_array, - 8: self._read_tag_string, - 9: self._read_tag_list, - 10:self._read_tag_compound, - } - payload = read_tagmap[tagtype]() - - tags[name] = payload - - return tags - - - - def read_all(self): - """Reads the entire file and returns (name, payload) - name is the name of the root tag, and payload is a dictionary mapping - names to their payloads - - """ - # Read tag type - tagtype = ord(self._file.read(1)) - if tagtype != 10: - raise Exception("Expected a tag compound") - - # Read the tag name - name = self._read_tag_string() - - payload = self._read_tag_compound() - - return name, payload - - -# For reference, the MCR format is outlined at -# -class MCRFileReader(object): - """A class for reading chunk region files, as introduced in the - Beta 1.3 update. It provides functions for opening individual - chunks (as instances of NBTFileReader), getting chunk timestamps, - and for listing chunks contained in the file.""" - - def __init__(self, filename): - self._file = None - self._filename = filename - # cache used when the entire header tables are read in get_chunks() - self._locations = None - self._timestamps = None - self._chunks = None - - def _read_24bit_int(self): - """Read in a 24-bit, big-endian int, used in the chunk - location table.""" - - ret = 0 - bytes = self._file.read(3) - global _24bit_int - bytes = _24bit_int.unpack(bytes) - for i in xrange(3): - ret = ret << 8 - ret += bytes[i] - - return ret - - def _read_chunk_location(self, x=None, y=None): - """Read and return the (offset, length) of the given chunk - coordinate, or None if the requested chunk doesn't exist. x - and y must be between 0 and 31, or None. If they are None, - then there will be no file seek before doing the read.""" - - if x is not None and y is not None: - if (not x >= 0) or (not x < 32) or (not y >= 0) or (not y < 32): - raise ValueError("Chunk location out of range.") - - # check for a cached value - if self._locations: - return self._locations[x + y * 32] - - # go to the correct entry in the chunk location table - self._file.seek(4 * (x + y * 32)) - - - # 3-byte offset in 4KiB sectors - offset_sectors = self._read_24bit_int() - global _unsigned_byte - # 1-byte length in 4KiB sectors, rounded up - byte = self._file.read(1) - length_sectors = _unsigned_byte.unpack(byte)[0] - - # check for empty chunks - if offset_sectors == 0 or length_sectors == 0: - return None - - return (offset_sectors * 4096, length_sectors * 4096) - - def _read_chunk_timestamp(self, x=None, y=None): - """Read and return the last modification time of the given - chunk coordinate. x and y must be between 0 and 31, or - None. If they are, None, then there will be no file seek - before doing the read.""" - - if x is not None and y is not None: - if (not x >= 0) or (not x < 32) or (not y >= 0) or (not y < 32): - raise ValueError("Chunk location out of range.") - - # check for a cached value - if self._timestamps: - return self._timestamps[x + y * 32] - - # go to the correct entry in the chunk timestamp table - self._file.seek(4 * (x + y * 32) + 4096) - - bytes = self._file.read(4) - - global _unsigned_int - timestamp = _unsigned_int.unpack(bytes)[0] - - return timestamp - - def get_chunks(self): - """Return a list of all chunks contained in this region file, - as a list of (x, y) coordinate tuples. To load these chunks, - provide these coordinates to load_chunk().""" - - if self._chunks: - return self._chunks - if self._locations is None: - self.get_chunk_info() - self._chunks = filter(None,self._locations) - - return self._chunks - - def get_chunk_info(self,closeFile = True): - """Preloads region header information.""" - - if self._locations: - return - - if self._file is None: - self._file = open(self._filename,'rb'); - - self._chunks = None - self._locations = [] - self._timestamps = [] - - # go to the beginning of the file - self._file.seek(0) - - # read chunk location table - locations_append = self._locations.append - for x, y in [(x,y) for x in xrange(32) for y in xrange(32)]: - locations_append(self._read_chunk_location()) - - # read chunk timestamp table - timestamp_append = self._timestamps.append - for x, y in [(x,y) for x in xrange(32) for y in xrange(32)]: - timestamp_append(self._read_chunk_timestamp()) - - if closeFile: - #free the file object since it isn't safe to be reused in child processes (seek point goes wonky!) - self._file.close() - self._file = None - return - - def get_chunk_timestamp(self, x, y): - """Return the given chunk's modification time. If the given - chunk doesn't exist, this number may be nonsense. Like - load_chunk(), this will wrap x and y into the range [0, 31]. - """ - x = x % 32 - y = y % 32 - if self._timestamps is None: - self.get_chunk_info() - return self._timestamps[x + y * 32] - - def chunkExists(self, x, y): - """Determines if a chunk exists without triggering loading of the backend data""" - x = x % 32 - y = y % 32 - if self._locations is None: - self.get_chunk_info() - location = self._locations[x + y * 32] - return location is not None - - def load_chunk(self, x, y): - """Return a NBTFileReader instance for the given chunk, or - None if the given chunk doesn't exist in this region file. If - you provide an x or y not between 0 and 31, it will be - modulo'd into this range (x % 32, etc.) This is so you can - provide chunk coordinates in global coordinates, and still - have the chunks load out of regions properly.""" - x = x % 32 - y = y % 32 - if self._locations is None: - self.get_chunk_info() - - location = self._locations[x + y * 32] - if location is None: - return None - - if self._file is None: - self._file = open(self._filename,'rb'); - # seek to the data - self._file.seek(location[0]) - - # read in the chunk data header - bytes = self._file.read(5) - data_length,compression = _chunk_header.unpack(bytes) - - # figure out the compression - is_gzip = True - if compression == 1: - # gzip -- not used by the official client, but trivial to support here so... - is_gzip = True - elif compression == 2: - # deflate -- pure zlib stream - is_gzip = False - else: - # unsupported! - raise Exception("Unsupported chunk compression type: %i" % (compression)) - # turn the rest of the data into a StringIO object - # (using data_length - 1, as we already read 1 byte for compression) - data = self._file.read(data_length - 1) - data = StringIO.StringIO(data) - - return NBTFileReader(data, is_gzip=is_gzip) +# This file is part of the Minecraft Overviewer. +# +# Minecraft Overviewer 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. +# +# Minecraft Overviewer 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 the Overviewer. If not, see . + +import gzip, zlib +import struct +import StringIO +import os + +# decorator to handle filename or object as first parameter +def _file_loader(func): + def wrapper(fileobj, *args): + if isinstance(fileobj, basestring): + if not os.path.isfile(fileobj): + return None + + # Is actually a filename + fileobj = open(fileobj, 'rb',4096) + return func(fileobj, *args) + return wrapper + +@_file_loader +def load(fileobj): + return NBTFileReader(fileobj).read_all() + +def load_from_region(filename, x, y): + nbt = load_region(filename).load_chunk(x, y) + if nbt is None: + return None ## return none. I think this is who we should indicate missing chunks + #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) + return nbt.read_all() + +def load_region(filename): + return MCRFileReader(filename) + + +# compile the unpacker's into a classes +_byte = struct.Struct("b") +_short = struct.Struct(">h") +_int = struct.Struct(">i") +_long = struct.Struct(">q") +_float = struct.Struct(">f") +_double = struct.Struct(">d") + +_24bit_int = struct.Struct("B B B") +_unsigned_byte = struct.Struct("B") +_unsigned_int = struct.Struct(">I") +_chunk_header = struct.Struct(">I B") + +class NBTFileReader(object): + def __init__(self, fileobj, is_gzip=True): + if is_gzip: + self._file = gzip.GzipFile(fileobj=fileobj, mode='rb') + else: + # pure zlib stream -- maybe later replace this with + # a custom zlib file object? + data = zlib.decompress(fileobj.read()) + self._file = StringIO.StringIO(data) + + # These private methods read the payload only of the following types + def _read_tag_end(self): + # Nothing to read + return 0 + + def _read_tag_byte(self): + byte = self._file.read(1) + return _byte.unpack(byte)[0] + + def _read_tag_short(self): + bytes = self._file.read(2) + global _short + return _short.unpack(bytes)[0] + + def _read_tag_int(self): + bytes = self._file.read(4) + global _int + return _int.unpack(bytes)[0] + + def _read_tag_long(self): + bytes = self._file.read(8) + global _long + return _long.unpack(bytes)[0] + + def _read_tag_float(self): + bytes = self._file.read(4) + global _float + return _float.unpack(bytes)[0] + + def _read_tag_double(self): + bytes = self._file.read(8) + global _double + return _double.unpack(bytes)[0] + + def _read_tag_byte_array(self): + length = self._read_tag_int() + bytes = self._file.read(length) + return bytes + + def _read_tag_string(self): + length = self._read_tag_short() + + # Read the string + string = self._file.read(length) + + # decode it and return + return string.decode("UTF-8") + + def _read_tag_list(self): + tagid = self._read_tag_byte() + length = self._read_tag_int() + + read_tagmap = { + 0: self._read_tag_end, + 1: self._read_tag_byte, + 2: self._read_tag_short, + 3: self._read_tag_int, + 4: self._read_tag_long, + 5: self._read_tag_float, + 6: self._read_tag_double, + 7: self._read_tag_byte_array, + 8: self._read_tag_string, + 9: self._read_tag_list, + 10:self._read_tag_compound, + } + + read_method = read_tagmap[tagid] + l = [] + for _ in xrange(length): + l.append(read_method()) + return l + + def _read_tag_compound(self): + # Build a dictionary of all the tag names mapping to their payloads + tags = {} + while True: + # Read a tag + tagtype = ord(self._file.read(1)) + + if tagtype == 0: + break + + name = self._read_tag_string() + read_tagmap = { + 0: self._read_tag_end, + 1: self._read_tag_byte, + 2: self._read_tag_short, + 3: self._read_tag_int, + 4: self._read_tag_long, + 5: self._read_tag_float, + 6: self._read_tag_double, + 7: self._read_tag_byte_array, + 8: self._read_tag_string, + 9: self._read_tag_list, + 10:self._read_tag_compound, + } + payload = read_tagmap[tagtype]() + + tags[name] = payload + + return tags + + + + def read_all(self): + """Reads the entire file and returns (name, payload) + name is the name of the root tag, and payload is a dictionary mapping + names to their payloads + + """ + # Read tag type + tagtype = ord(self._file.read(1)) + if tagtype != 10: + raise Exception("Expected a tag compound") + + # Read the tag name + name = self._read_tag_string() + + payload = self._read_tag_compound() + + return name, payload + + +# For reference, the MCR format is outlined at +# +class MCRFileReader(object): + """A class for reading chunk region files, as introduced in the + Beta 1.3 update. It provides functions for opening individual + chunks (as instances of NBTFileReader), getting chunk timestamps, + and for listing chunks contained in the file.""" + + def __init__(self, filename): + self._file = None + self._filename = filename + # cache used when the entire header tables are read in get_chunks() + self._locations = None + self._timestamps = None + self._chunks = None + + def _read_24bit_int(self): + """Read in a 24-bit, big-endian int, used in the chunk + location table.""" + + ret = 0 + bytes = self._file.read(3) + global _24bit_int + bytes = _24bit_int.unpack(bytes) + for i in xrange(3): + ret = ret << 8 + ret += bytes[i] + + return ret + + def _read_chunk_location(self, x=None, y=None): + """Read and return the (offset, length) of the given chunk + coordinate, or None if the requested chunk doesn't exist. x + and y must be between 0 and 31, or None. If they are None, + then there will be no file seek before doing the read.""" + + if x is not None and y is not None: + if (not x >= 0) or (not x < 32) or (not y >= 0) or (not y < 32): + raise ValueError("Chunk location out of range.") + + # check for a cached value + if self._locations: + return self._locations[x + y * 32] + + # go to the correct entry in the chunk location table + self._file.seek(4 * (x + y * 32)) + + + # 3-byte offset in 4KiB sectors + offset_sectors = self._read_24bit_int() + global _unsigned_byte + # 1-byte length in 4KiB sectors, rounded up + byte = self._file.read(1) + length_sectors = _unsigned_byte.unpack(byte)[0] + + # check for empty chunks + if offset_sectors == 0 or length_sectors == 0: + return None + + return (offset_sectors * 4096, length_sectors * 4096) + + def _read_chunk_timestamp(self, x=None, y=None): + """Read and return the last modification time of the given + chunk coordinate. x and y must be between 0 and 31, or + None. If they are, None, then there will be no file seek + before doing the read.""" + + if x is not None and y is not None: + if (not x >= 0) or (not x < 32) or (not y >= 0) or (not y < 32): + raise ValueError("Chunk location out of range.") + + # check for a cached value + if self._timestamps: + return self._timestamps[x + y * 32] + + # go to the correct entry in the chunk timestamp table + self._file.seek(4 * (x + y * 32) + 4096) + + bytes = self._file.read(4) + + global _unsigned_int + timestamp = _unsigned_int.unpack(bytes)[0] + + return timestamp + + def get_chunks(self): + """Return a list of all chunks contained in this region file, + as a list of (x, y) coordinate tuples. To load these chunks, + provide these coordinates to load_chunk().""" + + if self._chunks is not None: + return self._chunks + if self._locations is None: + self.get_chunk_info() + self._chunks = [] + for x in xrange(32): + for y in xrange(32): + if self._locations[x + y * 32] is not None: + self._chunks.append((x,y)) + return self._chunks + + def get_chunk_info(self,closeFile = True): + """Preloads region header information.""" + + if self._locations: + return + + if self._file is None: + self._file = open(self._filename,'rb') + + self._chunks = None + self._locations = [] + self._timestamps = [] + + # go to the beginning of the file + self._file.seek(0) + + # read chunk location table + locations_append = self._locations.append + for _ in xrange(32*32): + locations_append(self._read_chunk_location()) + + # read chunk timestamp table + timestamp_append = self._timestamps.append + for _ in xrange(32*32): + timestamp_append(self._read_chunk_timestamp()) + + if closeFile: + #free the file object since it isn't safe to be reused in child processes (seek point goes wonky!) + self._file.close() + self._file = None + return + + def get_chunk_timestamp(self, x, y): + """Return the given chunk's modification time. If the given + chunk doesn't exist, this number may be nonsense. Like + load_chunk(), this will wrap x and y into the range [0, 31]. + """ + x = x % 32 + y = y % 32 + if self._timestamps is None: + self.get_chunk_info() + return self._timestamps[x + y * 32] + + def chunkExists(self, x, y): + """Determines if a chunk exists without triggering loading of the backend data""" + x = x % 32 + y = y % 32 + if self._locations is None: + self.get_chunk_info() + location = self._locations[x + y * 32] + return location is not None + + def load_chunk(self, x, y): + """Return a NBTFileReader instance for the given chunk, or + None if the given chunk doesn't exist in this region file. If + you provide an x or y not between 0 and 31, it will be + modulo'd into this range (x % 32, etc.) This is so you can + provide chunk coordinates in global coordinates, and still + have the chunks load out of regions properly.""" + x = x % 32 + y = y % 32 + if self._locations is None: + self.get_chunk_info() + + location = self._locations[x + y * 32] + if location is None: + return None + + if self._file is None: + self._file = open(self._filename,'rb'); + # seek to the data + self._file.seek(location[0]) + + # read in the chunk data header + bytes = self._file.read(5) + data_length,compression = _chunk_header.unpack(bytes) + + # figure out the compression + is_gzip = True + if compression == 1: + # gzip -- not used by the official client, but trivial to support here so... + is_gzip = True + elif compression == 2: + # deflate -- pure zlib stream + is_gzip = False + else: + # unsupported! + raise Exception("Unsupported chunk compression type: %i" % (compression)) + # turn the rest of the data into a StringIO object + # (using data_length - 1, as we already read 1 byte for compression) + data = self._file.read(data_length - 1) + data = StringIO.StringIO(data) + + return NBTFileReader(data, is_gzip=is_gzip) From ca36c9864194b902e39b3042ef77163dc9d1c2c7 Mon Sep 17 00:00:00 2001 From: Xon Date: Wed, 23 Mar 2011 21:42:13 +0800 Subject: [PATCH 099/213] Initial commit for multi-layer rendering. Hardwired to use all 4 render modes at once. Todo: add config file/commandline argument support. --- googlemap.py | 3 +- overviewer.py | 17 +- quadtree.py | 641 ++++++++++++++++---------------------------------- rendernode.py | 346 +++++++++++++++++++++++++++ 4 files changed, 569 insertions(+), 438 deletions(-) create mode 100644 rendernode.py diff --git a/googlemap.py b/googlemap.py index fbecc03..f53aae0 100644 --- a/googlemap.py +++ b/googlemap.py @@ -53,7 +53,8 @@ class MapGen(object): def __init__(self, quadtrees, skipjs=False, web_assets_hook=None): """Generates a Google Maps interface for the given list of quadtrees. All of the quadtrees must have the same destdir, - image format, and world.""" + image format, and world. + Note:tiledir for each quadtree should be unique. By default the tiledir is determined by the rendermode""" self.skipjs = skipjs self.web_assets_hook = web_assets_hook diff --git a/overviewer.py b/overviewer.py index ffc3882..cb1b0a9 100755 --- a/overviewer.py +++ b/overviewer.py @@ -45,6 +45,7 @@ import composite import world import quadtree import googlemap +import rendernode helptext = """ %prog [OPTIONS] @@ -171,21 +172,29 @@ def main(): useBiomeData = os.path.exists(os.path.join(worlddir, 'biomes')) if not useBiomeData: logging.info("Notice: Not using biome data for tinting") - + # First do world-level preprocessing w = world.World(worlddir, useBiomeData=useBiomeData) w.go(options.procs) # create the quadtrees # TODO chunklist - q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode=options.rendermode) + q = [] + q.append(quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode='normal', tiledir='tiles')) + q.append(quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode='lighting')) + q.append(quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode='night')) + q.append(quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode='spawn')) + + #create the distributed render + r = rendernode.RenderNode(w,q) # write out the map and web assets - m = googlemap.MapGen([q,], skipjs=options.skipjs, web_assets_hook=options.web_assets_hook) + m = googlemap.MapGen(q, skipjs=options.skipjs, web_assets_hook=options.web_assets_hook) m.go(options.procs) # render the tiles! - q.go(options.procs) + r.go(options.procs) + def delete_all(worlddir, tiledir): # TODO should we delete tiledir here too? diff --git a/quadtree.py b/quadtree.py index c32b757..60c097d 100644 --- a/quadtree.py +++ b/quadtree.py @@ -46,32 +46,9 @@ This module has routines related to generating a quadtree of tiles def iterate_base4(d): """Iterates over a base 4 number with d digits""" return itertools.product(xrange(4), repeat=d) - -def catch_keyboardinterrupt(func): - """Decorator that catches a keyboardinterrupt and raises a real exception - so that multiprocessing will propagate it properly""" - @functools.wraps(func) - def newfunc(*args, **kwargs): - try: - return func(*args, **kwargs) - except KeyboardInterrupt: - logging.error("Ctrl-C caught!") - raise Exception("Exiting") - except: - import traceback - traceback.print_exc() - raise - return newfunc - -child_quadtree = None -def pool_initializer(quadtree): - logging.debug("Child process {0}".format(os.getpid())) - #stash the quadtree object in a global variable after fork() for windows compat. - global child_quadtree - child_quadtree = quadtree - + class QuadtreeGen(object): - def __init__(self, worldobj, destdir, depth=None, tiledir="tiles", imgformat=None, optimizeimg=None, rendermode="normal"): + def __init__(self, worldobj, destdir, depth=None, tiledir=None, imgformat=None, optimizeimg=None, rendermode="normal"): """Generates a quadtree from the world given into the given dest directory @@ -93,6 +70,8 @@ class QuadtreeGen(object): # Make the destination dir if not os.path.exists(destdir): os.mkdir(destdir) + if tiledir is None: + tiledir = rendermode self.tiledir = tiledir if depth is None: @@ -128,23 +107,6 @@ class QuadtreeGen(object): self.destdir = destdir self.full_tiledir = os.path.join(destdir, tiledir) - - - def print_statusline(self, complete, total, level, unconditional=False): - if unconditional: - pass - elif complete < 100: - if not complete % 25 == 0: - return - elif complete < 1000: - if not complete % 100 == 0: - return - else: - if not complete % 1000 == 0: - return - logging.info("{0}/{1} tiles complete on level {2}/{3}".format( - complete, total, level, self.p)) - def _get_cur_depth(self): """How deep is the quadtree currently in the destdir? This glances in config.js to see what maxZoom is set to. @@ -219,64 +181,9 @@ class QuadtreeGen(object): os.rename(getpath("3", "0"), getpath("new3")) shutil.rmtree(getpath("3")) os.rename(getpath("new3"), getpath("3")) - - def _apply_render_worldtiles(self, pool,batch_size): - """Returns an iterator over result objects. Each time a new result is - requested, a new task is added to the pool and a result returned. - """ - - batch = [] - tiles = 0 - for path in iterate_base4(self.p): - # Get the range for this tile - colstart, rowstart = self._get_range_by_path(path) - colend = colstart + 2 - rowend = rowstart + 4 - - # This image is rendered at(relative to the worker's destdir): - tilepath = [str(x) for x in path] - tilepath = os.sep.join(tilepath) - #logging.debug("this is rendered at %s", dest) - - # Put this in the batch to be submited to the pool - batch.append((colstart, colend, rowstart, rowend, tilepath)) - tiles += 1 - if tiles >= batch_size: - tiles = 0 - yield pool.apply_async(func=render_worldtile_batch, args= [batch]) - batch = [] - - if tiles > 0: - yield pool.apply_async(func=render_worldtile_batch, args= (batch,)) - - - def _apply_render_inntertile(self, pool, zoom,batch_size): - """Same as _apply_render_worltiles but for the inntertile routine. - Returns an iterator that yields result objects from tasks that have - been applied to the pool. - """ - batch = [] - tiles = 0 - for path in iterate_base4(zoom): - # This image is rendered at(relative to the worker's destdir): - tilepath = [str(x) for x in path[:-1]] - tilepath = os.sep.join(tilepath) - name = str(path[-1]) - - - self.full_tiledir - batch.append((tilepath, name, self.imgformat, self.optimizeimg)) - tiles += 1 - if tiles >= batch_size: - tiles = 0 - yield pool.apply_async(func=render_innertile_batch, args= [batch]) - batch = [] - - if tiles > 0: - yield pool.apply_async(func=render_innertile_batch, args= [batch]) def go(self, procs): - """Renders all tiles""" + """Processing before tile rendering""" curdepth = self._get_cur_depth() if curdepth != -1: @@ -289,95 +196,8 @@ class QuadtreeGen(object): logging.warning("Your map seems to have shrunk. Re-arranging tiles, just a sec...") for _ in xrange(curdepth - self.p): self._decrease_depth() - - logging.debug("Parent process {0}".format(os.getpid())) - # Create a pool - if procs == 1: - pool = FakePool() - global child_quadtree - child_quadtree = self - else: - pool = multiprocessing.Pool(processes=procs,initializer=pool_initializer,initargs=(self,)) - #warm up the pool so it reports all the worker id's - pool.map(bool,xrange(multiprocessing.cpu_count()),1) - - # Render the highest level of tiles from the chunks - results = collections.deque() - complete = 0 - total = 4**self.p - logging.info("Rendering highest zoom level of tiles now.") - logging.info("There are {0} tiles to render".format(total)) - logging.info("There are {0} total levels to render".format(self.p)) - logging.info("Don't worry, each level has only 25% as many tiles as the last.") - logging.info("The others will go faster") - count = 0 - batch_size = 8 - timestamp = time.time() - for result in self._apply_render_worldtiles(pool,batch_size): - results.append(result) - # every second drain some of the queue - timestamp2 = time.time() - if timestamp2 >= timestamp + 1: - timestamp = timestamp2 - count_to_remove = (1000//batch_size) - if count_to_remove < len(results): - while count_to_remove > 0: - count_to_remove -= 1 - complete += results.popleft().get() - self.print_statusline(complete, total, 1) - if len(results) > (10000//batch_size): - # Empty the queue before adding any more, so that memory - # required has an upper bound - while len(results) > (500//batch_size): - complete += results.popleft().get() - self.print_statusline(complete, total, 1) - - # Wait for the rest of the results - while len(results) > 0: - complete += results.popleft().get() - self.print_statusline(complete, total, 1) - - self.print_statusline(complete, total, 1, True) - - # Now do the other layers - for zoom in xrange(self.p-1, 0, -1): - level = self.p - zoom + 1 - assert len(results) == 0 - complete = 0 - total = 4**zoom - logging.info("Starting level {0}".format(level)) - timestamp = time.time() - for result in self._apply_render_inntertile(pool, zoom,batch_size): - results.append(result) - # every second drain some of the queue - timestamp2 = time.time() - if timestamp2 >= timestamp + 1: - timestamp = timestamp2 - count_to_remove = (1000//batch_size) - if count_to_remove < len(results): - while count_to_remove > 0: - count_to_remove -= 1 - complete += results.popleft().get() - self.print_statusline(complete, total, 1) - if len(results) > (10000/batch_size): - while len(results) > (500/batch_size): - complete += results.popleft().get() - self.print_statusline(complete, total, level) - # Empty the queue - while len(results) > 0: - complete += results.popleft().get() - self.print_statusline(complete, total, level) - - self.print_statusline(complete, total, level, True) - - logging.info("Done") - - pool.close() - pool.join() - - # Do the final one right here: - render_innertile(os.path.join(self.destdir, self.tiledir), "base", self.imgformat, self.optimizeimg) - + + def _get_range_by_path(self, path): """Returns the x, y chunk coordinates of this tile""" x, y = self.mincol, self.minrow @@ -394,259 +214,214 @@ class QuadtreeGen(object): ysize //= 2 return x, y + + def get_worldtiles(self): + """Returns an iterator over the tiles of the most detailed layer + """ + for path in iterate_base4(self.p): + # Get the range for this tile + colstart, rowstart = self._get_range_by_path(path) + colend = colstart + 2 + rowend = rowstart + 4 + + # This image is rendered at(relative to the worker's destdir): + tilepath = [str(x) for x in path] + tilepath = os.sep.join(tilepath) + #logging.debug("this is rendered at %s", dest) + + # Put this in the batch to be submited to the pool + yield [self,colstart, colend, rowstart, rowend, tilepath] + + def get_innertiles(self,zoom): + """Same as get_worldtiles but for the inntertile routine. + """ + for path in iterate_base4(zoom): + # This image is rendered at(relative to the worker's destdir): + tilepath = [str(x) for x in path[:-1]] + tilepath = os.sep.join(tilepath) + name = str(path[-1]) + + yield [self,tilepath, name] + + def render_innertile(self, dest, name): + """ + Renders a tile at os.path.join(dest, name)+".ext" by taking tiles from + os.path.join(dest, name, "{0,1,2,3}.png") + """ + imgformat = self.imgformat + imgpath = os.path.join(dest, name) + "." + imgformat - def _get_chunks_in_range(self, colstart, colend, rowstart, rowend): - """Get chunks that are relevant to the tile rendering function that's - rendering that range""" - chunklist = [] - unconvert_coords = self.world.unconvert_coords - #get_region_path = self.world.get_region_path - get_region = self.world.regionfiles.get - for row in xrange(rowstart-16, rowend+1): - for col in xrange(colstart, colend+1): - # due to how chunks are arranged, we can only allow - # even row, even column or odd row, odd column - # otherwise, you end up with duplicates! - if row % 2 != col % 2: + if name == "base": + quadPath = [[(0,0),os.path.join(dest, "0." + imgformat)],[(192,0),os.path.join(dest, "1." + imgformat)], [(0, 192),os.path.join(dest, "2." + imgformat)],[(192,192),os.path.join(dest, "3." + imgformat)]] + else: + quadPath = [[(0,0),os.path.join(dest, name, "0." + imgformat)],[(192,0),os.path.join(dest, name, "1." + imgformat)],[(0, 192),os.path.join(dest, name, "2." + imgformat)],[(192,192),os.path.join(dest, name, "3." + imgformat)]] + + #stat the tile, we need to know if it exists or it's mtime + try: + tile_mtime = os.stat(imgpath)[stat.ST_MTIME]; + except OSError, e: + if e.errno != errno.ENOENT: + raise + tile_mtime = None + + #check mtimes on each part of the quad, this also checks if they exist + needs_rerender = tile_mtime is None + quadPath_filtered = [] + for path in quadPath: + try: + quad_mtime = os.stat(path[1])[stat.ST_MTIME]; + quadPath_filtered.append(path) + if quad_mtime > tile_mtime: + needs_rerender = True + except OSError: + # We need to stat all the quad files, so keep looping + pass + # do they all not exist? + if quadPath_filtered == []: + if tile_mtime is not None: + os.unlink(imgpath) + return + # quit now if we don't need rerender + if not needs_rerender: + return + #logging.debug("writing out innertile {0}".format(imgpath)) + + # Create the actual image now + img = Image.new("RGBA", (384, 384), (38,92,255,0)) + + # we'll use paste (NOT alpha_over) for quadtree generation because + # this is just straight image stitching, not alpha blending + + for path in quadPath_filtered: + try: + quad = Image.open(path[1]).resize((192,192), Image.ANTIALIAS) + img.paste(quad, path[0]) + except Exception, e: + logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", path[1], e) + + # Save it + if self.imgformat == 'jpg': + img.save(imgpath, quality=95, subsampling=0) + else: # png + img.save(imgpath) + + if self.optimizeimg: + optimize_image(imgpath, self.imgformat, self.optimizeimg) + + + + def render_worldtile(self, chunks, colstart, colend, rowstart, rowend, path): + """Renders just the specified chunks into a tile and save it. Unlike usual + python conventions, rowend and colend are inclusive. Additionally, the + chunks around the edges are half-way cut off (so that neighboring tiles + will render the other half) + + chunks is a list of (col, row, chunkx, chunky, filename) of chunk + images that are relevant to this call (with their associated regions) + + The image is saved to path+"."+self.imgformat + + If there are no chunks, this tile is not saved (if it already exists, it is + deleted) + + Standard tile size has colend-colstart=2 and rowend-rowstart=4 + + There is no return value + """ + + # width of one chunk is 384. Each column is half a chunk wide. The total + # width is (384 + 192*(numcols-1)) since the first column contributes full + # width, and each additional one contributes half since they're staggered. + # However, since we want to cut off half a chunk at each end (384 less + # pixels) and since (colend - colstart + 1) is the number of columns + # inclusive, the equation simplifies to: + width = 192 * (colend - colstart) + # Same deal with height + height = 96 * (rowend - rowstart) + + # The standard tile size is 3 columns by 5 rows, which works out to 384x384 + # pixels for 8 total chunks. (Since the chunks are staggered but the grid + # is not, some grid coordinates do not address chunks) The two chunks on + # the middle column are shown in full, the two chunks in the middle row are + # half cut off, and the four remaining chunks are one quarter shown. + # The above example with cols 0-3 and rows 0-4 has the chunks arranged like this: + # 0,0 2,0 + # 1,1 + # 0,2 2,2 + # 1,3 + # 0,4 2,4 + + # Due to how the tiles fit together, we may need to render chunks way above + # this (since very few chunks actually touch the top of the sky, some tiles + # way above this one are possibly visible in this tile). Render them + # anyways just in case). "chunks" should include up to rowstart-16 + + imgpath = path + "." + self.imgformat + world = self.world + #stat the file, we need to know if it exists or it's mtime + try: + tile_mtime = os.stat(imgpath)[stat.ST_MTIME]; + except OSError, e: + if e.errno != errno.ENOENT: + raise + tile_mtime = None + + if not chunks: + # No chunks were found in this tile + if tile_mtime is not None: + os.unlink(imgpath) + return None + + # Create the directory if not exists + dirdest = os.path.dirname(path) + if not os.path.exists(dirdest): + try: + os.makedirs(dirdest) + except OSError, e: + # Ignore errno EEXIST: file exists. Since this is multithreaded, + # two processes could conceivably try and create the same directory + # at the same time. + if e.errno != errno.EEXIST: + raise + + # check chunk mtimes to see if they are newer + try: + needs_rerender = False + for col, row, chunkx, chunky, regionfile in chunks: + # check region file mtime first. + region,regionMtime = world.get_region_mtime(regionfile) + if regionMtime <= tile_mtime: continue - # return (col, row, chunkx, chunky, regionpath) - chunkx, chunky = unconvert_coords(col, row) - #c = get_region_path(chunkx, chunky) - _, _, c, mcr = get_region((chunkx//32, chunky//32),(None,None,None,None)); - if c is not None and mcr.chunkExists(chunkx,chunky): - chunklist.append((col, row, chunkx, chunky, c)) - return chunklist - -@catch_keyboardinterrupt -def render_innertile_batch(batch): - global child_quadtree - quadtree = child_quadtree - count = 0 - #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) - for job in batch: - count += 1 - dest = quadtree.full_tiledir+os.sep+job[0] - render_innertile(dest,job[1],job[2],job[3]) - return count - -def render_innertile(dest, name, imgformat, optimizeimg): - """ - Renders a tile at os.path.join(dest, name)+".ext" by taking tiles from - os.path.join(dest, name, "{0,1,2,3}.png") - """ - imgpath = os.path.join(dest, name) + "." + imgformat - - if name == "base": - quadPath = [[(0,0),os.path.join(dest, "0." + imgformat)],[(192,0),os.path.join(dest, "1." + imgformat)], [(0, 192),os.path.join(dest, "2." + imgformat)],[(192,192),os.path.join(dest, "3." + imgformat)]] - else: - quadPath = [[(0,0),os.path.join(dest, name, "0." + imgformat)],[(192,0),os.path.join(dest, name, "1." + imgformat)],[(0, 192),os.path.join(dest, name, "2." + imgformat)],[(192,192),os.path.join(dest, name, "3." + imgformat)]] - - #stat the tile, we need to know if it exists or it's mtime - try: - tile_mtime = os.stat(imgpath)[stat.ST_MTIME]; - except OSError, e: - if e.errno != errno.ENOENT: - raise - tile_mtime = None - - #check mtimes on each part of the quad, this also checks if they exist - needs_rerender = tile_mtime is None - quadPath_filtered = [] - for path in quadPath: - try: - quad_mtime = os.stat(path[1])[stat.ST_MTIME]; - quadPath_filtered.append(path) - if quad_mtime > tile_mtime: - needs_rerender = True + # checking chunk mtime + if region.get_chunk_timestamp(chunkx, chunky) > tile_mtime: + needs_rerender = True + break + + # if after all that, we don't need a rerender, return + if not needs_rerender: + return None except OSError: - # We need to stat all the quad files, so keep looping - pass - # do they all not exist? - if quadPath_filtered == []: - if tile_mtime is not None: - os.unlink(imgpath) - return - # quit now if we don't need rerender - if not needs_rerender: - return - #logging.debug("writing out innertile {0}".format(imgpath)) - - # Create the actual image now - img = Image.new("RGBA", (384, 384), (38,92,255,0)) - - # we'll use paste (NOT alpha_over) for quadtree generation because - # this is just straight image stitching, not alpha blending - - for path in quadPath_filtered: - try: - quad = Image.open(path[1]).resize((192,192), Image.ANTIALIAS) - img.paste(quad, path[0]) - except Exception, e: - logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", path[1], e) - - # Save it - if imgformat == 'jpg': - img.save(imgpath, quality=95, subsampling=0) - else: # png - img.save(imgpath) - if optimizeimg: - optimize_image(imgpath, imgformat, optimizeimg) - -@catch_keyboardinterrupt -def render_worldtile_batch(batch): - global child_quadtree - quadtree = child_quadtree - count = 0 - _get_chunks_in_range = quadtree._get_chunks_in_range - #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) - for job in batch: - count += 1 - colstart = job[0] - colend = job[1] - rowstart = job[2] - rowend = job[3] - path = job[4] - path = quadtree.full_tiledir+os.sep+path - # (even if tilechunks is empty, render_worldtile will delete - # existing images if appropriate) - # And uses these chunks - tilechunks = _get_chunks_in_range(colstart, colend, rowstart,rowend) - #logging.debug(" tilechunks: %r", tilechunks) - - render_worldtile(quadtree,tilechunks,colstart, colend, rowstart, rowend, path) - return count - -def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path): - """Renders just the specified chunks into a tile and save it. Unlike usual - python conventions, rowend and colend are inclusive. Additionally, the - chunks around the edges are half-way cut off (so that neighboring tiles - will render the other half) - - chunks is a list of (col, row, chunkx, chunky, filename) of chunk - images that are relevant to this call (with their associated regions) - - The image is saved to path+"."+quadtree.imgformat - - If there are no chunks, this tile is not saved (if it already exists, it is - deleted) - - Standard tile size has colend-colstart=2 and rowend-rowstart=4 - - There is no return value - """ - - # width of one chunk is 384. Each column is half a chunk wide. The total - # width is (384 + 192*(numcols-1)) since the first column contributes full - # width, and each additional one contributes half since they're staggered. - # However, since we want to cut off half a chunk at each end (384 less - # pixels) and since (colend - colstart + 1) is the number of columns - # inclusive, the equation simplifies to: - width = 192 * (colend - colstart) - # Same deal with height - height = 96 * (rowend - rowstart) - - # The standard tile size is 3 columns by 5 rows, which works out to 384x384 - # pixels for 8 total chunks. (Since the chunks are staggered but the grid - # is not, some grid coordinates do not address chunks) The two chunks on - # the middle column are shown in full, the two chunks in the middle row are - # half cut off, and the four remaining chunks are one quarter shown. - # The above example with cols 0-3 and rows 0-4 has the chunks arranged like this: - # 0,0 2,0 - # 1,1 - # 0,2 2,2 - # 1,3 - # 0,4 2,4 - - # Due to how the tiles fit together, we may need to render chunks way above - # this (since very few chunks actually touch the top of the sky, some tiles - # way above this one are possibly visible in this tile). Render them - # anyways just in case). "chunks" should include up to rowstart-16 - - imgpath = path + "." + quadtree.imgformat - world = quadtree.world - #stat the file, we need to know if it exists or it's mtime - try: - tile_mtime = os.stat(imgpath)[stat.ST_MTIME]; - except OSError, e: - if e.errno != errno.ENOENT: - raise - tile_mtime = None + # couldn't get tile mtime, skip check + pass - if not chunks: - # No chunks were found in this tile - if tile_mtime is not None: - os.unlink(imgpath) - return None + #logging.debug("writing out worldtile {0}".format(imgpath)) - # Create the directory if not exists - dirdest = os.path.dirname(path) - if not os.path.exists(dirdest): - try: - os.makedirs(dirdest) - except OSError, e: - # Ignore errno EEXIST: file exists. Since this is multithreaded, - # two processes could conceivably try and create the same directory - # at the same time. - if e.errno != errno.EEXIST: - raise - - # check chunk mtimes to see if they are newer - try: - needs_rerender = False + # Compile this image + tileimg = Image.new("RGBA", (width, height), (38,92,255,0)) + + # col colstart will get drawn on the image starting at x coordinates -(384/2) + # row rowstart will get drawn on the image starting at y coordinates -(192/2) for col, row, chunkx, chunky, regionfile in chunks: - # check region file mtime first. - region,regionMtime = world.get_region_mtime(regionfile) - if regionMtime <= tile_mtime: - continue - - # checking chunk mtime - if region.get_chunk_timestamp(chunkx, chunky) > tile_mtime: - needs_rerender = True - break - - # if after all that, we don't need a rerender, return - if not needs_rerender: - return None - except OSError: - # couldn't get tile mtime, skip check - pass - - #logging.debug("writing out worldtile {0}".format(imgpath)) + xpos = -192 + (col-colstart)*192 + ypos = -96 + (row-rowstart)*96 - # Compile this image - tileimg = Image.new("RGBA", (width, height), (38,92,255,0)) + # draw the chunk! + # TODO POI queue + chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), self, False, None) - # col colstart will get drawn on the image starting at x coordinates -(384/2) - # row rowstart will get drawn on the image starting at y coordinates -(192/2) - for col, row, chunkx, chunky, regionfile in chunks: - xpos = -192 + (col-colstart)*192 - ypos = -96 + (row-rowstart)*96 + # Save them + tileimg.save(imgpath) - # draw the chunk! - # TODO POI queue - chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), quadtree, False, None) - - # Save them - tileimg.save(imgpath) - - if quadtree.optimizeimg: - optimize_image(imgpath, quadtree.imgformat, quadtree.optimizeimg) - -class FakeResult(object): - def __init__(self, res): - self.res = res - def get(self): - return self.res -class FakePool(object): - """A fake pool used to render things in sync. Implements a subset of - multiprocessing.Pool""" - def apply_async(self, func, args=(), kwargs=None): - if not kwargs: - kwargs = {} - result = func(*args, **kwargs) - return FakeResult(result) - def close(self): - pass - def join(self): - pass + if self.optimizeimg: + optimize_image(imgpath, self.imgformat, self.optimizeimg) diff --git a/rendernode.py b/rendernode.py new file mode 100644 index 0000000..5680beb --- /dev/null +++ b/rendernode.py @@ -0,0 +1,346 @@ +# This file is part of the Minecraft Overviewer. +# +# Minecraft Overviewer 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. +# +# Minecraft Overviewer 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 the Overviewer. If not, see . + +import multiprocessing +import itertools +from itertools import cycle, islice +import os +import os.path +import functools +import re +import shutil +import collections +import json +import logging +import util +import cPickle +import stat +import errno +import time +from time import gmtime, strftime, sleep + + +""" +This module has routines related to distributing the render job to multipule nodes + +""" + +def catch_keyboardinterrupt(func): + """Decorator that catches a keyboardinterrupt and raises a real exception + so that multiprocessing will propagate it properly""" + @functools.wraps(func) + def newfunc(*args, **kwargs): + try: + return func(*args, **kwargs) + except KeyboardInterrupt: + logging.error("Ctrl-C caught!") + raise Exception("Exiting") + except: + import traceback + traceback.print_exc() + raise + return newfunc + +child_rendernode = None +def pool_initializer(rendernode): + logging.debug("Child process {0}".format(os.getpid())) + #stash the quadtree objects in a global variable after fork() for windows compat. + global child_rendernode + child_rendernode = rendernode + +#http://docs.python.org/library/itertools.html +def roundrobin(iterables): + "roundrobin('ABC', 'D', 'EF') --> A D E B F C" + # Recipe credited to George Sakkis + pending = len(iterables) + nexts = cycle(iter(it).next for it in iterables) + while pending: + try: + for next in nexts: + yield next() + except StopIteration: + pending -= 1 + nexts = cycle(islice(nexts, pending)) + + +class RenderNode(object): + def __init__(self, world, quadtrees): + """Distributes the rendering of a list of quadtrees. All of the quadtrees must have the same world.""" + + if not len(quadtrees) > 0: + raise ValueError("there must be at least one quadtree to work on") + + self.world = world + self.quadtrees = quadtrees + #bind an index value to the quadtree so we can find it again + i = 0 + for q in quadtrees: + q._render_index = i + i += 1 + + def print_statusline(self, complete, total, level, unconditional=False): + if unconditional: + pass + elif complete < 100: + if not complete % 25 == 0: + return + elif complete < 1000: + if not complete % 100 == 0: + return + else: + if not complete % 1000 == 0: + return + logging.info("{0}/{1} tiles complete on level {2}/{3}".format( + complete, total, level, self.max_p)) + + def go(self, procs): + """Renders all tiles""" + + logging.debug("Parent process {0}".format(os.getpid())) + # Create a pool + if procs == 1: + pool = FakePool() + global child_rendernode + child_rendernode = self + else: + pool = multiprocessing.Pool(processes=procs,initializer=pool_initializer,initargs=(self,)) + #warm up the pool so it reports all the worker id's + pool.map(bool,xrange(multiprocessing.cpu_count()),1) + + quadtrees = self.quadtrees + + # do per-quadtree init. + max_p = 0 + total = 0 + for q in quadtrees: + total += 4**q.p + if q.p > max_p: + max_p = q.p + q.go(procs) + self.max_p = max_p + # Render the highest level of tiles from the chunks + results = collections.deque() + complete = 0 + logging.info("Rendering highest zoom level of tiles now.") + logging.info("Rendering {0} layer{1}".format(len(quadtrees),'s' if len(quadtrees) > 1 else '' )) + logging.info("There are {0} tiles to render".format(total)) + logging.info("There are {0} total levels to render".format(self.max_p)) + logging.info("Don't worry, each level has only 25% as many tiles as the last.") + logging.info("The others will go faster") + count = 0 + batch_size = 4*len(quadtrees) + while batch_size < 10: + batch_size *= 2 + timestamp = time.time() + for result in self._apply_render_worldtiles(pool,batch_size): + results.append(result) + # every second drain some of the queue + timestamp2 = time.time() + if timestamp2 >= timestamp + 1: + timestamp = timestamp2 + count_to_remove = (1000//batch_size) + if count_to_remove < len(results): + while count_to_remove > 0: + count_to_remove -= 1 + complete += results.popleft().get() + self.print_statusline(complete, total, 1) + if len(results) > (10000//batch_size): + # Empty the queue before adding any more, so that memory + # required has an upper bound + while len(results) > (500//batch_size): + complete += results.popleft().get() + self.print_statusline(complete, total, 1) + + # Wait for the rest of the results + while len(results) > 0: + complete += results.popleft().get() + self.print_statusline(complete, total, 1) + + self.print_statusline(complete, total, 1, True) + + # Now do the other layers + for zoom in xrange(self.max_p-1, 0, -1): + level = self.max_p - zoom + 1 + assert len(results) == 0 + complete = 0 + total = 0 + for q in quadtrees: + if zoom <= q.p: + total += 4**zoom + logging.info("Starting level {0}".format(level)) + timestamp = time.time() + for result in self._apply_render_inntertile(pool, zoom,batch_size): + results.append(result) + # every second drain some of the queue + timestamp2 = time.time() + if timestamp2 >= timestamp + 1: + timestamp = timestamp2 + count_to_remove = (1000//batch_size) + if count_to_remove < len(results): + while count_to_remove > 0: + count_to_remove -= 1 + complete += results.popleft().get() + self.print_statusline(complete, total, 1) + if len(results) > (10000/batch_size): + while len(results) > (500/batch_size): + complete += results.popleft().get() + self.print_statusline(complete, total, level) + # Empty the queue + while len(results) > 0: + complete += results.popleft().get() + self.print_statusline(complete, total, level) + + self.print_statusline(complete, total, level, True) + + logging.info("Done") + + pool.close() + pool.join() + + # Do the final one right here: + for q in quadtrees: + q.render_innertile(os.path.join(q.destdir, q.tiledir), "base") + + + def _get_chunks_in_range(self, colstart, colend, rowstart, rowend): + """Get chunks that are relevant to the tile rendering function that's + rendering that range""" + chunklist = [] + unconvert_coords = self.world.unconvert_coords + #get_region_path = self.world.get_region_path + get_region = self.world.regionfiles.get + for row in xrange(rowstart-16, rowend+1): + for col in xrange(colstart, colend+1): + # due to how chunks are arranged, we can only allow + # even row, even column or odd row, odd column + # otherwise, you end up with duplicates! + if row % 2 != col % 2: + continue + + # return (col, row, chunkx, chunky, regionpath) + chunkx, chunky = unconvert_coords(col, row) + #c = get_region_path(chunkx, chunky) + _, _, c, mcr = get_region((chunkx//32, chunky//32),(None,None,None,None)); + if c is not None and mcr.chunkExists(chunkx,chunky): + chunklist.append((col, row, chunkx, chunky, c)) + return chunklist + + + + def _apply_render_worldtiles(self, pool,batch_size): + """Returns an iterator over result objects. Each time a new result is + requested, a new task is added to the pool and a result returned. + """ + if batch_size < len(self.quadtrees): + batch_size = len(self.quadtrees) + batch = [] + jobcount = 0 + # roundrobin add tiles to a batch job (thus they should all roughly work on similar chunks) + iterables = [q.get_worldtiles() for q in self.quadtrees] + for job in roundrobin(iterables): + # fixup so the worker knows which quadtree this is + job[0] = job[0]._render_index + # Put this in the batch to be submited to the pool + batch.append(job) + jobcount += 1 + if jobcount >= batch_size: + jobcount = 0 + yield pool.apply_async(func=render_worldtile_batch, args= [batch]) + batch = [] + if jobcount > 0: + yield pool.apply_async(func=render_worldtile_batch, args= [batch]) + + def _apply_render_inntertile(self, pool, zoom,batch_size): + """Same as _apply_render_worltiles but for the inntertile routine. + Returns an iterator that yields result objects from tasks that have + been applied to the pool. + """ + + if batch_size < len(self.quadtrees): + batch_size = len(self.quadtrees) + batch = [] + jobcount = 0 + # roundrobin add tiles to a batch job (thus they should all roughly work on similar chunks) + iterables = [q.get_innertiles(zoom) for q in self.quadtrees if zoom <= q.p] + for job in roundrobin(iterables): + # fixup so the worker knows which quadtree this is + job[0] = job[0]._render_index + # Put this in the batch to be submited to the pool + batch.append(job) + jobcount += 1 + if jobcount >= batch_size: + jobcount = 0 + yield pool.apply_async(func=render_innertile_batch, args= [batch]) + batch = [] + + if jobcount > 0: + yield pool.apply_async(func=render_innertile_batch, args= [batch]) + +@catch_keyboardinterrupt +def render_worldtile_batch(batch): + global child_rendernode + rendernode = child_rendernode + count = 0 + _get_chunks_in_range = rendernode._get_chunks_in_range + #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) + for job in batch: + count += 1 + quadtree = rendernode.quadtrees[job[0]] + colstart = job[1] + colend = job[2] + rowstart = job[3] + rowend = job[4] + path = job[5] + path = quadtree.full_tiledir+os.sep+path + # (even if tilechunks is empty, render_worldtile will delete + # existing images if appropriate) + # And uses these chunks + tilechunks = _get_chunks_in_range(colstart, colend, rowstart,rowend) + #logging.debug(" tilechunks: %r", tilechunks) + + quadtree.render_worldtile(tilechunks,colstart, colend, rowstart, rowend, path) + return count + +@catch_keyboardinterrupt +def render_innertile_batch(batch): + global child_rendernode + rendernode = child_rendernode + count = 0 + #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) + for job in batch: + count += 1 + quadtree = rendernode.quadtrees[job[0]] + dest = quadtree.full_tiledir+os.sep+job[1] + quadtree.render_innertile(dest=dest,name=job[2]) + return count + +class FakeResult(object): + def __init__(self, res): + self.res = res + def get(self): + return self.res +class FakePool(object): + """A fake pool used to render things in sync. Implements a subset of + multiprocessing.Pool""" + def apply_async(self, func, args=(), kwargs=None): + if not kwargs: + kwargs = {} + result = func(*args, **kwargs) + return FakeResult(result) + def close(self): + pass + def join(self): + pass + \ No newline at end of file From c7920ce61e726777a77d54b0512967806d1a6285 Mon Sep 17 00:00:00 2001 From: Xon Date: Wed, 23 Mar 2011 23:23:42 +0800 Subject: [PATCH 100/213] Fixed multi-world support in multi-layer renderer --- quadtree.py | 23 +++++++++++++++++++++++ rendernode.py | 34 +++------------------------------- 2 files changed, 26 insertions(+), 31 deletions(-) diff --git a/quadtree.py b/quadtree.py index 60c097d..2b532a1 100644 --- a/quadtree.py +++ b/quadtree.py @@ -215,6 +215,29 @@ class QuadtreeGen(object): return x, y + def get_chunks_in_range(self, colstart, colend, rowstart, rowend): + """Get chunks that are relevant to the tile rendering function that's + rendering that range""" + chunklist = [] + unconvert_coords = self.world.unconvert_coords + #get_region_path = self.world.get_region_path + get_region = self.world.regionfiles.get + for row in xrange(rowstart-16, rowend+1): + for col in xrange(colstart, colend+1): + # due to how chunks are arranged, we can only allow + # even row, even column or odd row, odd column + # otherwise, you end up with duplicates! + if row % 2 != col % 2: + continue + + # return (col, row, chunkx, chunky, regionpath) + chunkx, chunky = unconvert_coords(col, row) + #c = get_region_path(chunkx, chunky) + _, _, c, mcr = get_region((chunkx//32, chunky//32),(None,None,None,None)); + if c is not None and mcr.chunkExists(chunkx,chunky): + chunklist.append((col, row, chunkx, chunky, c)) + return chunklist + def get_worldtiles(self): """Returns an iterator over the tiles of the most detailed layer """ diff --git a/rendernode.py b/rendernode.py index 5680beb..6cfae8a 100644 --- a/rendernode.py +++ b/rendernode.py @@ -76,13 +76,12 @@ def roundrobin(iterables): class RenderNode(object): - def __init__(self, world, quadtrees): - """Distributes the rendering of a list of quadtrees. All of the quadtrees must have the same world.""" + def __init__(self, quadtrees): + """Distributes the rendering of a list of quadtrees.""" if not len(quadtrees) > 0: raise ValueError("there must be at least one quadtree to work on") - self.world = world self.quadtrees = quadtrees #bind an index value to the quadtree so we can find it again i = 0 @@ -213,32 +212,6 @@ class RenderNode(object): for q in quadtrees: q.render_innertile(os.path.join(q.destdir, q.tiledir), "base") - - def _get_chunks_in_range(self, colstart, colend, rowstart, rowend): - """Get chunks that are relevant to the tile rendering function that's - rendering that range""" - chunklist = [] - unconvert_coords = self.world.unconvert_coords - #get_region_path = self.world.get_region_path - get_region = self.world.regionfiles.get - for row in xrange(rowstart-16, rowend+1): - for col in xrange(colstart, colend+1): - # due to how chunks are arranged, we can only allow - # even row, even column or odd row, odd column - # otherwise, you end up with duplicates! - if row % 2 != col % 2: - continue - - # return (col, row, chunkx, chunky, regionpath) - chunkx, chunky = unconvert_coords(col, row) - #c = get_region_path(chunkx, chunky) - _, _, c, mcr = get_region((chunkx//32, chunky//32),(None,None,None,None)); - if c is not None and mcr.chunkExists(chunkx,chunky): - chunklist.append((col, row, chunkx, chunky, c)) - return chunklist - - - def _apply_render_worldtiles(self, pool,batch_size): """Returns an iterator over result objects. Each time a new result is requested, a new task is added to the pool and a result returned. @@ -293,7 +266,6 @@ def render_worldtile_batch(batch): global child_rendernode rendernode = child_rendernode count = 0 - _get_chunks_in_range = rendernode._get_chunks_in_range #logging.debug("{0} working on batch of size {1}".format(os.getpid(),len(batch))) for job in batch: count += 1 @@ -307,7 +279,7 @@ def render_worldtile_batch(batch): # (even if tilechunks is empty, render_worldtile will delete # existing images if appropriate) # And uses these chunks - tilechunks = _get_chunks_in_range(colstart, colend, rowstart,rowend) + tilechunks = quadtree.get_chunks_in_range(colstart, colend, rowstart,rowend) #logging.debug(" tilechunks: %r", tilechunks) quadtree.render_worldtile(tilechunks,colstart, colend, rowstart, rowend, path) From e55b7045ea0a546ce405a0d90c428a2a6c37276f Mon Sep 17 00:00:00 2001 From: Xon Date: Wed, 23 Mar 2011 23:40:48 +0800 Subject: [PATCH 101/213] Updated findSigns.py & rerenderBlocks.py to work against region format --- chunk.py | 8 ++++---- contrib/findSigns.py | 38 +++++++++++++++++++++----------------- contrib/rerenderBlocks.py | 21 +++++++++++++-------- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/chunk.py b/chunk.py index 63de064..b9bc817 100644 --- a/chunk.py +++ b/chunk.py @@ -73,10 +73,10 @@ def get_blockarray(level): Block array, which just contains all the block ids""" return numpy.frombuffer(level['Blocks'], dtype=numpy.uint8).reshape((16,16,128)) -def get_blockarray_fromfile(world,filename): - """Same as get_blockarray except takes a filename and uses get_lvldata to - open it. This is a shortcut""" - level = get_lvldata(world,filename) +def get_blockarray_fromfile(filename): + """Same as get_blockarray except takes a filename. This is a shortcut""" + d = nbt.load_from_region(filename, x, y) + level = return d[1]['Level'] return get_blockarray(level) def get_skylight_array(level): diff --git a/contrib/findSigns.py b/contrib/findSigns.py index c390654..9e3b621 100644 --- a/contrib/findSigns.py +++ b/contrib/findSigns.py @@ -33,30 +33,34 @@ if os.path.exists(worlddir): else: sys.exit("Bad WorldDir") -matcher = re.compile(r"^c\..*\.dat$") +matcher = re.compile(r"^r\..*\.mcr$") POI = [] for dirpath, dirnames, filenames in os.walk(worlddir): for f in filenames: if matcher.match(f): + print f full = os.path.join(dirpath, f) - #print "inspecting %s" % full - data = nbt.load(full)[1]['Level']['TileEntities'] - for entity in data: - if entity['id'] == 'Sign': - msg=' \n'.join([entity['Text1'], entity['Text2'], entity['Text3'], entity['Text4']]) - #print "checking -->%s<--" % msg.strip() - if msg.strip(): - newPOI = dict(type="sign", - x= entity['x'], - y= entity['y'], - z= entity['z'], - msg=msg, - chunk= (entity['x']/16, entity['z']/16), - ) - POI.append(newPOI) - print "Found sign at (%d, %d, %d): %r" % (newPOI['x'], newPOI['y'], newPOI['z'], newPOI['msg']) + r = nbt.load_region(full) + chunks = r.get_chunks() + for x,y in chunks: + chunk = r.load_chunk(x,y).read_all() + data = chunk[1]['Level']['TileEntities'] + for entity in data: + if entity['id'] == 'Sign': + msg=' \n'.join([entity['Text1'], entity['Text2'], entity['Text3'], entity['Text4']]) + #print "checking -->%s<--" % msg.strip() + if msg.strip(): + newPOI = dict(type="sign", + x= entity['x'], + y= entity['y'], + z= entity['z'], + msg=msg, + chunk= (entity['x']/16, entity['z']/16), + ) + POI.append(newPOI) + print "Found sign at (%d, %d, %d): %r" % (newPOI['x'], newPOI['y'], newPOI['z'], newPOI['msg']) diff --git a/contrib/rerenderBlocks.py b/contrib/rerenderBlocks.py index 51940df..d0fa0df 100644 --- a/contrib/rerenderBlocks.py +++ b/contrib/rerenderBlocks.py @@ -7,8 +7,8 @@ blockID. The output is a chunklist file that is suitable to use with the Example: -python contrib/rerenderBlocks.py --ids=46,79,91 --world=world/> chunklist.txt - python overviewer.py --chunklist=chunklist.txt world/ output_dir/ +python contrib/rerenderBlocks.py --ids=46,79,91 --world=world/> regionlist.txt + python overviewer.py --regionlist=regionlist.txt world/ output_dir/ This will rerender any chunks that contain either TNT (46), Ice (79), or a Jack-O-Lantern (91) @@ -42,15 +42,20 @@ ids = map(lambda x: int(x),options.ids.split(",")) sys.stderr.write("Searching for these blocks: %r...\n" % ids) -matcher = re.compile(r"^c\..*\.dat$") +matcher = re.compile(r"^r\..*\.mcr$") for dirpath, dirnames, filenames in os.walk(options.world): for f in filenames: if matcher.match(f): full = os.path.join(dirpath, f) - blocks = get_blockarray_fromfile(full) - for i in ids: - if i in blocks: - print full - break + r = nbt.load_region(full) + chunks = r.get_chunks() + for x,y in chunks: + chunk = r.load_chunk(x,y).read_all() + blocks = get_blockarray(chunk[1]['Level']) + for i in ids: + if i in blocks: + print full + break + From 01790913b309a8298772a27fdd7f1290e2025af2 Mon Sep 17 00:00:00 2001 From: Xon Date: Fri, 25 Mar 2011 21:34:06 +0800 Subject: [PATCH 102/213] Check PyImport_ImportModule return result --- overviewer.py | 2 ++ src/iterate.c | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/overviewer.py b/overviewer.py index cb1b0a9..2c626c2 100755 --- a/overviewer.py +++ b/overviewer.py @@ -32,6 +32,8 @@ import util logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s") +#import this before to ensure it doesn't have an errors or c_overviewer will eat them +import chunk # make sure the c_overviewer extension is available try: import c_overviewer diff --git a/src/iterate.c b/src/iterate.c index 8b93cd8..1dcce1a 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -34,6 +34,12 @@ int init_chunk_render(void) { textures = PyImport_ImportModule("textures"); chunk_mod = PyImport_ImportModule("chunk"); + /* ensure none of these pointers are NULL */ + if ((!textures) || (!chunk_mod)) { + fprintf(stderr, "\ninit_chunk_render failed\n"); + return 1; + } + blockmap = PyObject_GetAttrString(textures, "blockmap"); special_blocks = PyObject_GetAttrString(textures, "special_blocks"); specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); From 21d04cd3fbeddd90b097128672fb0c28218d7212 Mon Sep 17 00:00:00 2001 From: Xon Date: Fri, 25 Mar 2011 21:42:18 +0800 Subject: [PATCH 103/213] Better error messages, removed import which triggered a failure --- overviewer.py | 2 -- src/iterate.c | 13 +++++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/overviewer.py b/overviewer.py index 2c626c2..cb1b0a9 100755 --- a/overviewer.py +++ b/overviewer.py @@ -32,8 +32,6 @@ import util logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s") -#import this before to ensure it doesn't have an errors or c_overviewer will eat them -import chunk # make sure the c_overviewer extension is available try: import c_overviewer diff --git a/src/iterate.c b/src/iterate.c index 1dcce1a..f535f39 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -32,11 +32,16 @@ int init_chunk_render(void) { if (blockmap) return 1; textures = PyImport_ImportModule("textures"); - chunk_mod = PyImport_ImportModule("chunk"); - /* ensure none of these pointers are NULL */ - if ((!textures) || (!chunk_mod)) { - fprintf(stderr, "\ninit_chunk_render failed\n"); + if ((!textures)) { + fprintf(stderr, "\ninit_chunk_render failed to load; textures\n"); + return 1; + } + + chunk_mod = PyImport_ImportModule("chunk"); + /* ensure none of these pointers are NULL */ + if ((!chunk_mod)) { + fprintf(stderr, "\ninit_chunk_render failed to load; chunk\n"); return 1; } From af3278e3ccccaf768de192a2673d6b5acb8c279c Mon Sep 17 00:00:00 2001 From: Xon Date: Fri, 25 Mar 2011 21:52:34 +0800 Subject: [PATCH 104/213] Merge fix ups --- chunk.py | 2 +- overviewer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/chunk.py b/chunk.py index b9bc817..761ea86 100644 --- a/chunk.py +++ b/chunk.py @@ -76,7 +76,7 @@ def get_blockarray(level): def get_blockarray_fromfile(filename): """Same as get_blockarray except takes a filename. This is a shortcut""" d = nbt.load_from_region(filename, x, y) - level = return d[1]['Level'] + level = d[1]['Level'] return get_blockarray(level) def get_skylight_array(level): diff --git a/overviewer.py b/overviewer.py index cb1b0a9..3e37dbc 100755 --- a/overviewer.py +++ b/overviewer.py @@ -186,7 +186,7 @@ def main(): q.append(quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode='spawn')) #create the distributed render - r = rendernode.RenderNode(w,q) + r = rendernode.RenderNode(q) # write out the map and web assets m = googlemap.MapGen(q, skipjs=options.skipjs, web_assets_hook=options.web_assets_hook) From 84988aa2f6cf45c36486f40ee5307e9bc7d00998 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Fri, 25 Mar 2011 19:48:55 -0400 Subject: [PATCH 105/213] 0-byte region files no longer crash the whole program --- nbt.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/nbt.py b/nbt.py index 73730a0..075335c 100644 --- a/nbt.py +++ b/nbt.py @@ -217,12 +217,16 @@ class MCRFileReader(object): # go to the correct entry in the chunk location table self._file.seek(4 * (x + y * 32)) - # 3-byte offset in 4KiB sectors - offset_sectors = self._read_24bit_int() - - # 1-byte length in 4KiB sectors, rounded up - byte = self._file.read(1) - length_sectors = struct.unpack("B", byte)[0] + try: + # 3-byte offset in 4KiB sectors + offset_sectors = self._read_24bit_int() + + # 1-byte length in 4KiB sectors, rounded up + byte = self._file.read(1) + length_sectors = struct.unpack("B", byte)[0] + except (IndexError, struct.error): + # got a problem somewhere + return None # check for empty chunks if offset_sectors == 0 or length_sectors == 0: @@ -247,8 +251,11 @@ class MCRFileReader(object): # go to the correct entry in the chunk timestamp table self._file.seek(4 * (x + y * 32) + 4096) - bytes = self._file.read(4) - timestamp = struct.unpack(">I", bytes)[0] + try: + bytes = self._file.read(4) + timestamp = struct.unpack(">I", bytes)[0] + except (IndexError, struct.error): + return 0 return timestamp From 82e0bf53b9a32f2bfdef2d39add976462f041894 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Fri, 25 Mar 2011 20:38:25 -0400 Subject: [PATCH 106/213] Added a new 'listify' parameter to the config file parser Specifying listify on an option will cause it to be parsed as a list. Use listdelim to specify the delimiter (defaults to ',') Examples, assuming you had the following option: add_option("--test","test", type="int", listify=True) Command line: --test 1 results in [1] --test 1,2,3 results in [1,2,3] Config file: test=1 results in [1] test="1,2,3" results in [1,2,3] test=[1,4,9] results in [1,4,9] --- configParser.py | 61 +++++++++++++++++++++++++++++++------------------ overviewer.py | 6 +++-- 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/configParser.py b/configParser.py index 8ca9eba..cbdbc4c 100644 --- a/configParser.py +++ b/configParser.py @@ -13,9 +13,11 @@ class ConfigOptionParser(object): # these are arguments not understood by OptionParser, so they must be removed # in add_option before being passed to the OptionParser + # note that default is a valid OptionParser argument, but we remove it # because we want to do our default value handling - self.customArgs = ["required", "commandLineOnly", "default"] + + self.customArgs = ["required", "commandLineOnly", "default", "listify", "listdelim", "choices"] self.requiredArgs = [] @@ -35,6 +37,8 @@ class ConfigOptionParser(object): for arg in self.customArgs: if arg in kwargs.keys(): del kwargs[arg] + if kwargs.get("type", None): + kwargs['type'] = 'string' # we'll do our own converting later self.cmdParser.add_option(*args, **kwargs) def print_help(self): @@ -114,29 +118,15 @@ class ConfigOptionParser(object): # sixth, check types for a in self.configVars: n = a['dest'] + if 'listify' in a.keys(): + # this thing may be a list! + if configResults.__dict__[n] != None and type(configResults.__dict__[n]) == str: + configResults.__dict__[n] = configResults.__dict__[n].split(a.get("listdelim",",")) + elif type(configResults.__dict__[n]) != list: + configResults.__dict__[n] = [configResults.__dict__[n]] if 'type' in a.keys() and configResults.__dict__[n] != None: try: - # switch on type. there are only 6 types that can be used with optparse - if a['type'] == "int": - configResults.__dict__[n] = int(configResults.__dict__[n]) - elif a['type'] == "string": - configResults.__dict__[n] = str(configResults.__dict__[n]) - elif a['type'] == "long": - configResults.__dict__[n] = long(configResults.__dict__[n]) - elif a['type'] == "choice": - if configResults.__dict__[n] not in a['choices']: - print "The value '%s' is not valid for config parameter '%s'" % (configResults.__dict__[n], n) - sys.exit(1) - elif a['type'] == "float": - configResults.__dict__[n] = long(configResults.__dict__[n]) - elif a['type'] == "complex": - configResults.__dict__[n] = complex(configResults.__dict__[n]) - elif a['type'] == "function": - if not callable(configResults.__dict__[n]): - raise ValueError("Not callable") - else: - print "Unknown type!" - sys.exit(1) + configResults.__dict__[n] = self.checkType(configResults.__dict__[n], a) except ValueError, ex: print "There was a problem converting the value '%s' to type %s for config parameter '%s'" % (configResults.__dict__[n], a['type'], n) import traceback @@ -149,3 +139,30 @@ class ConfigOptionParser(object): return configResults, args + def checkType(self, value, a): + + if type(value) == list: + return map(lambda x: self.checkType(x, a), value) + + # switch on type. there are only 7 types that can be used with optparse + if a['type'] == "int": + return int(value) + elif a['type'] == "string": + return str(value) + elif a['type'] == "long": + return long(value) + elif a['type'] == "choice": + if value not in a['choices']: + print "The value '%s' is not valid for config parameter '%s'" % (value, a['dest']) + sys.exit(1) + return value + elif a['type'] == "float": + return long(value) + elif a['type'] == "complex": + return complex(value) + elif a['type'] == "function": + if not callable(value): + raise ValueError("Not callable") + else: + print "Unknown type!" + sys.exit(1) diff --git a/overviewer.py b/overviewer.py index ffc3882..f07bef1 100755 --- a/overviewer.py +++ b/overviewer.py @@ -64,7 +64,7 @@ def main(): parser.add_option("-z", "--zoom", dest="zoom", help="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", action="store", type="int", configFileOnly=True) parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true", commandLineOnly=True) parser.add_option("--chunklist", dest="chunklist", help="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.") - parser.add_option("--rendermode", dest="rendermode", help="Specifies the render type: normal (default), lighting, night, or spawn.", type="choice", choices=["normal", "lighting", "night", "spawn"], required=True, default="normal") + parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render type: normal (default), lighting, night, or spawn.", type="choice", choices=["normal", "lighting", "night", "spawn"], required=True, default="normal", listify=True) parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.", configFileOnly=True ) parser.add_option("--optimize-img", dest="optimizeimg", help="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%", configFileOnly=True) parser.add_option("--web-assets-hook", dest="web_assets_hook", help="If provided, run this function after the web assets have been copied, but before actual tile rendering begins. It should accept a QuadtreeGen object as its only argument.", action="store", metavar="SCRIPT", type="function", configFileOnly=True) @@ -76,6 +76,7 @@ def main(): options, args = parser.parse_args() + if options.version: print "Minecraft-Overviewer" print "Git version: %s" % util.findGitVersion() @@ -178,7 +179,8 @@ def main(): # create the quadtrees # TODO chunklist - q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode=options.rendermode) + # NOTE: options.rendermode is now a list of rendermodes. for now, always use the first one + q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, rendermode=options.rendermode[0]) # write out the map and web assets m = googlemap.MapGen([q,], skipjs=options.skipjs, web_assets_hook=options.web_assets_hook) From a6baf5b6e809b50be92a299a4cef70cea756004e Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Fri, 25 Mar 2011 20:45:25 -0400 Subject: [PATCH 107/213] Added missing import --- googlemap.py | 1 + 1 file changed, 1 insertion(+) diff --git a/googlemap.py b/googlemap.py index fbecc03..fc99270 100644 --- a/googlemap.py +++ b/googlemap.py @@ -15,6 +15,7 @@ import os import os.path +import stat import cPickle import Image import shutil From 16a4abe84317269bacf144a3f3863a83f2dd636b Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Fri, 25 Mar 2011 22:50:54 -0400 Subject: [PATCH 108/213] added my render timing script to contrib/ --- contrib/testRender.py | 144 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 144 insertions(+) create mode 100644 contrib/testRender.py diff --git a/contrib/testRender.py b/contrib/testRender.py new file mode 100644 index 0000000..7646dad --- /dev/null +++ b/contrib/testRender.py @@ -0,0 +1,144 @@ +#!/usr/bin/python + +import os, shutil, tempfile, time, sys, math, re +from subprocess import Popen, PIPE, STDOUT, CalledProcessError +from optparse import OptionParser + +def check_call(*args, **kwargs): + quiet = False + if "quiet" in kwargs.keys(): + quiet = kwargs["quiet"] + del kwargs["quiet"] + if quiet: + kwargs['stdout'] = PIPE + kwargs['stderr'] = STDOUT + p = Popen(*args, **kwargs) + output = "" + if quiet: + while p.poll() == None: + output += p.communicate()[0] + returncode = p.wait() + if returncode: + if quiet: + print output + raise CalledProcessError(returncode, args) + return returncode + +def check_output(*args, **kwargs): + kwargs['stdout'] = PIPE + # will hang for HUGE output... you were warned + p = Popen(*args, **kwargs) + returncode = p.wait() + if returncode: + raise CalledProcessError(returncode, args) + return p.communicate()[0] + +def clean_render(overviewerargs, quiet): + tempdir = tempfile.mkdtemp('mc-overviewer-test') + 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) + starttime = time.time() + check_call(['./overviewer.py',] + overviewerargs + [tempdir,], quiet=quiet) + endtime = time.time() + + return endtime - starttime + finally: + shutil.rmtree(tempdir, True) + +def get_stats(timelist): + stats = {} + + stats['count'] = len(timelist) + stats['minimum'] = min(timelist) + stats['maximum'] = max(timelist) + stats['average'] = sum(timelist) / float(len(timelist)) + + meandiff = map(lambda x: (x - stats['average'])**2, timelist) + stats['standard deviation'] = math.sqrt(sum(meandiff) / float(len(meandiff))) + + return stats + +commitre = re.compile('^commit ([a-z0-9]{40})$', re.MULTILINE) +branchre = re.compile('^\\* (.+)$', re.MULTILINE) +def get_current_commit(): + gittext = check_output(['git', 'branch']) + match = branchre.search(gittext) + if match and not ("no branch" in match.group(1)): + return match.group(1) + gittext = check_output(['git', 'show', 'HEAD']) + match = commitre.match(gittext) + if match == None: + return None + return match.group(1) + +def get_commits(gitrange): + gittext = check_output(['git', 'log', '--raw', '--reverse', gitrange]) + for match in commitre.finditer(gittext): + yield match.group(1) + +def set_commit(commit): + check_call(['git', 'checkout', commit], quiet=True) + +parser = OptionParser(usage="usage: %prog [options] -- [overviewer options/world]") +parser.add_option("-n", "--number", metavar="N", + action="store", type="int", dest="number", default=3, + help="number of renders per commit [default: 3]") +parser.add_option("-c", "--commits", metavar="RANGE", + action="append", type="string", dest="commits", default=[], + help="the commit (or range of commits) to test [default: current]") +parser.add_option("-v", "--verbose", + action="store_false", dest="quiet", default=True, + help="don't suppress overviewer output") +parser.add_option("-k", "--keep-going", + action="store_false", dest="fatal_errors", default=True, + help="don't stop testing when Overviewer croaks") +parser.add_option("-l", "--log", dest="log", default="", metavar="FILE", + help="log all test results to a file") + +(options, args) = parser.parse_args() + +if len(args) == 0: + parser.print_help() + sys.exit(0) + +commits = [] +for commit in options.commits: + if '..' in commit: + commits = get_commits(commit) + else: + commits.append(commit) +if not commits: + commits = [get_current_commit(),] + +log = None +if options.log != "": + log = open(options.log, "w") + +reset_commit = get_current_commit() +try: + for commit in commits: + print "testing commit", commit + set_commit(commit) + timelist = [] + print " -- ", + try: + for i in range(options.number): + sys.stdout.write(str(i+1)+" ") + sys.stdout.flush() + timelist.append(clean_render(args, options.quiet)) + print "... done" + stats = get_stats(timelist) + print stats + log.write("%s %s\n" % (commit, repr(stats))) + except CalledProcessError, e: + if options.fatal_errors: + print + print "Overviewer croaked, exiting..." + print "(to avoid this, use --keep-going)" + sys.exit(1) +finally: + set_commit(reset_commit) + if log: + log.close() From 71a2a024ccdc484f7c7118bb06b92fff9097c3bf Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 26 Mar 2011 00:06:24 -0400 Subject: [PATCH 109/213] Print tracebacks on error --- src/iterate.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/iterate.c b/src/iterate.c index f535f39..6f2b02b 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -35,6 +35,7 @@ int init_chunk_render(void) { /* ensure none of these pointers are NULL */ if ((!textures)) { fprintf(stderr, "\ninit_chunk_render failed to load; textures\n"); + PyErr_Print(); return 1; } @@ -42,6 +43,7 @@ int init_chunk_render(void) { /* ensure none of these pointers are NULL */ if ((!chunk_mod)) { fprintf(stderr, "\ninit_chunk_render failed to load; chunk\n"); + PyErr_Print(); return 1; } @@ -53,6 +55,7 @@ int init_chunk_render(void) { /* ensure none of these pointers are NULL */ if ((!transparent_blocks) || (!blockmap) || (!special_blocks) || (!specialblockmap)) { fprintf(stderr, "\ninit_chunk_render failed\n"); + PyErr_Print(); return 1; } From 729141d4261de6b92c3be0a74086f93b7277ad53 Mon Sep 17 00:00:00 2001 From: Xon Date: Sat, 26 Mar 2011 13:27:33 +0800 Subject: [PATCH 110/213] Initial chunk cache commit mk2 --- chunk.py | 6 +++-- nbt.py | 26 ++++++++++++++------- overviewer.py | 6 +---- quadtree.py | 3 ++- world.py | 64 +++++++++++++++++++++++++++++++++++++++------------ 5 files changed, 74 insertions(+), 31 deletions(-) diff --git a/chunk.py b/chunk.py index 45184c4..a66d4fb 100644 --- a/chunk.py +++ b/chunk.py @@ -52,7 +52,7 @@ def get_lvldata(world, filename, x, y, retries=2): # non existent region file doesn't mean corrupt chunk. if filename == None: - raise NoSuchChunk + return None try: d = world.load_from_region(filename, x, y) @@ -60,13 +60,15 @@ def get_lvldata(world, filename, x, y, retries=2): if retries > 0: # wait a little bit, and try again (up to `retries` times) time.sleep(1) + #make sure we reload region info + world.reload_region(filename) return get_lvldata(world, filename, x, y, retries=retries-1) else: logging.warning("Error opening chunk (%i, %i) in %s. It may be corrupt. %s", x, y, filename, e) raise ChunkCorrupt(str(e)) if not d: raise NoSuchChunk(x,y) - return d[1]['Level'] + return d def get_blockarray(level): """Takes the level struct as returned from get_lvldata, and returns the diff --git a/nbt.py b/nbt.py index 1f8fcca..aa30700 100644 --- a/nbt.py +++ b/nbt.py @@ -282,6 +282,17 @@ class MCRFileReader(object): return timestamp + def openfile(self): + #make sure we clean up + if self._file is None: + self._file = open(self._filename,'rb') + + def closefile(self): + #make sure we clean up + if self._file is not None: + self._file.close() + self._file = None + def get_chunks(self): """Return a list of all chunks contained in this region file, as a list of (x, y) coordinate tuples. To load these chunks, @@ -304,8 +315,7 @@ class MCRFileReader(object): if self._locations: return - if self._file is None: - self._file = open(self._filename,'rb') + self.openfile() self._chunks = None self._locations = [] @@ -325,9 +335,7 @@ class MCRFileReader(object): timestamp_append(self._read_chunk_timestamp()) if closeFile: - #free the file object since it isn't safe to be reused in child processes (seek point goes wonky!) - self._file.close() - self._file = None + self.closefile() return def get_chunk_timestamp(self, x, y): @@ -350,7 +358,7 @@ class MCRFileReader(object): location = self._locations[x + y * 32] return location is not None - def load_chunk(self, x, y): + def load_chunk(self, x, y,closeFile=True): """Return a NBTFileReader instance for the given chunk, or None if the given chunk doesn't exist in this region file. If you provide an x or y not between 0 and 31, it will be @@ -366,8 +374,8 @@ class MCRFileReader(object): if location is None: return None - if self._file is None: - self._file = open(self._filename,'rb'); + self.openfile() + # seek to the data self._file.seek(location[0]) @@ -391,4 +399,6 @@ class MCRFileReader(object): data = self._file.read(data_length - 1) data = StringIO.StringIO(data) + if closeFile: + self.closefile() return NBTFileReader(data, is_gzip=is_gzip) diff --git a/overviewer.py b/overviewer.py index ccdadd3..b2aa275 100755 --- a/overviewer.py +++ b/overviewer.py @@ -41,7 +41,6 @@ except ImportError: sys.exit(1) import optimizeimages -import composite import world import quadtree import googlemap @@ -166,10 +165,7 @@ def main(): logging.info("Welcome to Minecraft Overviewer!") logging.debug("Current log level: {0}".format(logging.getLogger().level)) - - if not composite.extension_alpha_over: - logging.info("Notice: alpha_over extension not found; using default PIL paste()") - + useBiomeData = os.path.exists(os.path.join(worlddir, 'biomes')) if not useBiomeData: logging.info("Notice: Not using biome data for tinting") diff --git a/quadtree.py b/quadtree.py index 2b532a1..77fac8b 100644 --- a/quadtree.py +++ b/quadtree.py @@ -410,9 +410,10 @@ class QuadtreeGen(object): # check chunk mtimes to see if they are newer try: needs_rerender = False + get_region_mtime = world.get_region_mtime for col, row, chunkx, chunky, regionfile in chunks: # check region file mtime first. - region,regionMtime = world.get_region_mtime(regionfile) + region,regionMtime = get_region_mtime(regionfile) if regionMtime <= tile_mtime: continue diff --git a/world.py b/world.py index 077a17e..4f1b4cf 100644 --- a/world.py +++ b/world.py @@ -29,6 +29,7 @@ import numpy import chunk import nbt import textures +import time """ This module has routines for extracting information about available worlds @@ -75,14 +76,17 @@ class World(object): #this also caches all the region file header info logging.info("Scanning regions") regionfiles = {} - regions = {} - for x, y, regionfile in self._iterate_regionfiles(): - mcr = nbt.MCRFileReader(regionfile) + self.regions = {} + for x, y, regionfile in self._iterate_regionfiles(): + mcr = self.reload_region(regionfile) mcr.get_chunk_info() - regions[regionfile] = (mcr,os.path.getmtime(regionfile)) regionfiles[(x,y)] = (x,y,regionfile,mcr) self.regionfiles = regionfiles - self.regions = regions + # set the number of region file handles we will permit open at any time before we start closing them +# self.regionlimit = 1000 + # the max number of chunks we will keep before removing them + self.chunklimit = 1024*6 # this should be a multipule of the max chunks per region or things could get wonky ??? + self.chunkcount = 0 logging.debug("Done scanning regions") # figure out chunk format is in use @@ -118,24 +122,54 @@ class World(object): """ _, _, regionfile,_ = self.regionfiles.get((chunkX//32, chunkY//32),(None,None,None,None)); return regionfile - + def load_from_region(self,filename, x, y): - nbt = self.load_region(filename).load_chunk(x, y) - if nbt is None: - return None ## return none. I think this is who we should indicate missing chunks - #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) - return nbt.read_all() - + #we need to manage the chunk cache + regioninfo = self.regions[filename] + if regioninfo is None: + return None + chunks = regioninfo[2] + chunk_data = chunks.get((x,y)) + if chunk_data is None: + nbt = self.load_region(filename).load_chunk(x, y) + if nbt is None: + chunks[(x,y)] = [None,None] + return None ## return none. I think this is who we should indicate missing chunks + #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) + #prune the cache if required + if self.chunkcount > self.chunklimit: #todo: make the emptying the chunk cache slightly less crazy + [self.reload_region(regionfile) for regionfile in self.regions if regionfile <> filename] + self.chunkcount += 1 + + #we cache the transformed data, not it's raw form + data = nbt.read_all() + level = data[1]['Level'] + chunk_data = level + #chunk_data = {} + #chunk_data['skylight'] = chunk.get_skylight_array(level) + #chunk_data['blocklight'] = chunk.get_blocklight_array(level) + #chunk_data['blockarray'] = chunk.get_blockdata_array(level) + #chunk_data['TileEntities'] = chunk.get_tileentity_data(level) + + chunks[(x,y)] = [level,time.time()] + else: + chunk_data = chunk_data[0] + return chunk_data #used to reload a changed region def reload_region(self,filename): - self.regions[filename] = (nbt.MCRFileReader(filename),os.path.getmtime(regionfile)) + if self.regions.get(filename) is not None: + self.regions[filename][0].closefile() + chunkcache = {} + mcr = nbt.MCRFileReader(filename) + self.regions[filename] = (mcr,os.path.getmtime(filename),chunkcache) + return mcr - def load_region(self,filename): + def load_region(self,filename): return self.regions[filename][0] def get_region_mtime(self,filename): - return self.regions[filename] + return (self.regions[filename][0],self.regions[filename][1]) def convert_coords(self, chunkx, chunky): """Takes a coordinate (chunkx, chunky) where chunkx and chunky are From 0465787e6a66720d58f99c943aa7914b19476cd8 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sat, 26 Mar 2011 02:03:13 -0400 Subject: [PATCH 111/213] fixed accidental revert of fenixin's fix --- chunk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chunk.py b/chunk.py index a66d4fb..2849c4b 100644 --- a/chunk.py +++ b/chunk.py @@ -52,7 +52,7 @@ def get_lvldata(world, filename, x, y, retries=2): # non existent region file doesn't mean corrupt chunk. if filename == None: - return None + raise NoSuchChunk try: d = world.load_from_region(filename, x, y) From 0fe9c7c0507dd07b5c00ff9fc05c361bbeb05fb4 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 26 Mar 2011 19:28:32 -0400 Subject: [PATCH 112/213] Fix status line typo --- rendernode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rendernode.py b/rendernode.py index 6cfae8a..a3e12b1 100644 --- a/rendernode.py +++ b/rendernode.py @@ -191,7 +191,7 @@ class RenderNode(object): while count_to_remove > 0: count_to_remove -= 1 complete += results.popleft().get() - self.print_statusline(complete, total, 1) + self.print_statusline(complete, total, level) if len(results) > (10000/batch_size): while len(results) > (500/batch_size): complete += results.popleft().get() @@ -315,4 +315,4 @@ class FakePool(object): pass def join(self): pass - \ No newline at end of file + From afb05098f9559c8cc23a3bc2aa070cc5b1cb7b5f Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Sun, 27 Mar 2011 03:51:45 +0200 Subject: [PATCH 113/213] Add support for redstone and improve the looking of waterfalls. Changes in textures.py and iterate.c --- src/iterate.c | 82 ++++++++++++++++++---- textures.py | 185 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 252 insertions(+), 15 deletions(-) diff --git a/src/iterate.c b/src/iterate.c index 6f2b02b..4febb2a 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -74,7 +74,7 @@ is_transparent(unsigned char b) { unsigned char - check_adjacent_blocks(RenderState *state, unsigned char blockid) { + check_adjacent_blocks(RenderState *state, int x,int y,int z, unsigned char blockid) { /* * Generates a pseudo ancillary data for blocks that depend of * what are surrounded and don't have ancillary data. This @@ -96,48 +96,48 @@ unsigned char if (state->x == 15) { /* +x direction */ if (state->up_right_blocks != Py_None) { /* just in case we are in the end of the world */ - if (getArrayByte3D(state->up_right_blocks, 0, state->y, state->z) == blockid) { + if (getArrayByte3D(state->up_right_blocks, 0, y, z) == blockid) { pdata = pdata|(1 << 3); } } } else { - if (getArrayByte3D(state->blocks, state->x + 1, state->y, state->z) == blockid) { + if (getArrayByte3D(state->blocks, x + 1, y, z) == blockid) { pdata = pdata|(1 << 3); } } if (state->y == 15) { /* +y direction*/ if (state->right_blocks != Py_None) { - if (getArrayByte3D(state->right_blocks, state->x, 0, state->z) == blockid) { + if (getArrayByte3D(state->right_blocks, x, 0, z) == blockid) { pdata = pdata|(1 << 2); } } } else { - if (getArrayByte3D(state->blocks, state->x, state->y + 1, state->z) == blockid) { + if (getArrayByte3D(state->blocks, x, y + 1, z) == blockid) { pdata = pdata|(1 << 2); } } if (state->x == 0) { /* -x direction*/ if (state->left_blocks != Py_None) { - if (getArrayByte3D(state->left_blocks, 15, state->y, state->z) == blockid) { + if (getArrayByte3D(state->left_blocks, 15, y, z) == blockid) { pdata = pdata|(1 << 1); } } } else { - if (getArrayByte3D(state->blocks, state->x - 1, state->y, state->z) == blockid) { + if (getArrayByte3D(state->blocks, x - 1, y, z) == blockid) { pdata = pdata|(1 << 1); } } if (state->y == 0) { /* -y direction */ if (state->up_left_blocks != Py_None) { - if (getArrayByte3D(state->up_left_blocks, state->x, 15, state->z) == blockid) { + if (getArrayByte3D(state->up_left_blocks, x, 15, z) == blockid) { pdata = pdata|(1 << 0); } } } else { - if (getArrayByte3D(state->blocks, state->x, state->y - 1, state->z) == blockid) { + if (getArrayByte3D(state->blocks, x, y - 1, z) == blockid) { pdata = pdata|(1 << 0); } } @@ -147,15 +147,69 @@ unsigned char unsigned char -generate_pseudo_data(RenderState *state) { +generate_pseudo_data(RenderState *state, unsigned char ancilData) { /* * Generates a fake ancillary data for blocks that are drawn * depending on what are surrounded. */ + int x = state->x, y = state->y, z = state->z; + unsigned char data = 0; + + if (state->block == 9) { /* water */ + /* an aditional bit for top is added to the 4 bits of check_adjacent_blocks */ + if ((ancilData == 0) || (ancilData >= 10)) { /* static water, only top, and unkown ancildata values */ + data = 16; + return data; /* = 0b10000 */ + } else if ((ancilData > 0) && (ancilData < 8)) { /* flowing water */ + data = (check_adjacent_blocks(state, x, y, z, state->block) ^ 0b1111) | 0b10000; + return data; + } else if ((ancilData == 8) || (ancilData == 9)) { /* falling water */ + data = (check_adjacent_blocks(state, x, y, z, state->block) ^ 0b1111); + return data; + } - if (state->block == 85) { /* fences */ - return check_adjacent_blocks(state, state->block); + + } else if (state->block == 85) { /* fences */ + return check_adjacent_blocks(state, x, y, z, state->block); + + + } else if (state->block == 55) { /* redstone */ + /* three addiotional bit are added, one for on/off state, and + * another two for going-up redstone wire in the same block + * (connection with the level z+1) */ + unsigned char above_level_data = 0, same_level_data = 0, below_level_data = 0, possibly_connected = 0, final_data = 0; + + /* check for air in z+1, no air = no connection with upper level */ + if ((z != 127) && (getArrayByte3D(state->left_blocks, x, y, z) == 0)) { + above_level_data = check_adjacent_blocks(state, x, y, z + 1, state->block); + } /* else above_level_data = 0 */ + + /* check connection with same level */ + same_level_data = check_adjacent_blocks(state, x, y, z, 55); + + /* check the posibility of connection with z-1 level, check for air */ + possibly_connected = check_adjacent_blocks(state, x, y, z, 0); + + /* check connection with z-1 level */ + if (z != 0) { + below_level_data = check_adjacent_blocks(state, x, y, z - 1, state->block); + } /* else below_level_data = 0 */ + + final_data = above_level_data | same_level_data | (below_level_data & possibly_connected); + + /* add the three bits */ + if (ancilData > 0) { /* powered redstone wire */ + final_data = final_data | 0b1000000; + } + if ((above_level_data & 0b0001) == 1) { /* draw top left going up redstonewire */ + final_data = final_data | 0b0100000; + } + if ((above_level_data & 0b1000) == 8) { /* draw top right going up redstonewire */ + final_data = final_data | 0b0010000; + } + return final_data; } + return 0; } @@ -275,8 +329,8 @@ chunk_render(PyObject *self, PyObject *args) { PyObject *tmp; unsigned char ancilData = getArrayByte3D(blockdata_expanded, state.x, state.y, state.z); - if (state.block == 85) { - ancilData = generate_pseudo_data(&state); + if ((state.block == 85) || (state.block == 9) || (state.block == 55)) { + ancilData = generate_pseudo_data(&state, ancilData); } tmp = PyTuple_New(2); diff --git a/textures.py b/textures.py index 08d70f8..9e4a930 100644 --- a/textures.py +++ b/textures.py @@ -265,6 +265,79 @@ def _build_block(top, side, blockID=None): return img +def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None): + """From a top texture, a bottom texture and 4 different side textures, + 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) + + A non transparent block uses top, side 3 and side 4 + + """ + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + # first back sides + if side1 != None : + side1 = transform_image_side(side1, blockID) + side1 = side1.transpose(Image.FLIP_TOP_BOTTOM) + + # Darken this side. + sidealpha = side1.split()[3] + side1 = ImageEnhance.Brightness(side1).enhance(0.9) + side1.putalpha(sidealpha) + + composite.alpha_over(img, side1, (0,0), side1) + + + if side2 != None : + side2 = transform_image_side(side2, blockID) + side2 = side2.transpose(Image.FLIP_LEFT_RIGHT) + side2 = side2.transpose(Image.FLIP_TOP_BOTTOM) + + # Darken this side. + sidealpha2 = side2.split()[3] + side2 = ImageEnhance.Brightness(side2).enhance(0.8) + side2.putalpha(sidealpha2) + + composite.alpha_over(img, side2, (12,0), side2) + + if bottom != None : + bottom = transform_image(bottom, blockID) + composite.alpha_over(img, bottom, (0,12), top) + + if top != None : + top = transform_image(top, blockID) + composite.alpha_over(img, top, (0,0), top) + + # front sides + if side3 != None : + side3 = transform_image_side(side3, blockID) + + # Darken this side + sidealpha = side3.split()[3] + side3 = ImageEnhance.Brightness(side3).enhance(0.9) + side3.putalpha(sidealpha) + + composite.alpha_over(img, side3, (0,6), side3) + + if side4 != None : + side4 = transform_image_side(side4, blockID) + side4 = side4.transpose(Image.FLIP_LEFT_RIGHT) + + # Darken this side + sidealpha = side4.split()[3] + side4 = ImageEnhance.Brightness(side4).enhance(0.8) + side4.putalpha(sidealpha) + + composite.alpha_over(img, side4, (12,6), side4) + + 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). @@ -801,6 +874,114 @@ def generate_special_texture(blockID, data): img = _build_block(top, side, blockID) return (img.convert("RGB"), img.split()[3]) + + if blockID == 9: # spring water, flowing water and waterfall water + + watertexture = _load_image("water.png") + + if (data & 0b10000) == 16: + top = watertexture + + else: top = None + + if (data & 0b0001) == 1: + side1 = watertexture # top left + else: side1 = None + + if (data & 0b1000) == 8: + side2 = watertexture # top right + else: side2 = None + + if (data & 0b0010) == 2: + side3 = watertexture # bottom left + else: side3 = None + + if (data & 0b0100) == 4: + side4 = watertexture # bottom right + else: side4 = None + + img = _build_full_block(top,side1,side2,side3,side4) + + return (img.convert("RGB"),img.split()[3]) + + + if blockID == 55: # redstone wire + + if data & 0b1000000 == 64: # powered redstone wire + redstone_wire_t = terrain_images[165] + redstone_wire_t = tintTexture(redstone_wire_t,(255,0,0)) + + redstone_cross_t = terrain_images[164] + redstone_cross_t = tintTexture(redstone_cross_t,(255,0,0)) + + + else: # unpowered redstone wire + redstone_wire_t = terrain_images[165] + redstone_wire_t = tintTexture(redstone_wire_t,(48,0,0)) + + redstone_cross_t = terrain_images[164] + redstone_cross_t = tintTexture(redstone_cross_t,(48,0,0)) + + # generate an image per redstone direction + branch_top_left = redstone_cross_t.copy() + ImageDraw.Draw(branch_top_left).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_top_left).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_top_left).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + branch_top_right = redstone_cross_t.copy() + ImageDraw.Draw(branch_top_right).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_top_right).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_top_right).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + branch_bottom_right = redstone_cross_t.copy() + ImageDraw.Draw(branch_bottom_right).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_bottom_right).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_bottom_right).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + branch_bottom_left = redstone_cross_t.copy() + ImageDraw.Draw(branch_bottom_left).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_bottom_left).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_bottom_left).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + # generate the bottom texture + if data & 0b111111 == 0: + bottom = redstone_cross_t.copy() + + elif data & 0b1111 == 10: #= 0b1010 redstone wire in the x direction + bottom = redstone_wire_t.copy() + + elif data & 0b1111 == 5: #= 0b0101 redstone wire in the y direction + bottom = redstone_wire_t.copy().rotate(90) + + else: + bottom = Image.new("RGBA", (16,16), (38,92,255,0)) + if (data & 0b0001) == 1: + composite.alpha_over(bottom,branch_top_left) + + if (data & 0b1000) == 8: + composite.alpha_over(bottom,branch_top_right) + + if (data & 0b0010) == 2: + composite.alpha_over(bottom,branch_bottom_left) + + if (data & 0b0100) == 4: + composite.alpha_over(bottom,branch_bottom_right) + + # check for going up redstone wire + if data & 0b100000 == 32: + side1 = redstone_wire_t.rotate(90) + else: + side1 = None + + if data & 0b010000 == 16: + side2 = redstone_wire_t.rotate(90) + else: + side2 = None + + img = _build_full_block(None,side1,side2,None,None,bottom) + + return (img.convert("RGB"),img.split()[3]) + return None def tintTexture(im, c): @@ -896,7 +1077,7 @@ def getBiomeData(worlddir, chunkX, chunkY): # This set holds block ids that require special pre-computing. These are typically # things that require ancillary data to render properly (i.e. ladder plus orientation) -special_blocks = set([66,59,61,62, 65,64,71,91,86,2,18,85,17,23,35,51,43,44]) +special_blocks = set([66,59,61,62, 65,64,71,91,86,2,18,85,17,23,35,51,43,44,9,55]) # this is a map of special blockIDs to a list of all # possible values for ancillary data that it might have. @@ -917,6 +1098,8 @@ special_map[35] = range(16) # wool, colored and white special_map[51] = range(16) # fire special_map[43] = range(4) # stone, sandstone, wooden and cobblestone double-slab special_map[44] = range(4) # stone, sandstone, wooden and cobblestone slab +special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values. +special_map[55] = range(128) # redstone wire # apparently pumpkins and jack-o-lanterns have ancillary data, but it's unknown # what that data represents. For now, assume that the range for data is 0 to 5 From 1f27ccb9be2954a80590a405379be66496fb733e Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sat, 26 Mar 2011 21:52:16 -0400 Subject: [PATCH 114/213] added overall_alpha parameter to alpha_over, speeding up lighting --- src/composite.c | 83 ++++++++++++++++----------------------- src/overviewer.h | 7 ++-- src/rendermode-lighting.c | 9 +---- 3 files changed, 39 insertions(+), 60 deletions(-) diff --git a/src/composite.c b/src/composite.c index 8b507bc..16a826f 100644 --- a/src/composite.c +++ b/src/composite.c @@ -58,52 +58,20 @@ imaging_python_to_c(PyObject *obj) return image; } -/** - * img should be an Image object, type 'L' or 'RGBA' - * for RGBA images, operates on the alpha only - * factor should be between 0 and 1, inclusive - */ -PyObject *brightness(PyObject *img, float factor) { - Imaging imDest; - - int offset, stride, x, y, xsize, ysize; - - imDest = imaging_python_to_c(img); - if (!imDest) - return NULL; - - if (strcmp(imDest->mode, "RGBA") != 0 && strcmp(imDest->mode, "L") != 0) { - PyErr_SetString(PyExc_ValueError, - "given image does not have mode \"RGBA\" or \"L\""); - return NULL; - } - - /* how far into image the first alpha byte resides */ - offset = (imDest->pixelsize == 4 ? 3 : 0); - /* how many bytes to skip to get to the next alpha byte */ - stride = imDest->pixelsize; - - xsize = imDest->xsize; - ysize = imDest->ysize; - - for (y = 0; y < ysize; y++) { - UINT8 *out = (UINT8 *)imDest->image[y] + offset; - - for (x = 0; x < xsize; x++) { - *out *= factor; - out += stride; - } - } - return NULL; +/* convenience alpha_over with 1.0 as overall_alpha */ +inline PyObject* alpha_over(PyObject *dest, PyObject *src, PyObject *mask, + int dx, int dy, int xsize, int ysize) { + return alpha_over_full(dest, src, mask, 1.0f, dx, dy, xsize, ysize); } -/* the alpha_over function, in a form that can be called from C */ -/* if xsize, ysize are negative, they are instead set to the size of the image in src */ -/* returns NULL on error, dest on success. You do NOT need to decref the return! */ -PyObject * -alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, int dy, - int xsize, int ysize) -{ +/* the full alpha_over function, in a form that can be called from C + * overall_alpha is multiplied with the whole mask, useful for lighting... + * if xsize, ysize are negative, they are instead set to the size of the image in src + * returns NULL on error, dest on success. You do NOT need to decref the return! + */ +inline PyObject * +alpha_over_full(PyObject *dest, PyObject *src, PyObject *mask, float overall_alpha, + int dx, int dy, int xsize, int ysize) { /* libImaging handles */ Imaging imDest, imSrc, imMask; /* cached blend properties */ @@ -114,6 +82,12 @@ alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, int dy, unsigned int x, y, i; /* temporary calculation variables */ int tmp1, tmp2, tmp3; + /* integer [0, 255] version of overall_alpha */ + UINT8 overall_alpha_int = 255 * overall_alpha; + + /* short-circuit this whole thing if overall_alpha is zero */ + if (overall_alpha_int == 0) + return dest; imDest = imaging_python_to_c(dest); imSrc = imaging_python_to_c(src); @@ -202,9 +176,18 @@ alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, int dy, UINT8 *inmask = (UINT8 *)imMask->image[sy + y] + sx * mask_stride + mask_offset; for (x = 0; x < xsize; x++) { + UINT8 in_alpha; + + /* apply overall_alpha */ + if (overall_alpha_int != 255 && *inmask != 0) { + in_alpha = MULDIV255(*inmask, overall_alpha_int, tmp1); + } else { + in_alpha = *inmask; + } + /* special cases */ - if (*inmask == 255 || *outmask == 0) { - *outmask = *inmask; + if (in_alpha == 255 || *outmask == 0) { + *outmask = in_alpha; *out = *in; out++, in++; @@ -213,18 +196,18 @@ alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, int dy, *out = *in; out++, in++; } - else if (*inmask == 0) { + else if (in_alpha == 0) { /* do nothing -- source is fully transparent */ out += 3; in += 3; } else { /* general case */ - int alpha = *inmask + MULDIV255(*outmask, 255 - *inmask, tmp1); + int alpha = in_alpha + MULDIV255(*outmask, 255 - in_alpha, tmp1); for (i = 0; i < 3; i++) { /* general case */ - *out = MULDIV255(*in, *inmask, tmp1) + - MULDIV255(MULDIV255(*out, *outmask, tmp2), 255 - *inmask, tmp3); + *out = MULDIV255(*in, in_alpha, tmp1) + + MULDIV255(MULDIV255(*out, *outmask, tmp2), 255 - in_alpha, tmp3); *out = (*out * 255) / alpha; out++, in++; diff --git a/src/overviewer.h b/src/overviewer.h index b11ba03..6b9db21 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -38,10 +38,11 @@ /* in composite.c */ Imaging imaging_python_to_c(PyObject *obj); -PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, - int dy, int xsize, int ysize); +PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask, + int dx, int dy, int xsize, int ysize); +PyObject *alpha_over_full(PyObject *dest, PyObject *src, PyObject *mask, float overall_alpha, + int dx, int dy, int xsize, int ysize); PyObject *alpha_over_wrap(PyObject *self, PyObject *args); -PyObject *brightness(PyObject *img, float factor); /* in iterate.c */ typedef struct { diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index d83522e..737a11b 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -135,9 +135,8 @@ get_lighting_coefficient(RenderModeLighting *self, RenderState *state, lighting results from (x, y, z) */ static inline void do_shading_with_mask(RenderModeLighting *self, RenderState *state, - int x, int y, int z, PyObject *facemask) { + int x, int y, int z, PyObject *mask) { float black_coeff; - PyObject *mask; /* first, check for occlusion if the block is in the local chunk */ if (x >= 0 && x < 16 && y >= 0 && y < 16 && z >= 0 && z < 128) { @@ -149,11 +148,7 @@ do_shading_with_mask(RenderModeLighting *self, RenderState *state, } black_coeff = get_lighting_coefficient(self, state, x, y, z, NULL); - - mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref - brightness(mask, black_coeff); - alpha_over(state->img, self->black_color, mask, state->imgx, state->imgy, 0, 0); - Py_DECREF(mask); + alpha_over_full(state->img, self->black_color, mask, black_coeff, state->imgx, state->imgy, 0, 0); } static int From fc176206ff92b2d6edfd3146c6a0a8ecb0e29927 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 27 Mar 2011 05:49:57 -0400 Subject: [PATCH 115/213] initial C biome code --- chunk.py | 7 -- src/composite.c | 168 +++++++++++++++++++++++++++++++--------- src/iterate.c | 2 +- src/overviewer.h | 5 +- src/rendermode-normal.c | 125 +++++++++++++++++++++++++++++- src/rendermodes.h | 13 +++- textures.py | 49 ++---------- 7 files changed, 274 insertions(+), 95 deletions(-) diff --git a/chunk.py b/chunk.py index 2849c4b..6a02669 100644 --- a/chunk.py +++ b/chunk.py @@ -437,13 +437,6 @@ class ChunkRenderer(object): tileEntities = get_tileentity_data(self.level) - if self.world.useBiomeData: - biomeColorData = textures.getBiomeData(self.world.worlddir, - self.chunkX, self.chunkY) - # in the 32x32 block of biome data, what chunk is this?l - startX = self.chunkX % 32 - startY = self.chunkY % 32 - # Each block is 24x24 # The next block on the X axis adds 12px to x and subtracts 6px from y in the image # The next block on the Y axis adds 12px to x and adds 6px to y in the image diff --git a/src/composite.c b/src/composite.c index 16a826f..f7186f6 100644 --- a/src/composite.c +++ b/src/composite.c @@ -34,7 +34,7 @@ typedef struct { Imaging image; } ImagingObject; -Imaging +inline Imaging imaging_python_to_c(PyObject *obj) { PyObject *im; @@ -58,6 +58,45 @@ imaging_python_to_c(PyObject *obj) return image; } +/* helper function to setup s{x,y}, d{x,y}, and {x,y}size variables + in these composite functions -- even handles auto-sizing to src! */ +static inline void +setup_source_destination(Imaging src, Imaging dest, + int *sx, int *sy, int *dx, int *dy, int *xsize, int *ysize) +{ + /* handle negative/zero sizes appropriately */ + if (*xsize <= 0 || *ysize <= 0) { + *xsize = src->xsize; + *ysize = src->ysize; + } + + /* set up the source position, size and destination position */ + /* handle negative dest pos */ + if (*dx < 0) { + *sx = -(*dx); + *dx = 0; + } else { + *sx = 0; + } + + if (*dy < 0) { + *sy = -(*dy); + *dy = 0; + } else { + *sy = 0; + } + + /* set up source dimensions */ + *xsize -= *sx; + *ysize -= *sy; + + /* clip dimensions, if needed */ + if (*dx + *xsize > dest->xsize) + *xsize = dest->xsize - *dx; + if (*dy + *ysize > dest->ysize) + *ysize = dest->ysize - *dy; +} + /* convenience alpha_over with 1.0 as overall_alpha */ inline PyObject* alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, int dy, int xsize, int ysize) { @@ -129,39 +168,8 @@ alpha_over_full(PyObject *dest, PyObject *src, PyObject *mask, float overall_alp /* how many bytes to skip to get to the next alpha byte */ mask_stride = imMask->pixelsize; - /* handle negative/zero sizes appropriately */ - if (xsize <= 0 || ysize <= 0) { - xsize = imSrc->xsize; - ysize = imSrc->ysize; - } - - /* set up the source position, size and destination position */ - /* handle negative dest pos */ - if (dx < 0) { - sx = -dx; - dx = 0; - } - else { - sx = 0; - } - - if (dy < 0) { - sy = -dy; - dy = 0; - } - else { - sy = 0; - } - - /* set up source dimensions */ - xsize -= sx; - ysize -= sy; - - /* clip dimensions, if needed */ - if (dx + xsize > imDest->xsize) - xsize = imDest->xsize - dx; - if (dy + ysize > imDest->ysize) - ysize = imDest->ysize - dy; + /* setup source & destination vars */ + setup_source_destination(imSrc, imDest, &sx, &sy, &dx, &dy, &xsize, &ysize); /* check that there remains any blending to be done */ if (xsize <= 0 || ysize <= 0) { @@ -195,13 +203,11 @@ alpha_over_full(PyObject *dest, PyObject *src, PyObject *mask, float overall_alp out++, in++; *out = *in; out++, in++; - } - else if (in_alpha == 0) { + } else if (in_alpha == 0) { /* do nothing -- source is fully transparent */ out += 3; in += 3; - } - else { + } else { /* general case */ int alpha = in_alpha + MULDIV255(*outmask, 255 - in_alpha, tmp1); for (i = 0; i < 3; i++) { @@ -262,3 +268,89 @@ alpha_over_wrap(PyObject *self, PyObject *args) } return ret; } + +/* like alpha_over, but instead of src image it takes a source color + * also, it multiplies instead of doing an over operation + */ +inline PyObject * +tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb, + PyObject *mask, int dx, int dy, int xsize, int ysize) { + /* libImaging handles */ + Imaging imDest, imMask; + /* cached blend properties */ + int mask_offset, mask_stride; + /* source position */ + int sx, sy; + /* iteration variables */ + unsigned int x, y; + /* temporary calculation variables */ + int tmp1, tmp2; + + imDest = imaging_python_to_c(dest); + imMask = imaging_python_to_c(mask); + + if (!imDest || !imMask) + return NULL; + + /* check the various image modes, make sure they make sense */ + if (strcmp(imDest->mode, "RGBA") != 0) { + PyErr_SetString(PyExc_ValueError, + "given destination image does not have mode \"RGBA\""); + return NULL; + } + + if (strcmp(imMask->mode, "RGBA") != 0 && strcmp(imMask->mode, "L") != 0) { + PyErr_SetString(PyExc_ValueError, + "given mask image does not have mode \"RGBA\" or \"L\""); + return NULL; + } + + /* how far into image the first alpha byte resides */ + mask_offset = (imMask->pixelsize == 4 ? 3 : 0); + /* how many bytes to skip to get to the next alpha byte */ + mask_stride = imMask->pixelsize; + + /* setup source & destination vars */ + setup_source_destination(imMask, imDest, &sx, &sy, &dx, &dy, &xsize, &ysize); + + /* check that there remains any blending to be done */ + if (xsize <= 0 || ysize <= 0) { + /* nothing to do, return */ + return dest; + } + + for (y = 0; y < ysize; y++) { + UINT8 *out = (UINT8 *)imDest->image[dy + y] + dx * 4; + UINT8 *inmask = (UINT8 *)imMask->image[sy + y] + sx * mask_stride + mask_offset; + + for (x = 0; x < xsize; x++) { + /* special cases */ + if (*inmask == 255) { + *out = MULDIV255(*out, sr, tmp1); + out++; + *out = MULDIV255(*out, sg, tmp1); + out++; + *out = MULDIV255(*out, sb, tmp1); + out++; + } else if (*inmask == 0) { + /* do nothing -- source is fully transparent */ + out += 3; + } else { + /* general case */ + + /* TODO work out general case */ + *out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sr, *inmask, tmp1), tmp2); + out++; + *out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sg, *inmask, tmp1), tmp2); + out++; + *out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sb, *inmask, tmp1), tmp2); + out++; + } + + out++; + inmask += mask_stride; + } + } + + return dest; +} diff --git a/src/iterate.c b/src/iterate.c index 8fab11b..71bd7e3 100644 --- a/src/iterate.c +++ b/src/iterate.c @@ -247,7 +247,7 @@ chunk_render(PyObject *self, PyObject *args) { /* set up the render mode */ rendermode = get_render_mode(&state); - rm_data = malloc(rendermode->data_size); + rm_data = calloc(1, rendermode->data_size); if (rendermode->start(rm_data, &state)) { free(rm_data); return Py_BuildValue("i", "-1"); diff --git a/src/overviewer.h b/src/overviewer.h index 6b9db21..ebf83ce 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -29,8 +29,9 @@ #include #include -/* macro for getting a value out of a 3D numpy byte array */ +/* macro for getting a value out of various numpy arrays */ #define getArrayByte3D(array, x,y,z) (*(unsigned char *)(PyArray_GETPTR3((array), (x), (y), (z)))) +#define getArrayShort1D(array, x) (*(unsigned short *)(PyArray_GETPTR1((array), (x)))) /* generally useful MAX / MIN macros */ #define MAX(a, b) ((a) > (b) ? (a) : (b)) @@ -43,6 +44,8 @@ PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask, PyObject *alpha_over_full(PyObject *dest, PyObject *src, PyObject *mask, float overall_alpha, int dx, int dy, int xsize, int ysize); PyObject *alpha_over_wrap(PyObject *self, PyObject *args); +PyObject *tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb, + PyObject *mask, int dx, int dy, int xsize, int ysize); /* in iterate.c */ typedef struct { diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c index ca1b9c5..85e55ed 100644 --- a/src/rendermode-normal.c +++ b/src/rendermode-normal.c @@ -19,13 +19,75 @@ static int rendermode_normal_start(void *data, RenderState *state) { - /* do nothing */ + PyObject *chunk_x_py, *chunk_y_py, *world, *use_biomes, *worlddir; + RenderModeNormal *self = (RenderModeNormal *)data; + + chunk_x_py = PyObject_GetAttrString(state->self, "chunkX"); + chunk_y_py = PyObject_GetAttrString(state->self, "chunkY"); + + /* careful now -- C's % operator works differently from python's + we can't just do x % 32 like we did before */ + self->chunk_x = PyInt_AsLong(chunk_x_py); + self->chunk_y = PyInt_AsLong(chunk_y_py); + + while (self->chunk_x < 0) + self->chunk_x += 32; + while (self->chunk_y < 0) + self->chunk_y += 32; + + self->chunk_x %= 32; + self->chunk_y %= 32; + + /* fetch the biome data from textures.py, if needed */ + world = PyObject_GetAttrString(state->self, "world"); + worlddir = PyObject_GetAttrString(world, "worlddir"); + use_biomes = PyObject_GetAttrString(world, "useBiomeData"); + Py_DECREF(world); + + if (PyObject_IsTrue(use_biomes)) { + PyObject *facemasks_py; + + self->biome_data = PyObject_CallMethod(state->textures, "getBiomeData", "OOO", + worlddir, chunk_x_py, chunk_y_py); + self->foliagecolor = PyObject_GetAttrString(state->textures, "foliagecolor"); + self->grasscolor = PyObject_GetAttrString(state->textures, "grasscolor"); + + self->leaf_texture = PyObject_GetAttrString(state->textures, "biome_leaf_texture"); + self->grass_texture = PyObject_GetAttrString(state->textures, "biome_grass_texture"); + + facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks"); + /* borrowed reference, needs to be incref'd if we keep it */ + self->facemask_top = PyTuple_GetItem(facemasks_py, 0); + Py_INCREF(self->facemask_top); + Py_DECREF(facemasks_py); + } else { + self->biome_data = NULL; + self->foliagecolor = NULL; + self->grasscolor = NULL; + + self->leaf_texture = NULL; + self->grass_texture = NULL; + self->facemask_top = NULL; + } + + Py_DECREF(use_biomes); + Py_DECREF(worlddir); + Py_DECREF(chunk_x_py); + Py_DECREF(chunk_y_py); + return 0; } static void rendermode_normal_finish(void *data, RenderState *state) { - /* do nothing */ + RenderModeNormal *self = (RenderModeNormal *)data; + + Py_XDECREF(self->biome_data); + Py_XDECREF(self->foliagecolor); + Py_XDECREF(self->grasscolor); + Py_XDECREF(self->leaf_texture); + Py_XDECREF(self->grass_texture); + Py_XDECREF(self->facemask_top); } static int @@ -44,7 +106,66 @@ rendermode_normal_occluded(void *data, RenderState *state) { static void rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { + RenderModeNormal *self = (RenderModeNormal *)data; + + /* first, check to see if we should use biome-compatible src, mask */ + if (self->biome_data) { + switch (state->block) { + case 2: + src = mask = self->grass_texture; + break; + case 18: + src = mask = self->leaf_texture; + break; + default: + break; + }; + } + + /* draw the block! */ alpha_over(state->img, src, mask, state->imgx, state->imgy, 0, 0); + + if (self->biome_data) { + /* do the biome stuff! */ + unsigned int index; + PyObject *index_py, *color = NULL, *facemask = NULL; + unsigned char r, g, b; + + index = ((self->chunk_y * 16) + state->y) * 16 * 32 + (self->chunk_x * 16) + state->x; + /* TODO for some reason, this one doesn't work: + * index = getArrayShort1D(self->biome_data, index); + */ + index_py = PySequence_GetItem(self->biome_data, index); + index = PyInt_AsLong(index_py); + Py_DECREF(index_py); + + switch (state->block) { + case 2: + /* grass */ + color = PySequence_GetItem(self->grasscolor, index); + facemask = self->facemask_top; + break; + case 18: + /* leaves */ + color = PySequence_GetItem(self->foliagecolor, index); + facemask = mask; + break; + default: + break; + }; + + if (color) + { + /* we've got work to do */ + + r = PyInt_AsLong(PyTuple_GET_ITEM(color, 0)); + g = PyInt_AsLong(PyTuple_GET_ITEM(color, 1)); + b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2)); + Py_DECREF(color); + + tint_with_mask(state->img, r, g, b, facemask, state->imgx, state->imgy, 0, 0); + } + } } RenderModeInterface rendermode_normal = { diff --git a/src/rendermodes.h b/src/rendermodes.h index 5ebd18b..d26091d 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -58,9 +58,16 @@ RenderModeInterface *get_render_mode(RenderState *state); /* NORMAL */ typedef struct { - /* normal mode does not have any special data, so just use a dummy int - this way, normal mode is just like any other type of render mode */ - int dummy; + /* coordinates of the chunk, inside its region file */ + int chunk_x, chunk_y; + /* biome data for the region */ + PyObject *biome_data; + /* grasscolor and foliagecolor lookup tables */ + PyObject *grasscolor, *foliagecolor; + /* biome-compatible grass/leaf textures */ + PyObject *grass_texture, *leaf_texture; + /* top facemask for grass biome tinting */ + PyObject *facemask_top; } RenderModeNormal; extern RenderModeInterface rendermode_normal; diff --git a/textures.py b/textures.py index 9e4a930..d0900a1 100644 --- a/textures.py +++ b/textures.py @@ -633,15 +633,8 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"), img.split()[3]) if blockID == 2: # grass - top = transform_image(tintTexture(terrain_images[0],(115,175,71))) - side1 = transform_image_side(terrain_images[3]) - side2 = transform_image_side(terrain_images[3]).transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - composite.alpha_over(img, side1, (0,6), side1) - composite.alpha_over(img, side2, (12,6), side2) - composite.alpha_over(img, top, (0,0), top) + top = tintTexture(terrain_images[0],(115,175,71)) + img = _build_block(top, terrain_images[3], 2) return (img.convert("RGB"), img.split()[3]) if blockID == 51: # fire @@ -661,15 +654,7 @@ def generate_special_texture(blockID, data): if blockID == 18: # leaves t = tintTexture(terrain_images[52], (37, 118, 25)) - top = transform_image(t) - side1 = transform_image_side(t) - side2 = transform_image_side(t).transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - composite.alpha_over(img, side1, (0,6), side1) - composite.alpha_over(img, side2, (12,6), side2) - composite.alpha_over(img, top, (0,0), top) + img = _build_block(t, t, 18) return (img.convert("RGB"), img.split()[3]) if blockID == 17: # wood: normal, birch and pines @@ -990,31 +975,9 @@ def tintTexture(im, c): i.putalpha(im.split()[3]); # copy the alpha band back in. assuming RGBA return i -grassSide1 = transform_image_side(terrain_images[3]) -grassSide2 = transform_image_side(terrain_images[3]).transpose(Image.FLIP_LEFT_RIGHT) -def prepareGrassTexture(color): - top = transform_image(tintTexture(terrain_images[0],color)) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - img.paste(grassSide1, (0,6), grassSide1) - img.paste(grassSide2, (12,6), grassSide2) - img.paste(top, (0,0), top) - return (img.convert("RGB"), img.split()[3]) - - -def prepareLeafTexture(color): - t = tintTexture(terrain_images[52], color) - top = transform_image(t) - side1 = transform_image_side(t) - side2 = transform_image_side(t).transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - img.paste(side1, (0,6), side1) - img.paste(side2, (12,6), side2) - img.paste(top, (0,0), top) - return (img.convert("RGB"), img.split()[3]) - +# generate biome (still grayscale) leaf, grass textures +biome_grass_texture = _build_block(terrain_images[0], terrain_images[3], 2) +biome_leaf_texture = _build_block(terrain_images[52], terrain_images[52], 18) currentBiomeFile = None From e0e571971a5fc2bda90053377ee4b6ce9a672c47 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 27 Mar 2011 06:07:43 -0400 Subject: [PATCH 116/213] added endianness helper functions, and reading biome data directly --- setup.py | 2 +- src/endian.c | 39 +++++++++++++++++++++++++++++++++++++++ src/main.c | 2 ++ src/overviewer.h | 5 +++++ src/rendermode-normal.c | 9 ++------- 5 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 src/endian.c diff --git a/setup.py b/setup.py index 9d2f7aa..85ade1e 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ except AttributeError: numpy_include = numpy.get_numpy_include() -c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c'] +c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/endian.c'] c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c', 'src/rendermode-night.c', 'src/rendermode-spawn.c'] setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], extra_link_args=[])) # tell build_ext to build the extension in-place diff --git a/src/endian.c b/src/endian.c new file mode 100644 index 0000000..5b0a105 --- /dev/null +++ b/src/endian.c @@ -0,0 +1,39 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + +/* simple routines for dealing with endian conversion */ + +#define UNKNOWN_ENDIAN 0 +#define BIG_ENDIAN 1 +#define LITTLE_ENDIAN 2 + +static int endianness = UNKNOWN_ENDIAN; + +void init_endian() { + /* figure out what our endianness is! */ + short word = 0x0001; + char* byte = (char*)(&word); + endianness = byte[0] ? LITTLE_ENDIAN : BIG_ENDIAN; +} + +unsigned short big_endian_ushort(unsigned short in) { + return (endianness == LITTLE_ENDIAN) ? ((in >> 8) | (in << 8)) : in; +} + +unsigned int big_endian_uint(unsigned int in) { + return (endianness == LITTLE_ENDIAN) ? (((in & 0x000000FF) << 24) + ((in & 0x0000FF00) << 8) + ((in & 0x00FF0000) >> 8) + ((in & 0xFF000000) >> 24)) : in; +} diff --git a/src/main.c b/src/main.c index 32ca491..a8c6075 100644 --- a/src/main.c +++ b/src/main.c @@ -37,4 +37,6 @@ initc_overviewer(void) fprintf(stderr, "failed to init_chunk_render\n"); exit(1); // TODO better way to indicate error? } + + init_endian(); } diff --git a/src/overviewer.h b/src/overviewer.h index ebf83ce..3475fdf 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -78,4 +78,9 @@ PyObject *chunk_render(PyObject *self, PyObject *args); /* pull in the rendermode info */ #include "rendermodes.h" +/* in endian.c */ +void init_endian(); +unsigned short big_endian_ushort(unsigned short in); +unsigned int big_endian_uint(unsigned int in); + #endif /* __OVERVIEWER_H_INCLUDED__ */ diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c index 85e55ed..d7a1367 100644 --- a/src/rendermode-normal.c +++ b/src/rendermode-normal.c @@ -128,16 +128,11 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * if (self->biome_data) { /* do the biome stuff! */ unsigned int index; - PyObject *index_py, *color = NULL, *facemask = NULL; + PyObject *color = NULL, *facemask = NULL; unsigned char r, g, b; index = ((self->chunk_y * 16) + state->y) * 16 * 32 + (self->chunk_x * 16) + state->x; - /* TODO for some reason, this one doesn't work: - * index = getArrayShort1D(self->biome_data, index); - */ - index_py = PySequence_GetItem(self->biome_data, index); - index = PyInt_AsLong(index_py); - Py_DECREF(index_py); + index = big_endian_ushort(getArrayShort1D(self->biome_data, index)); switch (state->block) { case 2: From 8fa8a0949229348ca87762be14183824b9b78368 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Sun, 27 Mar 2011 13:41:41 +0200 Subject: [PATCH 117/213] Add crafting table to the special blocks set. --- textures.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/textures.py b/textures.py index d0900a1..87f4a72 100644 --- a/textures.py +++ b/textures.py @@ -354,7 +354,7 @@ 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, 80, -1, 65, 4, 25, -1, 98, 24, 43, -1, 86, -1, -1, -1, # Torch from above? leaving out fire. Redstone wire? Crops/furnaces handled elsewhere. sign post + 36, 37, 80, -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 # 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 # 80 81 82 83 84 85 86 87 88 89 90 91 @@ -371,7 +371,7 @@ 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, 80, -1, 65, 4, 25,101, 98, 24, 43, -1, 86, -1, -1, -1, + 36, 37, 80, -1, 65, 4, 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, # 80 81 82 83 84 85 86 87 88 89 90 91 @@ -490,7 +490,15 @@ def generate_special_texture(blockID, data): composite.alpha_over(img, track, (0,12), track) return (img.convert("RGB"), img.split()[3]) + + if blockID == 58: # crafting table + top = terrain_images[43] + side3 = terrain_images[43+16] + side4 = terrain_images[43+16+1] + img = _build_full_block(top, None, None, side3, side4, None, 58) + return (img.convert("RGB"), img.split()[3]) + if blockID == 59: # crops raw_crop = terrain_images[88+data] crop1 = transform_image(raw_crop, blockID) @@ -1040,7 +1048,7 @@ def getBiomeData(worlddir, chunkX, chunkY): # This set holds block ids that require special pre-computing. These are typically # things that require ancillary data to render properly (i.e. ladder plus orientation) -special_blocks = set([66,59,61,62, 65,64,71,91,86,2,18,85,17,23,35,51,43,44,9,55]) +special_blocks = set([66,59,61,62, 65,64,71,91,86,2,18,85,17,23,35,51,43,44,9,55,58]) # this is a map of special blockIDs to a list of all # possible values for ancillary data that it might have. @@ -1063,6 +1071,7 @@ 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[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values. special_map[55] = range(128) # redstone wire +special_map[58] = (0,) # crafting table # apparently pumpkins and jack-o-lanterns have ancillary data, but it's unknown # what that data represents. For now, assume that the range for data is 0 to 5 From e33f55977bb3815b8327506c5143c6048cf75f3e Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Sun, 27 Mar 2011 13:58:42 +0200 Subject: [PATCH 118/213] Add redstone torches to textures.py (without orientation). --- textures.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/textures.py b/textures.py index 87f4a72..ec7d034 100644 --- a/textures.py +++ b/textures.py @@ -226,7 +226,9 @@ def _build_block(top, side, blockID=None): ## special case for non-block things # TODO once torches are handled by generate_special_texture, remove # them from this list - if blockID in (37,38,6,39,40,50,83): ## flowers, sapling, mushrooms, regular torch, reeds + if blockID in (37,38,6,39,40,50,83,75,76): ## flowers, sapling, mushrooms, regular torch, reeds, + # redstone torch on, redstone torch off + # # instead of pasting these blocks at the cube edges, place them in the middle: # and omit the top composite.alpha_over(img, side, (6,3), side) @@ -356,7 +358,7 @@ def _build_blockimages(): # 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 36, 37, 80, -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 # 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 + -1, -1, -1, 16, -1, -1, -1, -1, -1, 51, 51,115, 99, -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? ] @@ -373,7 +375,7 @@ def _build_blockimages(): # 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 36, 37, 80, -1, 65, 4, 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, 16, -1, -1, -1, -1, -1, 51, 51,115, 99, -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 ] From 805450e5f4c386d8b520f3abef21efa3a691befa Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Sun, 27 Mar 2011 14:42:21 +0200 Subject: [PATCH 119/213] Add cake to textures.py (cake!) --- chunk.py | 4 ++-- textures.py | 35 ++++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/chunk.py b/chunk.py index 6a02669..99019b6 100644 --- a/chunk.py +++ b/chunk.py @@ -115,12 +115,12 @@ def get_tileentity_data(level): # This set holds blocks ids that can be seen through, for occlusion calculations transparent_blocks = set([0, 6, 8, 9, 18, 20, 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]) + 59, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 81, 83, 85, 92]) # 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, 23, 24, 25, 35, 41, 42, 43, 44, 45, 46, 47, 48, 49, 53, 54, 56, 57, 58, 60, - 61, 62, 64, 65, 66, 67, 71, 73, 74, 78, 79, 80, 81, 82, 84, 86, 87, 88, 89, 91]) + 61, 62, 64, 65, 66, 67, 71, 73, 74, 78, 79, 80, 81, 82, 84, 86, 87, 88, 89, 91, 92]) # This set holds block ids that are fluid blocks fluid_blocks = set([8,9,10,11]) diff --git a/textures.py b/textures.py index ec7d034..565d194 100644 --- a/textures.py +++ b/textures.py @@ -121,8 +121,8 @@ def transform_image(img, blockID=None): """ - if blockID in (81,): # cacti - # Resize to 15x15, since the cactus texture is a little smaller than the other textures + 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.BILINEAR) else: @@ -977,6 +977,34 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"),img.split()[3]) + + if blockID == 92: # cake! (without bites, at the moment) + + top = terrain_images[121] + side = terrain_images[122] + top = transform_image(top, blockID) + side = transform_image_side(side, blockID) + otherside = side.transpose(Image.FLIP_LEFT_RIGHT) + + 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 = 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, (2,6), side) + #~ composite.alpha_over(img, otherside, (10,6), otherside) + #~ composite.alpha_over(img, top, (0,2), top) + return (img.convert("RGB"), img.split()[3]) + + return None def tintTexture(im, c): @@ -1050,7 +1078,7 @@ def getBiomeData(worlddir, chunkX, chunkY): # This set holds block ids that require special pre-computing. These are typically # things that require ancillary data to render properly (i.e. ladder plus orientation) -special_blocks = set([66,59,61,62, 65,64,71,91,86,2,18,85,17,23,35,51,43,44,9,55,58]) +special_blocks = set([66,59,61,62, 65,64,71,91,86,2,18,85,17,23,35,51,43,44,9,55,58,92]) # this is a map of special blockIDs to a list of all # possible values for ancillary data that it might have. @@ -1074,6 +1102,7 @@ special_map[44] = range(4) # stone, sandstone, wooden and cobblestone slab special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values. special_map[55] = range(128) # redstone wire special_map[58] = (0,) # crafting table +special_map[92] = range(6) # cake! # apparently pumpkins and jack-o-lanterns have ancillary data, but it's unknown # what that data represents. For now, assume that the range for data is 0 to 5 From 69179097c54f21e35a3ed01ddf3f40b709c09763 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Sun, 27 Mar 2011 14:46:15 +0200 Subject: [PATCH 120/213] Paste the top texture the last in _build_full_blocks --- textures.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/textures.py b/textures.py index 565d194..fa1ac68 100644 --- a/textures.py +++ b/textures.py @@ -311,10 +311,6 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None bottom = transform_image(bottom, blockID) composite.alpha_over(img, bottom, (0,12), top) - if top != None : - top = transform_image(top, blockID) - composite.alpha_over(img, top, (0,0), top) - # front sides if side3 != None : side3 = transform_image_side(side3, blockID) @@ -337,6 +333,10 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None composite.alpha_over(img, side4, (12,6), side4) + if top != None : + top = transform_image(top, blockID) + composite.alpha_over(img, top, (0,0), top) + return img From 38ce8681fdfc980d507371cb98cb5d692c529e05 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 27 Mar 2011 13:59:13 -0400 Subject: [PATCH 121/213] suppport win64 builds --- setup.py | 6 +++++- src/composite.c | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 85ade1e..34f646b 100644 --- a/setup.py +++ b/setup.py @@ -31,7 +31,11 @@ if py2exe is not None: ('', ['config.js', 'COPYING.txt', 'README.rst']), ('web_assets', glob.glob('web_assets/*'))] setup_kwargs['zipfile'] = None - setup_kwargs['options']['py2exe'] = {'bundle_files' : 1, 'excludes': 'Tkinter'} + if platform.system() == 'Windows' and '64bit' in platform.architecture(): + b = 3 + else: + b = 1 + setup_kwargs['options']['py2exe'] = {'bundle_files' : b, 'excludes': 'Tkinter'} # # c_overviewer extension diff --git a/src/composite.c b/src/composite.c index f7186f6..4a03d23 100644 --- a/src/composite.c +++ b/src/composite.c @@ -272,7 +272,7 @@ alpha_over_wrap(PyObject *self, PyObject *args) /* like alpha_over, but instead of src image it takes a source color * also, it multiplies instead of doing an over operation */ -inline PyObject * +PyObject * tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb, PyObject *mask, int dx, int dy, int xsize, int ysize) { /* libImaging handles */ From 4977ae876756eb0c0f0104b1e28d4e43a883b1e5 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 27 Mar 2011 21:24:25 -0400 Subject: [PATCH 122/213] added exceptions for .DS_Store and Thumbs.db in mirror_dir --- googlemap.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/googlemap.py b/googlemap.py index 3541e28..840d2ac 100644 --- a/googlemap.py +++ b/googlemap.py @@ -35,9 +35,17 @@ def mirror_dir(src, dst, entities=None): if not os.path.exists(dst): os.mkdir(dst) if entities and type(entities) != list: raise Exception("Expected a list, got a %r instead" % type(entities)) - + + # files which are problematic and should not be copied + # usually, generated by the OS + skip_files = ['Thumbs.db', '.DS_Store'] + for entry in os.listdir(src): - if entities and entry not in entities: continue + if entry in skip_files: + continue + if entities and entry not in entities: + continue + if os.path.isdir(os.path.join(src,entry)): mirror_dir(os.path.join(src, entry), os.path.join(dst, entry)) elif os.path.isfile(os.path.join(src,entry)): From 9f69f099adc49dacbaff364dd85510715b830af4 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 28 Mar 2011 02:50:36 -0400 Subject: [PATCH 123/213] initial support for map overlays --- chunk.py | 1 + config.js | 3 +- googlemap.py | 6 +- overviewer.py | 3 +- setup.py | 8 +- src/composite.c | 10 ++- src/overviewer.h | 5 +- src/rendermode-normal.c | 2 +- src/rendermode-overlay.c | 75 ++++++++++++++++++ src/rendermodes.c | 4 + src/rendermodes.h | 7 ++ web_assets/functions.js | 165 ++++++++++++++++++++++++--------------- web_assets/style.css | 6 +- 13 files changed, 216 insertions(+), 79 deletions(-) create mode 100644 src/rendermode-overlay.c diff --git a/chunk.py b/chunk.py index 99019b6..56e58ef 100644 --- a/chunk.py +++ b/chunk.py @@ -503,6 +503,7 @@ def generate_facemasks(): facemasks = generate_facemasks() black_color = Image.new("RGB", (24,24), (0,0,0)) red_color = Image.new("RGB", (24,24), (229,36,38)) +white_color = Image.new("RGB", (24,24), (255,255,255)) # Render 128 different color images for color coded depth blending in cave mode def generate_depthcolors(): diff --git a/config.js b/config.js index 9296853..f52c170 100644 --- a/config.js +++ b/config.js @@ -44,7 +44,8 @@ var mapTypeData=[ {'label': 'Unlit', 'path': 'tiles'}, // {'label': 'Day', 'path': 'lighting/tiles'}, // {'label': 'Night', 'path': 'night/tiles'}, -// {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'} +// {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'}, +// {'label': 'Overlay', 'path': 'overlay/tiles', 'overlay': true} ]; */ diff --git a/googlemap.py b/googlemap.py index 840d2ac..9ae846a 100644 --- a/googlemap.py +++ b/googlemap.py @@ -95,8 +95,12 @@ class MapGen(object): "{imgformat}", str(imgformat)) # create generated map type data, from given quadtrees + # FIXME hook this into render_modes in setup.py, somehow + overlay_types = ['overlay'] maptypedata = map(lambda q: {'label' : q.rendermode.capitalize(), - 'path' : q.tiledir}, self.quadtrees) + 'path' : q.tiledir, + 'overlay' : q.rendermode in overlay_types}, + self.quadtrees) config = config.replace("{maptypedata}", json.dumps(maptypedata)) with open(os.path.join(self.destdir, "config.js"), 'w') as output: diff --git a/overviewer.py b/overviewer.py index b2aa275..7082d0c 100755 --- a/overviewer.py +++ b/overviewer.py @@ -64,7 +64,8 @@ def main(): parser.add_option("-z", "--zoom", dest="zoom", help="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", action="store", type="int", configFileOnly=True) parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true", commandLineOnly=True) parser.add_option("--chunklist", dest="chunklist", help="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.") - parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render type: normal (default), lighting, night, or spawn.", type="choice", choices=["normal", "lighting", "night", "spawn"], required=True, default="normal", listify=True) + # TODO hook this up to render_modes in setup.py + parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render type: normal (default), lighting, night, or spawn.", type="choice", choices=["normal", "lighting", "night", "spawn", "overlay"], required=True, default="normal", listify=True) parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.", configFileOnly=True ) parser.add_option("--optimize-img", dest="optimizeimg", help="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%", configFileOnly=True) parser.add_option("--web-assets-hook", dest="web_assets_hook", help="If provided, run this function after the web assets have been copied, but before actual tile rendering begins. It should accept a QuadtreeGen object as its only argument.", action="store", metavar="SCRIPT", type="function", configFileOnly=True) diff --git a/setup.py b/setup.py index 34f646b..27bb755 100644 --- a/setup.py +++ b/setup.py @@ -49,9 +49,13 @@ try: except AttributeError: numpy_include = numpy.get_numpy_include() +# used to figure out what files to compile +# TODO and, potentially, to check which are available +render_modes = ['normal', 'lighting', 'night', 'spawn', 'overlay'] + +c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/endian.c', 'src/rendermodes.c'] +c_overviewer_files += map(lambda mode: 'src/rendermode-%s.c' % (mode,), render_modes) -c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/endian.c'] -c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c', 'src/rendermode-night.c', 'src/rendermode-spawn.c'] setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], extra_link_args=[])) # tell build_ext to build the extension in-place # (NOT in build/) diff --git a/src/composite.c b/src/composite.c index 4a03d23..432ece8 100644 --- a/src/composite.c +++ b/src/composite.c @@ -273,7 +273,8 @@ alpha_over_wrap(PyObject *self, PyObject *args) * also, it multiplies instead of doing an over operation */ PyObject * -tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb, +tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, + unsigned char sb, unsigned char sa, PyObject *mask, int dx, int dy, int xsize, int ysize) { /* libImaging handles */ Imaging imDest, imMask; @@ -332,9 +333,11 @@ tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char out++; *out = MULDIV255(*out, sb, tmp1); out++; + *out = MULDIV255(*out, sa, tmp1); + out++; } else if (*inmask == 0) { /* do nothing -- source is fully transparent */ - out += 3; + out += 4; } else { /* general case */ @@ -345,9 +348,10 @@ tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char out++; *out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sb, *inmask, tmp1), tmp2); out++; + *out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sa, *inmask, tmp1), tmp2); + out++; } - out++; inmask += mask_stride; } } diff --git a/src/overviewer.h b/src/overviewer.h index 3475fdf..4045313 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -44,7 +44,8 @@ PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask, PyObject *alpha_over_full(PyObject *dest, PyObject *src, PyObject *mask, float overall_alpha, int dx, int dy, int xsize, int ysize); PyObject *alpha_over_wrap(PyObject *self, PyObject *args); -PyObject *tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb, +PyObject *tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, + unsigned char sb, unsigned char sa, PyObject *mask, int dx, int dy, int xsize, int ysize); /* in iterate.c */ @@ -79,7 +80,7 @@ PyObject *chunk_render(PyObject *self, PyObject *args); #include "rendermodes.h" /* in endian.c */ -void init_endian(); +void init_endian(void); unsigned short big_endian_ushort(unsigned short in); unsigned int big_endian_uint(unsigned int in); diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c index d7a1367..f0cdb96 100644 --- a/src/rendermode-normal.c +++ b/src/rendermode-normal.c @@ -158,7 +158,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2)); Py_DECREF(color); - tint_with_mask(state->img, r, g, b, facemask, state->imgx, state->imgy, 0, 0); + tint_with_mask(state->img, r, g, b, 255, facemask, state->imgx, state->imgy, 0, 0); } } } diff --git a/src/rendermode-overlay.c b/src/rendermode-overlay.c new file mode 100644 index 0000000..32cb03a --- /dev/null +++ b/src/rendermode-overlay.c @@ -0,0 +1,75 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + +#include "overviewer.h" + +static int +rendermode_overlay_start(void *data, RenderState *state) { + PyObject *facemasks_py; + RenderModeOverlay *self = (RenderModeOverlay *)data; + + facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks"); + /* borrowed reference, needs to be incref'd if we keep it */ + self->facemask_top = PyTuple_GetItem(facemasks_py, 0); + Py_INCREF(self->facemask_top); + Py_DECREF(facemasks_py); + + self->white_color = PyObject_GetAttrString(state->chunk, "white_color"); + + return 0; +} + +static void +rendermode_overlay_finish(void *data, RenderState *state) { + RenderModeOverlay *self = (RenderModeOverlay *)data; + + Py_XDECREF(self->facemask_top); + Py_XDECREF(self->white_color); +} + +static int +rendermode_overlay_occluded(void *data, RenderState *state) { + int x = state->x, y = state->y, z = state->z; + + if ( (x != 0) && (y != 15) && (z != 127) && + !is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) && + !is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) && + !is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) { + return 1; + } + + return 0; +} + +static void +rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { + RenderModeOverlay *self = (RenderModeOverlay *)data; + + /* clear the draw space -- set alpha to 0 within mask */ + tint_with_mask(state->img, 255, 255, 255, 0, mask, state->imgx, state->imgy, 0, 0); + + /* do the overlay */ + alpha_over_full(state->img, self->white_color, self->facemask_top, 0.5, state->imgx, state->imgy, 0, 0); +} + +RenderModeInterface rendermode_overlay = { + sizeof(RenderModeOverlay), + rendermode_overlay_start, + rendermode_overlay_finish, + rendermode_overlay_occluded, + rendermode_overlay_draw, +}; diff --git a/src/rendermodes.c b/src/rendermodes.c index f4fbe89..46cc432 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -31,6 +31,10 @@ RenderModeInterface *get_render_mode(RenderState *state) { iface = &rendermode_night; } else if (strcmp(rendermode, "spawn") == 0) { iface = &rendermode_spawn; + } else if (strcmp(rendermode, "overlay") == 0) { + /* TODO temporarily use overlay directly, but later on + you want to use overlay-derived modes */ + iface = &rendermode_overlay; } Py_DECREF(rendermode_py); diff --git a/src/rendermodes.h b/src/rendermodes.h index d26091d..87c7cb1 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -71,6 +71,13 @@ typedef struct { } RenderModeNormal; extern RenderModeInterface rendermode_normal; +/* OVERLAY */ +typedef struct { + /* top facemask and white color image, for drawing overlays */ + PyObject *facemask_top, *white_color; +} RenderModeOverlay; +extern RenderModeInterface rendermode_overlay; + /* LIGHTING */ typedef struct { /* inherits from normal render mode */ diff --git a/web_assets/functions.js b/web_assets/functions.js index 174a43a..9b7ac12 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -14,6 +14,61 @@ function prepareSignMarker(marker, item) { } +function createDropDown(title, items) { + var control = document.createElement("DIV"); + control.id = "customControl"; // let's let a style sheet do most of the styling here + + var controlBorder = document.createElement("DIV"); + controlBorder.id="top"; + control.appendChild(controlBorder); + + var controlText = document.createElement("DIV"); + + controlBorder.appendChild(controlText); + + controlText.innerHTML = title; + + var dropdownDiv = document.createElement("DIV"); + + + $(controlText).click(function() { + $(dropdownDiv).toggle(); + + }); + + + dropdownDiv.id="dropDown"; + control.appendChild(dropdownDiv); + dropdownDiv.innerHTML=""; + + map.controls[google.maps.ControlPosition.TOP_RIGHT].push(control); + + for (idx in items) { + var item = items[idx]; + //console.log(item); + label = item.label; + action = item.action; + var d = document.createElement("div"); + var n = document.createElement("input"); + n.type="checkbox"; + + $(n).data("label",label); + jQuery(n).click(function(e) { + var t = $(e.target); + action(idx, label, e.target.checked); + }); + + if (item.checked) { + n.checked = true; + action(idx, label, item.checked); + } + dropdownDiv.appendChild(d); + d.appendChild(n) + var textNode = document.createElement("text"); + textNode.innerHTML = label + "
"; + d.appendChild(textNode); + } +} function drawMapControls() { @@ -42,69 +97,43 @@ function drawMapControls() { if (signGroups.length > 0) { - // signpost display control - // - - var signControl = document.createElement("DIV"); - signControl.id = "signControl"; // let's let a style sheet do most of the styling here - - var controlBorder = document.createElement("DIV"); - controlBorder.id="top"; - signControl.appendChild(controlBorder); - - var controlText = document.createElement("DIV"); - - controlBorder.appendChild(controlText); - - controlText.innerHTML = "Signposts"; - - var dropdownDiv = document.createElement("DIV"); - - - $(controlText).click(function() { - $(dropdownDiv).toggle(); - - }); - - - dropdownDiv.id="dropDown"; - signControl.appendChild(dropdownDiv); - dropdownDiv.innerHTML=""; - - map.controls[google.maps.ControlPosition.TOP_RIGHT].push(signControl); - - - - var hasSignGroup = false; - for (idx in signGroups) { - var item = signGroups[idx]; - //console.log(item); - label = item.label; - hasSignGroup = true; - var d = document.createElement("div"); - var n = document.createElement("input"); - n.type="checkbox"; - - $(n).data("label",label); - jQuery(n).click(function(e) { - var t = $(e.target); - jQuery.each(markerCollection[t.data("label")], function(i,elem) {elem.setVisible(e.target.checked);}); - }); - - - if (item.checked) { - n.checked = true; - jQuery.each(markerCollection[label], function(i,elem) {elem.setVisible(n.checked);}); + // signpost display control + + var items = []; + for (idx in signGroups) { + var item = signGroups[idx]; + items.push({"label": item.label, "checked": item.checked, + "action": function(n, l, checked) { + jQuery.each(markerCollection[l], function(i,elem) {elem.setVisible(checked);}); + }}); } - dropdownDiv.appendChild(d); - d.appendChild(n) - var textNode = document.createElement("text"); - textNode.innerHTML = label + "
"; - d.appendChild(textNode); - + createDropDown("Signposts", items); } - - + + if (overlayMapTypes.length > 0) { + // overlay maps control + + var items = []; + for (idx in overlayMapTypes) { + var overlay = overlayMapTypes[idx]; + items.push({"label": overlay.name, "checked": false, + "action": function(i, l, checked) { + if (checked) { + map.overlayMapTypes.push(overlay); + } else { + var idx_to_delete = -1; + map.overlayMapTypes.forEach(function(e, j) { + if (e == overlay) { + idx_to_delete = j; + } + }); + if (idx_to_delete >= 0) { + map.overlayMapTypes.removeAt(idx_to_delete); + } + } + }}); + } + createDropDown("Overlays", items); } } @@ -303,7 +332,7 @@ function initialize() { for (idx in MCMapType) { map.mapTypes.set('mcmap' + MCMapType[idx].name, MCMapType[idx]); } - + // We can now set the map to use the 'coordinate' map type map.setMapTypeId(mapTypeIdDefault); @@ -412,6 +441,7 @@ var MCMapOptions = new Array; var MCMapType = new Array; var mapTypeIdDefault = null; var mapTypeIds = []; +var overlayMapTypes = []; for (idx in mapTypeData) { var view = mapTypeData[idx]; @@ -427,10 +457,15 @@ for (idx in mapTypeData) { MCMapType[view.label].name = view.label; MCMapType[view.label].alt = "Minecraft " + view.label + " Map"; MCMapType[view.label].projection = new MCMapProjection(); - if (mapTypeIdDefault == null) { - mapTypeIdDefault = 'mcmap' + view.label; + + if (view.overlay) { + overlayMapTypes.push(MCMapType[view.label]); + } else { + if (mapTypeIdDefault == null) { + mapTypeIdDefault = 'mcmap' + view.label; + } + mapTypeIds.push('mcmap' + view.label); } - mapTypeIds.push('mcmap' + view.label); } function CoordMapType() { diff --git a/web_assets/style.css b/web_assets/style.css index e6029dc..32389bf 100644 --- a/web_assets/style.css +++ b/web_assets/style.css @@ -17,13 +17,13 @@ body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; } font-family: monospace; } -#signControl { +#customControl { padding: 5px; height: 15px; font-family: Arial, sans-serif; } -#signControl > div#top { +#customControl > div#top { background-color: #fff; border: 2px solid #000; text-align: center; @@ -33,7 +33,7 @@ body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; } cursor: pointer; } -#signControl > div#dropDown { +#customControl > div#dropDown { border: 1px solid #000; font-size: 12px; background-color: #fff; From d4bd1d713c88e18569a3c1cb83a0f1fd2c7c4069 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 28 Mar 2011 03:09:00 -0400 Subject: [PATCH 124/213] overlay mode now only considers blocks where overlays make sense --- src/rendermode-overlay.c | 41 +++++++++++++++++++++++++++++++++++++--- src/rendermodes.h | 2 ++ 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/src/rendermode-overlay.c b/src/rendermode-overlay.c index 32cb03a..27321ce 100644 --- a/src/rendermode-overlay.c +++ b/src/rendermode-overlay.c @@ -30,6 +30,9 @@ rendermode_overlay_start(void *data, RenderState *state) { self->white_color = PyObject_GetAttrString(state->chunk, "white_color"); + self->solid_blocks = PyObject_GetAttrString(state->chunk, "solid_blocks"); + self->fluid_blocks = PyObject_GetAttrString(state->chunk, "fluid_blocks"); + return 0; } @@ -37,8 +40,10 @@ static void rendermode_overlay_finish(void *data, RenderState *state) { RenderModeOverlay *self = (RenderModeOverlay *)data; - Py_XDECREF(self->facemask_top); - Py_XDECREF(self->white_color); + Py_DECREF(self->facemask_top); + Py_DECREF(self->white_color); + Py_DECREF(self->solid_blocks); + Py_DECREF(self->fluid_blocks); } static int @@ -51,7 +56,7 @@ rendermode_overlay_occluded(void *data, RenderState *state) { !is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) { return 1; } - + return 0; } @@ -61,7 +66,37 @@ rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject /* clear the draw space -- set alpha to 0 within mask */ tint_with_mask(state->img, 255, 255, 255, 0, mask, state->imgx, state->imgy, 0, 0); + + /* skip rendering the overlay if we can't see it */ + if (state->z != 127) { + unsigned char top_block = getArrayByte3D(state->blocks, state->x, state->y, state->z+1); + if (!is_transparent(top_block)) { + return; + } + + /* check to be sure this block is solid/fluid */ + PyObject *top_block_py = PyInt_FromLong(top_block); + if (PySequence_Contains(self->solid_blocks, top_block_py) || + PySequence_Contains(self->fluid_blocks, top_block_py)) { + + /* top block is fluid or solid, skip drawing */ + Py_DECREF(top_block_py); + return; + } + Py_DECREF(top_block_py); + } + /* check to be sure this block is solid/fluid */ + PyObject *block_py = PyInt_FromLong(state->block); + if (!PySequence_Contains(self->solid_blocks, block_py) && + !PySequence_Contains(self->fluid_blocks, block_py)) { + + /* not fluid or solid, skip drawing the overlay */ + Py_DECREF(block_py); + return; + } + Py_DECREF(block_py); + /* do the overlay */ alpha_over_full(state->img, self->white_color, self->facemask_top, 0.5, state->imgx, state->imgy, 0, 0); } diff --git a/src/rendermodes.h b/src/rendermodes.h index 87c7cb1..848c070 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -75,6 +75,8 @@ extern RenderModeInterface rendermode_normal; typedef struct { /* top facemask and white color image, for drawing overlays */ PyObject *facemask_top, *white_color; + /* only show overlay on top of solid or fluid blocks */ + PyObject *solid_blocks, *fluid_blocks; } RenderModeOverlay; extern RenderModeInterface rendermode_overlay; From 83db528d818b2ff02ea02a1cb08251179487b9ba Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 28 Mar 2011 03:20:16 -0400 Subject: [PATCH 125/213] added callback for derived rendermodes to easily provide overlay color --- src/rendermode-overlay.c | 19 ++++++++++++++++++- src/rendermodes.h | 5 +++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/rendermode-overlay.c b/src/rendermode-overlay.c index 27321ce..08e3b30 100644 --- a/src/rendermode-overlay.c +++ b/src/rendermode-overlay.c @@ -17,6 +17,14 @@ #include "overviewer.h" +static void get_color(void *data, RenderState *state, + unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) { + *r = 200; + *g = 200; + *b = 255; + *a = 155; +} + static int rendermode_overlay_start(void *data, RenderState *state) { PyObject *facemasks_py; @@ -33,6 +41,8 @@ rendermode_overlay_start(void *data, RenderState *state) { self->solid_blocks = PyObject_GetAttrString(state->chunk, "solid_blocks"); self->fluid_blocks = PyObject_GetAttrString(state->chunk, "fluid_blocks"); + self->get_color = get_color; + return 0; } @@ -63,6 +73,7 @@ rendermode_overlay_occluded(void *data, RenderState *state) { static void rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { RenderModeOverlay *self = (RenderModeOverlay *)data; + unsigned char r, g, b, a; /* clear the draw space -- set alpha to 0 within mask */ tint_with_mask(state->img, 255, 255, 255, 0, mask, state->imgx, state->imgy, 0, 0); @@ -97,8 +108,14 @@ rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject } Py_DECREF(block_py); + /* get our color info */ + self->get_color(data, state, &r, &g, &b, &a); + /* do the overlay */ - alpha_over_full(state->img, self->white_color, self->facemask_top, 0.5, state->imgx, state->imgy, 0, 0); + if (a > 0) { + alpha_over(state->img, self->white_color, self->facemask_top, state->imgx, state->imgy, 0, 0); + tint_with_mask(state->img, r, g, b, a, self->facemask_top, state->imgx, state->imgy, 0, 0); + } } RenderModeInterface rendermode_overlay = { diff --git a/src/rendermodes.h b/src/rendermodes.h index 848c070..6129980 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -77,6 +77,11 @@ typedef struct { PyObject *facemask_top, *white_color; /* only show overlay on top of solid or fluid blocks */ PyObject *solid_blocks, *fluid_blocks; + /* can be overridden in derived classes to control + overlay alpha and color + last four vars are r, g, b, a out */ + void (*get_color)(void *, RenderState *, + unsigned char *, unsigned char *, unsigned char *, unsigned char *); } RenderModeOverlay; extern RenderModeInterface rendermode_overlay; From f23d3ddac9b5d59da1ad649b3787c816edcef38d Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 28 Mar 2011 03:40:02 -0400 Subject: [PATCH 126/213] converted spawn into a overlay-based rendermode --- chunk.py | 1 - googlemap.py | 2 +- overviewer.py | 2 +- src/rendermode-spawn.c | 117 ++++++++++++++++++----------------------- src/rendermodes.c | 4 -- src/rendermodes.h | 9 ++-- 6 files changed, 58 insertions(+), 77 deletions(-) diff --git a/chunk.py b/chunk.py index 56e58ef..bfe7e24 100644 --- a/chunk.py +++ b/chunk.py @@ -502,7 +502,6 @@ def generate_facemasks(): return (top, left, right) facemasks = generate_facemasks() black_color = Image.new("RGB", (24,24), (0,0,0)) -red_color = Image.new("RGB", (24,24), (229,36,38)) white_color = Image.new("RGB", (24,24), (255,255,255)) # Render 128 different color images for color coded depth blending in cave mode diff --git a/googlemap.py b/googlemap.py index 9ae846a..29e197f 100644 --- a/googlemap.py +++ b/googlemap.py @@ -96,7 +96,7 @@ class MapGen(object): # create generated map type data, from given quadtrees # FIXME hook this into render_modes in setup.py, somehow - overlay_types = ['overlay'] + overlay_types = ['spawn'] maptypedata = map(lambda q: {'label' : q.rendermode.capitalize(), 'path' : q.tiledir, 'overlay' : q.rendermode in overlay_types}, diff --git a/overviewer.py b/overviewer.py index 7082d0c..4ac0e8d 100755 --- a/overviewer.py +++ b/overviewer.py @@ -65,7 +65,7 @@ def main(): parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true", commandLineOnly=True) parser.add_option("--chunklist", dest="chunklist", help="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.") # TODO hook this up to render_modes in setup.py - parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render type: normal (default), lighting, night, or spawn.", type="choice", choices=["normal", "lighting", "night", "spawn", "overlay"], required=True, default="normal", listify=True) + parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render type: normal (default), lighting, night, or spawn.", type="choice", choices=["normal", "lighting", "night", "spawn"], required=True, default="normal", listify=True) parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.", configFileOnly=True ) parser.add_option("--optimize-img", dest="optimizeimg", help="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%", configFileOnly=True) parser.add_option("--web-assets-hook", dest="web_assets_hook", help="If provided, run this function after the web assets have been copied, but before actual tile rendering begins. It should accept a QuadtreeGen object as its only argument.", action="store", metavar="SCRIPT", type="function", configFileOnly=True) diff --git a/src/rendermode-spawn.c b/src/rendermode-spawn.c index 1a7c96a..b0b78a4 100644 --- a/src/rendermode-spawn.c +++ b/src/rendermode-spawn.c @@ -18,21 +18,63 @@ #include "overviewer.h" #include +static void get_color(void *data, RenderState *state, + unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) { + + RenderModeSpawn* self = (RenderModeSpawn *)data; + int x = state->x, y = state->y, z = state->z; + unsigned char blocklight, skylight; + PyObject *block_py; + + /* set a nice, pretty red color */ + *r = 229; + *g = 36; + *b = 38; + + /* default to no overlay, until told otherwise */ + *a = 0; + + /* if we're at the top, skip */ + if (z == 127) + return; + + block_py = PyInt_FromLong(state->block); + if (PySequence_Contains(self->nospawn_blocks, block_py)) { + /* nothing can spawn on this */ + Py_DECREF(block_py); + return; + } + Py_DECREF(block_py); + + blocklight = getArrayByte3D(self->blocklight, x, y, z+1); + skylight = getArrayByte3D(self->skylight, x, y, z+1); + + if (MAX(blocklight, skylight) <= 7) { + /* hostile mobs spawn in daylight */ + *a = 240; + } else if (MAX(blocklight, skylight - 11) <= 7) { + /* hostile mobs spawn at night */ + *a = 150; + } +} + static int rendermode_spawn_start(void *data, RenderState *state) { RenderModeSpawn* self; /* first, chain up */ - int ret = rendermode_night.start(data, state); + int ret = rendermode_overlay.start(data, state); if (ret != 0) return ret; /* now do custom initializations */ self = (RenderModeSpawn *)data; - self->solid_blocks = PyObject_GetAttrString(state->chunk, "solid_blocks"); self->nospawn_blocks = PyObject_GetAttrString(state->chunk, "nospawn_blocks"); - self->fluid_blocks = PyObject_GetAttrString(state->chunk, "fluid_blocks"); - self->red_color = PyObject_GetAttrString(state->chunk, "red_color"); + self->blocklight = PyObject_GetAttrString(state->self, "blocklight"); + self->skylight = PyObject_GetAttrString(state->self, "skylight"); + + /* setup custom color */ + self->parent.get_color = get_color; return 0; } @@ -40,81 +82,26 @@ rendermode_spawn_start(void *data, RenderState *state) { static void rendermode_spawn_finish(void *data, RenderState *state) { /* first free all *our* stuff */ - RenderModeSpawn* self = (RenderModeSpawn *)data; + RenderModeSpawn* self = (RenderModeSpawn *)data; - Py_DECREF(self->solid_blocks); Py_DECREF(self->nospawn_blocks); - Py_DECREF(self->fluid_blocks); + Py_DECREF(self->blocklight); + Py_DECREF(self->skylight); /* now, chain up */ - rendermode_night.finish(data, state); + rendermode_overlay.finish(data, state); } static int rendermode_spawn_occluded(void *data, RenderState *state) { /* no special occlusion here */ - return rendermode_night.occluded(data, state); + return rendermode_overlay.occluded(data, state); } static void rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { - /* different versions of self (spawn, lighting) */ - RenderModeSpawn* self = (RenderModeSpawn *)data; - RenderModeLighting *lighting = (RenderModeLighting *)self; - - int x = state->x, y = state->y, z = state->z; - PyObject *old_black_color = NULL; - - /* figure out the appropriate darkness: - this block for transparents, the block above for non-transparent */ - float darkness = 0.0; - if (is_transparent(state->block)) { - darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z, NULL); - } else { - darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z+1, NULL); - } - - /* if it's dark enough... */ - if (darkness > 0.8) { - PyObject *block_py = PyInt_FromLong(state->block); - - /* make sure it's solid */ - if (PySequence_Contains(self->solid_blocks, block_py)) { - int spawnable = 1; - - /* not spawnable if its in the nospawn list */ - if (PySequence_Contains(self->nospawn_blocks, block_py)) - spawnable = 0; - - /* check the block above for solid or fluid */ - if (spawnable && z != 127) { - PyObject *top_block_py = PyInt_FromLong(getArrayByte3D(state->blocks, x, y, z+1)); - if (PySequence_Contains(self->solid_blocks, top_block_py) || - PySequence_Contains(self->fluid_blocks, top_block_py)) { - - spawnable = 0; - } - - Py_DECREF(top_block_py); - } - - /* if we passed all the checks, replace black_color with red_color */ - if (spawnable) { - old_black_color = lighting->black_color; - lighting->black_color = self->red_color; - } - } - - Py_DECREF(block_py); - } - /* draw normally */ - rendermode_night.draw(data, state, src, mask); - - /* reset black_color, if needed */ - if (old_black_color != NULL) { - lighting->black_color = old_black_color; - } + rendermode_overlay.draw(data, state, src, mask); } RenderModeInterface rendermode_spawn = { diff --git a/src/rendermodes.c b/src/rendermodes.c index 46cc432..f4fbe89 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -31,10 +31,6 @@ RenderModeInterface *get_render_mode(RenderState *state) { iface = &rendermode_night; } else if (strcmp(rendermode, "spawn") == 0) { iface = &rendermode_spawn; - } else if (strcmp(rendermode, "overlay") == 0) { - /* TODO temporarily use overlay directly, but later on - you want to use overlay-derived modes */ - iface = &rendermode_overlay; } Py_DECREF(rendermode_py); diff --git a/src/rendermodes.h b/src/rendermodes.h index 6129980..a0d18a4 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -115,13 +115,12 @@ extern RenderModeInterface rendermode_night; /* SPAWN */ typedef struct { - /* inherits from night */ - RenderModeNight parent; + /* inherits from overlay */ + RenderModeOverlay parent; /* used to figure out which blocks are spawnable */ - PyObject *solid_blocks, *nospawn_blocks, *fluid_blocks; - /* replacement for black_color */ - PyObject *red_color; + PyObject *nospawn_blocks; + PyObject *skylight, *blocklight; } RenderModeSpawn; extern RenderModeInterface rendermode_spawn; From b24ae6c00ab185cd645bbc9b5310d4873d988efa Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 28 Mar 2011 05:18:41 -0400 Subject: [PATCH 127/213] signposts controls no longer toggle overlays --- web_assets/functions.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/web_assets/functions.js b/web_assets/functions.js index 9b7ac12..87e538f 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -46,26 +46,23 @@ function createDropDown(title, items) { for (idx in items) { var item = items[idx]; //console.log(item); - label = item.label; - action = item.action; var d = document.createElement("div"); var n = document.createElement("input"); n.type="checkbox"; - $(n).data("label",label); + $(n).data("label",item.label); jQuery(n).click(function(e) { - var t = $(e.target); - action(idx, label, e.target.checked); + item.action(idx, item.label, e.target.checked); }); if (item.checked) { n.checked = true; - action(idx, label, item.checked); + item.action(idx, item.label, item.checked); } dropdownDiv.appendChild(d); d.appendChild(n) var textNode = document.createElement("text"); - textNode.innerHTML = label + "
"; + textNode.innerHTML = item.label + "
"; d.appendChild(textNode); } } From d2252acfe6d4a4a24a253fffb98df796dd7ec2f5 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 27 Mar 2011 11:42:11 -0400 Subject: [PATCH 128/213] Signs should be working again --- googlemap.py | 7 +++++-- overviewer.py | 4 +++- quadtree.py | 4 ++-- rendernode.py | 42 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/googlemap.py b/googlemap.py index 840d2ac..65fd8e8 100644 --- a/googlemap.py +++ b/googlemap.py @@ -127,6 +127,11 @@ class MapGen(object): self.web_assets_hook(self) return + + def finalize(self): + if self.skipjs: + return + # since we will only discover PointsOfInterest in chunks that need to be # [re]rendered, POIs like signs in unchanged chunks will not be listed # in self.world.POI. To make sure we don't remove these from markers.js @@ -153,5 +158,3 @@ class MapGen(object): output.write(' // ]},\n') output.write('];') - if self.web_assets_hook: - self.web_assets_hook(self) diff --git a/overviewer.py b/overviewer.py index b2aa275..a161f22 100755 --- a/overviewer.py +++ b/overviewer.py @@ -186,7 +186,7 @@ def main(): q.append(qtree) #create the distributed render - r = rendernode.RenderNode(q) + r = rendernode.RenderNode(q, world=w) # write out the map and web assets m = googlemap.MapGen(q, skipjs=options.skipjs, web_assets_hook=options.web_assets_hook) @@ -195,6 +195,8 @@ def main(): # render the tiles! r.go(options.procs) + m.finalize() + def delete_all(worlddir, tiledir): # TODO should we delete tiledir here too? diff --git a/quadtree.py b/quadtree.py index 77fac8b..b64333a 100644 --- a/quadtree.py +++ b/quadtree.py @@ -333,7 +333,7 @@ class QuadtreeGen(object): - def render_worldtile(self, chunks, colstart, colend, rowstart, rowend, path): + def render_worldtile(self, chunks, colstart, colend, rowstart, rowend, path, poi_queue=None): """Renders just the specified chunks into a tile and save it. Unlike usual python conventions, rowend and colend are inclusive. Additionally, the chunks around the edges are half-way cut off (so that neighboring tiles @@ -442,7 +442,7 @@ class QuadtreeGen(object): # draw the chunk! # TODO POI queue - chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), self, False, None) + chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), self, False, poi_queue) # Save them tileimg.save(imgpath) diff --git a/rendernode.py b/rendernode.py index a3e12b1..43b175d 100644 --- a/rendernode.py +++ b/rendernode.py @@ -14,6 +14,7 @@ # with the Overviewer. If not, see . import multiprocessing +import Queue import itertools from itertools import cycle, islice import os @@ -76,7 +77,7 @@ def roundrobin(iterables): class RenderNode(object): - def __init__(self, quadtrees): + def __init__(self, quadtrees, world): """Distributes the rendering of a list of quadtrees.""" if not len(quadtrees) > 0: @@ -89,6 +90,13 @@ class RenderNode(object): q._render_index = i i += 1 + # queue for receiving interesting events from the renderer + # (like the discovery of signs! + self.poi_q = multiprocessing.Queue() + + self.world = world + + def print_statusline(self, complete, total, level, unconditional=False): if unconditional: pass @@ -151,6 +159,18 @@ class RenderNode(object): timestamp = timestamp2 count_to_remove = (1000//batch_size) if count_to_remove < len(results): + try: + while (1): + # an exception will break us out of this loop + item = self.poi_q.get(block=False) + if item[0] == "newpoi": + if item[1] not in self.world.POI: + #print "got an item from the queue!" + self.world.POI.append(item[1]) + elif item[0] == "removePOI": + self.world.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], self.world.persistentData['POI']) + except Queue.Empty: + pass while count_to_remove > 0: count_to_remove -= 1 complete += results.popleft().get() @@ -166,6 +186,18 @@ class RenderNode(object): while len(results) > 0: complete += results.popleft().get() self.print_statusline(complete, total, 1) + try: + while (1): + # an exception will break us out of this loop + item = self.poi_q.get(block=False) + if item[0] == "newpoi": + if item[1] not in self.world.POI: + #print "got an item from the queue!" + self.world.POI.append(item[1]) + elif item[0] == "removePOI": + self.world.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], self.world.persistentData['POI']) + except Queue.Empty: + pass self.print_statusline(complete, total, 1, True) @@ -230,10 +262,10 @@ class RenderNode(object): jobcount += 1 if jobcount >= batch_size: jobcount = 0 - yield pool.apply_async(func=render_worldtile_batch, args= [batch]) + yield pool.apply_async(func=render_worldtile_batch, args= [batch, self.poi_q]) batch = [] if jobcount > 0: - yield pool.apply_async(func=render_worldtile_batch, args= [batch]) + yield pool.apply_async(func=render_worldtile_batch, args= [batch, self.poi_q]) def _apply_render_inntertile(self, pool, zoom,batch_size): """Same as _apply_render_worltiles but for the inntertile routine. @@ -262,7 +294,7 @@ class RenderNode(object): yield pool.apply_async(func=render_innertile_batch, args= [batch]) @catch_keyboardinterrupt -def render_worldtile_batch(batch): +def render_worldtile_batch(batch, poi_queue): global child_rendernode rendernode = child_rendernode count = 0 @@ -282,7 +314,7 @@ def render_worldtile_batch(batch): tilechunks = quadtree.get_chunks_in_range(colstart, colend, rowstart,rowend) #logging.debug(" tilechunks: %r", tilechunks) - quadtree.render_worldtile(tilechunks,colstart, colend, rowstart, rowend, path) + quadtree.render_worldtile(tilechunks,colstart, colend, rowstart, rowend, path, poi_queue) return count @catch_keyboardinterrupt From d5477275567bcf7d3f4b7c867ce3e3d06e2a94ef Mon Sep 17 00:00:00 2001 From: Xon Date: Mon, 28 Mar 2011 12:48:18 +0800 Subject: [PATCH 129/213] Inline render_to_image since it was just creating a ChunkRender object can calling it. Moved biome init code out of chunk.py and into rendernode.py for per-worker initialization --- chunk.py | 12 +++--------- quadtree.py | 8 ++++++-- rendernode.py | 11 ++++++++--- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/chunk.py b/chunk.py index 99019b6..38c4e7e 100644 --- a/chunk.py +++ b/chunk.py @@ -177,8 +177,8 @@ class ChunkRenderer(object): self.queue = queue self.regionfile = worldobj.get_region_path(*chunkcoords) - if not os.path.exists(self.regionfile): - raise ValueError("Could not find regionfile: %s" % self.regionfile) + #if not os.path.exists(self.regionfile): + # raise ValueError("Could not find regionfile: %s" % self.regionfile) ## TODO TODO all of this class @@ -187,7 +187,7 @@ class ChunkRenderer(object): #chunkcoords = filename_split[1:3] #self.coords = map(world.base36decode, chunkcoords) - self.blockid = "%d.%d" % chunkcoords + #self.blockid = "%d.%d" % chunkcoords # chunk coordinates (useful to converting local block coords to # global block coords) @@ -197,12 +197,6 @@ class ChunkRenderer(object): self.world = worldobj self.rendermode = rendermode - if self.world.useBiomeData: - # make sure we've at least *tried* to load the color arrays in this process... - textures.prepareBiomeData(self.world.worlddir) - if not textures.grasscolor or not textures.foliagecolor: - raise Exception("Can't find grasscolor.png or foliagecolor.png") - def _load_level(self): """Loads and returns the level structure""" if not hasattr(self, "_level"): diff --git a/quadtree.py b/quadtree.py index b64333a..2437b33 100644 --- a/quadtree.py +++ b/quadtree.py @@ -434,6 +434,8 @@ class QuadtreeGen(object): # Compile this image tileimg = Image.new("RGBA", (width, height), (38,92,255,0)) + world = self.world + rendermode = self.rendermode # col colstart will get drawn on the image starting at x coordinates -(384/2) # row rowstart will get drawn on the image starting at y coordinates -(192/2) for col, row, chunkx, chunky, regionfile in chunks: @@ -441,8 +443,10 @@ class QuadtreeGen(object): ypos = -96 + (row-rowstart)*96 # draw the chunk! - # TODO POI queue - chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), self, False, poi_queue) + 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) + # Save them tileimg.save(imgpath) diff --git a/rendernode.py b/rendernode.py index 43b175d..006c820 100644 --- a/rendernode.py +++ b/rendernode.py @@ -60,7 +60,13 @@ def pool_initializer(rendernode): #stash the quadtree objects in a global variable after fork() for windows compat. global child_rendernode child_rendernode = rendernode - + for quadtree in rendernode.quadtrees: + if quadtree.world.useBiomeData: + # make sure we've at least *tried* to load the color arrays in this process... + textures.prepareBiomeData(quadtree.world.worlddir) + if not textures.grasscolor or not textures.foliagecolor: + raise Exception("Can't find grasscolor.png or foliagecolor.png") + #http://docs.python.org/library/itertools.html def roundrobin(iterables): "roundrobin('ABC', 'D', 'EF') --> A D E B F C" @@ -119,8 +125,7 @@ class RenderNode(object): # Create a pool if procs == 1: pool = FakePool() - global child_rendernode - child_rendernode = self + pool_initializer(self) else: pool = multiprocessing.Pool(processes=procs,initializer=pool_initializer,initargs=(self,)) #warm up the pool so it reports all the worker id's From bb07e07f26f66ee9c19b7326175f018cd0d0ff1b Mon Sep 17 00:00:00 2001 From: Xon Date: Mon, 28 Mar 2011 16:11:04 +0800 Subject: [PATCH 130/213] Cleaned up leftovers in chunk.py from before c_overviewer did rendering --- chunk.py | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/chunk.py b/chunk.py index 38c4e7e..47d6d02 100644 --- a/chunk.py +++ b/chunk.py @@ -406,22 +406,7 @@ class ChunkRenderer(object): For cave mode, all blocks that have any direct sunlight are not rendered, and blocks are drawn with a color tint depending on their depth.""" - blocks = self.blocks - pseudo_ancildata_blocks = set([85]) - left_blocks = self.left_blocks - right_blocks = self.right_blocks - - if cave: - # Cave mode. Actually go through and 0 out all blocks that are not in a - # cave, so that it only renders caves. - - # Places where the skylight is not 0 (there's some amount of skylight - # touching it) change it to something that won't get rendered, AND - # won't get counted as "transparent". - blocks = blocks.copy() - blocks[self.skylight != 0] = 21 - blockData = get_blockdata_array(self.level) blockData_expanded = numpy.empty((16,16,128), dtype=numpy.uint8) # Even elements get the lower 4 bits @@ -429,7 +414,6 @@ class ChunkRenderer(object): # Odd elements get the upper 4 bits blockData_expanded[:,:,1::2] = blockData >> 4 - tileEntities = get_tileentity_data(self.level) # Each block is 24x24 # The next block on the X axis adds 12px to x and subtracts 6px from y in the image @@ -443,6 +427,8 @@ class ChunkRenderer(object): c_overviewer.render_loop(self, img, xoff, yoff, blockData_expanded) + #tileEntities = get_tileentity_data(self.level) + tileEntities = [] for entity in tileEntities: if entity['id'] == 'Sign': msg=' \n'.join([entity['Text1'], entity['Text2'], entity['Text3'], entity['Text4']]) From ff7e9d0794d5966a94eee2bf11a30d1ebbdf0486 Mon Sep 17 00:00:00 2001 From: Xon Date: Mon, 28 Mar 2011 16:51:13 +0800 Subject: [PATCH 131/213] Pool warmup (to report worker IDs) set to aynsc when not in verbose mode --- rendernode.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/rendernode.py b/rendernode.py index 006c820..98a70e1 100644 --- a/rendernode.py +++ b/rendernode.py @@ -129,7 +129,10 @@ class RenderNode(object): else: pool = multiprocessing.Pool(processes=procs,initializer=pool_initializer,initargs=(self,)) #warm up the pool so it reports all the worker id's - pool.map(bool,xrange(multiprocessing.cpu_count()),1) + if logging.getLogger().level >= 10: + pool.map_(bool,xrange(multiprocessing.cpu_count()),1) + else: + pool.map_async(bool,xrange(multiprocessing.cpu_count()),1) quadtrees = self.quadtrees From db7c61a0905eda835a9414d04ca2f4ef2997ef2c Mon Sep 17 00:00:00 2001 From: Xon Date: Mon, 28 Mar 2011 19:32:41 +0800 Subject: [PATCH 132/213] Fix missing import --- rendernode.py | 1 + 1 file changed, 1 insertion(+) diff --git a/rendernode.py b/rendernode.py index 98a70e1..c993a17 100644 --- a/rendernode.py +++ b/rendernode.py @@ -62,6 +62,7 @@ def pool_initializer(rendernode): child_rendernode = rendernode for quadtree in rendernode.quadtrees: if quadtree.world.useBiomeData: + import textures # make sure we've at least *tried* to load the color arrays in this process... textures.prepareBiomeData(quadtree.world.worlddir) if not textures.grasscolor or not textures.foliagecolor: From 1ff6783e35b56dec67c505fe7e5eddf6fe4fa02a Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Mon, 28 Mar 2011 19:26:48 -0400 Subject: [PATCH 133/213] Use a different Queue object the multiprocessing module is a mystery to me --- rendernode.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rendernode.py b/rendernode.py index 43b175d..74d7c46 100644 --- a/rendernode.py +++ b/rendernode.py @@ -92,7 +92,8 @@ class RenderNode(object): # queue for receiving interesting events from the renderer # (like the discovery of signs! - self.poi_q = multiprocessing.Queue() + self.manager = multiprocessing.Manager() + self.poi_q = self.manager.Queue() self.world = world From 0caa033292536828c43cf0456ae167a15ac49338 Mon Sep 17 00:00:00 2001 From: Xon Date: Tue, 29 Mar 2011 07:38:49 +0800 Subject: [PATCH 134/213] Made rendernode.py POI queue world independant --- overviewer.py | 2 +- rendernode.py | 72 ++++++++++++++++++++++++++++----------------------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/overviewer.py b/overviewer.py index a161f22..0e012fe 100755 --- a/overviewer.py +++ b/overviewer.py @@ -186,7 +186,7 @@ def main(): q.append(qtree) #create the distributed render - r = rendernode.RenderNode(q, world=w) + r = rendernode.RenderNode(q) # write out the map and web assets m = googlemap.MapGen(q, skipjs=options.skipjs, web_assets_hook=options.web_assets_hook) diff --git a/rendernode.py b/rendernode.py index c993a17..f61a986 100644 --- a/rendernode.py +++ b/rendernode.py @@ -84,7 +84,7 @@ def roundrobin(iterables): class RenderNode(object): - def __init__(self, quadtrees, world): + def __init__(self, quadtrees): """Distributes the rendering of a list of quadtrees.""" if not len(quadtrees) > 0: @@ -92,16 +92,21 @@ class RenderNode(object): self.quadtrees = quadtrees #bind an index value to the quadtree so we can find it again + #and figure out which worlds are where i = 0 + self.worlds = [] for q in quadtrees: q._render_index = i - i += 1 + i += 1 + if q.world not in self.worlds: + self.worlds.append(q.world) + manager = multiprocessing.Manager() # queue for receiving interesting events from the renderer # (like the discovery of signs! - self.poi_q = multiprocessing.Queue() - - self.world = world + #stash into the world object like we stash an index into the quadtree + for world in self.worlds: + world.poi_q = manager.Queue() def print_statusline(self, complete, total, level, unconditional=False): @@ -168,18 +173,19 @@ class RenderNode(object): timestamp = timestamp2 count_to_remove = (1000//batch_size) if count_to_remove < len(results): - try: - while (1): - # an exception will break us out of this loop - item = self.poi_q.get(block=False) - if item[0] == "newpoi": - if item[1] not in self.world.POI: - #print "got an item from the queue!" - self.world.POI.append(item[1]) - elif item[0] == "removePOI": - self.world.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], self.world.persistentData['POI']) - except Queue.Empty: - pass + for world in self.worlds: + try: + while (1): + # an exception will break us out of this loop + item = world.poi_q.get(block=False) + if item[0] == "newpoi": + if item[1] not in world.POI: + #print "got an item from the queue!" + world.POI.append(item[1]) + elif item[0] == "removePOI": + world.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], world.persistentData['POI']) + except Queue.Empty: + pass while count_to_remove > 0: count_to_remove -= 1 complete += results.popleft().get() @@ -195,18 +201,19 @@ class RenderNode(object): while len(results) > 0: complete += results.popleft().get() self.print_statusline(complete, total, 1) - try: - while (1): - # an exception will break us out of this loop - item = self.poi_q.get(block=False) - if item[0] == "newpoi": - if item[1] not in self.world.POI: - #print "got an item from the queue!" - self.world.POI.append(item[1]) - elif item[0] == "removePOI": - self.world.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], self.world.persistentData['POI']) - except Queue.Empty: - pass + for world in self.worlds: + try: + while (1): + # an exception will break us out of this loop + item = world.poi_q.get(block=False) + if item[0] == "newpoi": + if item[1] not in world.POI: + #print "got an item from the queue!" + world.POI.append(item[1]) + elif item[0] == "removePOI": + world.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], world.persistentData['POI']) + except Queue.Empty: + pass self.print_statusline(complete, total, 1, True) @@ -271,10 +278,10 @@ class RenderNode(object): jobcount += 1 if jobcount >= batch_size: jobcount = 0 - yield pool.apply_async(func=render_worldtile_batch, args= [batch, self.poi_q]) + yield pool.apply_async(func=render_worldtile_batch, args= [batch]) batch = [] if jobcount > 0: - yield pool.apply_async(func=render_worldtile_batch, args= [batch, self.poi_q]) + yield pool.apply_async(func=render_worldtile_batch, args= [batch]) def _apply_render_inntertile(self, pool, zoom,batch_size): """Same as _apply_render_worltiles but for the inntertile routine. @@ -303,7 +310,7 @@ class RenderNode(object): yield pool.apply_async(func=render_innertile_batch, args= [batch]) @catch_keyboardinterrupt -def render_worldtile_batch(batch, poi_queue): +def render_worldtile_batch(batch): global child_rendernode rendernode = child_rendernode count = 0 @@ -316,6 +323,7 @@ def render_worldtile_batch(batch, poi_queue): rowstart = job[3] rowend = job[4] path = job[5] + poi_queue = quadtree.world.poi_q path = quadtree.full_tiledir+os.sep+path # (even if tilechunks is empty, render_worldtile will delete # existing images if appropriate) From a1f5c9ddaf4be8e07f407c697ba225eb9c2663a7 Mon Sep 17 00:00:00 2001 From: Xon Date: Tue, 29 Mar 2011 09:13:30 +0800 Subject: [PATCH 135/213] Fixed POI code in chunk.py after cleanup --- chunk.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/chunk.py b/chunk.py index 47d6d02..a59d90e 100644 --- a/chunk.py +++ b/chunk.py @@ -427,8 +427,7 @@ class ChunkRenderer(object): c_overviewer.render_loop(self, img, xoff, yoff, blockData_expanded) - #tileEntities = get_tileentity_data(self.level) - tileEntities = [] + tileEntities = get_tileentity_data(self.level) for entity in tileEntities: if entity['id'] == 'Sign': msg=' \n'.join([entity['Text1'], entity['Text2'], entity['Text3'], entity['Text4']]) From 2a234e065960927d16b3d0c625f7948699719223 Mon Sep 17 00:00:00 2001 From: Xon Date: Tue, 29 Mar 2011 09:15:34 +0800 Subject: [PATCH 136/213] Fix typo in pool warmup in non-verbose mode --- rendernode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rendernode.py b/rendernode.py index f61a986..d762b73 100644 --- a/rendernode.py +++ b/rendernode.py @@ -136,7 +136,7 @@ class RenderNode(object): pool = multiprocessing.Pool(processes=procs,initializer=pool_initializer,initargs=(self,)) #warm up the pool so it reports all the worker id's if logging.getLogger().level >= 10: - pool.map_(bool,xrange(multiprocessing.cpu_count()),1) + pool.map(bool,xrange(multiprocessing.cpu_count()),1) else: pool.map_async(bool,xrange(multiprocessing.cpu_count()),1) From 346c42d64bf76f79d789c63c536d08beba719f23 Mon Sep 17 00:00:00 2001 From: Xon Date: Tue, 29 Mar 2011 09:56:00 +0800 Subject: [PATCH 137/213] Fixed chunk caching to correctly count empty chunk responses. Tweaked upper bound on number of chunks to cache. --- world.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/world.py b/world.py index 4f1b4cf..665619d 100644 --- a/world.py +++ b/world.py @@ -84,9 +84,10 @@ class World(object): self.regionfiles = regionfiles # set the number of region file handles we will permit open at any time before we start closing them # self.regionlimit = 1000 - # the max number of chunks we will keep before removing them - self.chunklimit = 1024*6 # this should be a multipule of the max chunks per region or things could get wonky ??? + # the max number of chunks we will keep before removing them (includes emptry chunks) + self.chunklimit = 1024 self.chunkcount = 0 + self.empty_chunk = [None,None] logging.debug("Done scanning regions") # figure out chunk format is in use @@ -116,7 +117,8 @@ class World(object): else: # some defaults self.persistentData = dict(POI=[]) - + + def get_region_path(self, chunkX, chunkY): """Returns the path to the region that contains chunk (chunkX, chunkY) """ @@ -131,16 +133,18 @@ class World(object): chunks = regioninfo[2] chunk_data = chunks.get((x,y)) if chunk_data is None: - nbt = self.load_region(filename).load_chunk(x, y) - if nbt is None: - chunks[(x,y)] = [None,None] - return None ## return none. I think this is who we should indicate missing chunks - #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) #prune the cache if required if self.chunkcount > self.chunklimit: #todo: make the emptying the chunk cache slightly less crazy [self.reload_region(regionfile) for regionfile in self.regions if regionfile <> filename] + self.chunkcount = 0 self.chunkcount += 1 + nbt = self.load_region(filename).load_chunk(x, y) + if nbt is None: + chunks[(x,y)] = self.empty_chunk + return None ## return none. I think this is who we should indicate missing chunks + #raise IOError("No such chunk in region: (%i, %i)" % (x, y)) + #we cache the transformed data, not it's raw form data = nbt.read_all() level = data[1]['Level'] From d7af3d437b65d3776344c155be465331561082a6 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 29 Mar 2011 04:33:41 -0400 Subject: [PATCH 138/213] changing the C header files now correctly rebuilds whole extension --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 34f646b..62cc86e 100644 --- a/setup.py +++ b/setup.py @@ -52,7 +52,8 @@ except AttributeError: c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/endian.c'] c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c', 'src/rendermode-night.c', 'src/rendermode-spawn.c'] -setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], extra_link_args=[])) +c_overviewer_includes = ['src/overviewer.h', 'src/rendermodes.h'] +setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], depends=c_overviewer_includes, extra_link_args=[])) # tell build_ext to build the extension in-place # (NOT in build/) setup_kwargs['options']['build_ext'] = {'inplace' : 1} From 862ee62cb924fc520a936c98f2cfd04aac39fd2c Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 29 Mar 2011 04:46:40 -0400 Subject: [PATCH 139/213] fixed warning during compile about endian.c --- src/endian.c | 2 +- src/overviewer.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/endian.c b/src/endian.c index 5b0a105..6434239 100644 --- a/src/endian.c +++ b/src/endian.c @@ -23,7 +23,7 @@ static int endianness = UNKNOWN_ENDIAN; -void init_endian() { +void init_endian(void) { /* figure out what our endianness is! */ short word = 0x0001; char* byte = (char*)(&word); diff --git a/src/overviewer.h b/src/overviewer.h index 3475fdf..dc1c31e 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -79,7 +79,7 @@ PyObject *chunk_render(PyObject *self, PyObject *args); #include "rendermodes.h" /* in endian.c */ -void init_endian(); +void init_endian(void); unsigned short big_endian_ushort(unsigned short in); unsigned int big_endian_uint(unsigned int in); From 5f0766839e71cbfcef69135451ecbbd0e87716bf Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 29 Mar 2011 05:13:25 -0400 Subject: [PATCH 140/213] added metadata to rendermodes, and python bindings for them --- src/main.c | 4 ++ src/rendermode-lighting.c | 1 + src/rendermode-night.c | 1 + src/rendermode-normal.c | 1 + src/rendermode-spawn.c | 1 + src/rendermodes.c | 79 +++++++++++++++++++++++++++++++++++---- src/rendermodes.h | 10 ++++- 7 files changed, 88 insertions(+), 9 deletions(-) diff --git a/src/main.c b/src/main.c index a8c6075..e2be7a3 100644 --- a/src/main.c +++ b/src/main.c @@ -22,6 +22,10 @@ static PyMethodDef COverviewerMethods[] = { "alpha over composite function"}, {"render_loop", chunk_render, METH_VARARGS, "Renders stuffs"}, + {"get_render_modes", get_render_modes, METH_VARARGS, + "returns available render modes"}, + {"get_render_mode_info", get_render_mode_info, METH_VARARGS, + "returns info for a particular render mode"}, {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index 737a11b..628e5c4 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -228,6 +228,7 @@ rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject } RenderModeInterface rendermode_lighting = { + "lighting", "draw shadows from the lighting data", sizeof(RenderModeLighting), rendermode_lighting_start, rendermode_lighting_finish, diff --git a/src/rendermode-night.c b/src/rendermode-night.c index d4b1eb4..1e54f05 100644 --- a/src/rendermode-night.c +++ b/src/rendermode-night.c @@ -60,6 +60,7 @@ rendermode_night_draw(void *data, RenderState *state, PyObject *src, PyObject *m } RenderModeInterface rendermode_night = { + "night", "like \"lighting\", except at night", sizeof(RenderModeNight), rendermode_night_start, rendermode_night_finish, diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c index d7a1367..e38118d 100644 --- a/src/rendermode-normal.c +++ b/src/rendermode-normal.c @@ -164,6 +164,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * } RenderModeInterface rendermode_normal = { + "normal", "nothing special, just render the blocks", sizeof(RenderModeNormal), rendermode_normal_start, rendermode_normal_finish, diff --git a/src/rendermode-spawn.c b/src/rendermode-spawn.c index 1a7c96a..b19f409 100644 --- a/src/rendermode-spawn.c +++ b/src/rendermode-spawn.c @@ -118,6 +118,7 @@ rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *m } RenderModeInterface rendermode_spawn = { + "spawn", "draws red where monsters can spawn at night", sizeof(RenderModeSpawn), rendermode_spawn_start, rendermode_spawn_finish, diff --git a/src/rendermodes.c b/src/rendermodes.c index f4fbe89..0ef5550 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -18,21 +18,84 @@ #include "overviewer.h" #include +/* list of all render modes, ending in NULL + all of these will be available to the user, so DON'T include modes + that are only useful as a base for other modes. */ +static RenderModeInterface *render_modes[] = { + &rendermode_normal, + &rendermode_lighting, + &rendermode_night, + &rendermode_spawn, + NULL +}; + /* decides which render mode to use */ RenderModeInterface *get_render_mode(RenderState *state) { - /* default: normal */ - RenderModeInterface *iface = &rendermode_normal; + unsigned int i; + /* default: NULL --> an error */ + RenderModeInterface *iface = NULL; PyObject *rendermode_py = PyObject_GetAttrString(state->self, "rendermode"); const char *rendermode = PyString_AsString(rendermode_py); - if (strcmp(rendermode, "lighting") == 0) { - iface = &rendermode_lighting; - } else if (strcmp(rendermode, "night") == 0) { - iface = &rendermode_night; - } else if (strcmp(rendermode, "spawn") == 0) { - iface = &rendermode_spawn; + for (i = 0; render_modes[i] != NULL; i++) { + if (strcmp(render_modes[i]->name, rendermode) == 0) { + iface = render_modes[i]; + break; + } } Py_DECREF(rendermode_py); return iface; } + +/* bindings for python -- get all the rendermode names */ +PyObject *get_render_modes(PyObject *self, PyObject *args) { + PyObject *modes; + unsigned int i; + if (!PyArg_ParseTuple(args, "")) + return NULL; + + modes = PyList_New(0); + if (modes == NULL) + return NULL; + + for (i = 0; render_modes[i] != NULL; i++) { + PyObject *name = PyString_FromString(render_modes[i]->name); + PyList_Append(modes, name); + Py_DECREF(name); + } + + return modes; +} + +/* more bindings -- return info for a given rendermode name */ +PyObject *get_render_mode_info(PyObject *self, PyObject *args) { + const char* rendermode; + PyObject *info; + unsigned int i; + if (!PyArg_ParseTuple(args, "s", &rendermode)) + return NULL; + + info = PyDict_New(); + if (info == NULL) + return NULL; + + for (i = 0; render_modes[i] != NULL; i++) { + if (strcmp(render_modes[i]->name, rendermode) == 0) { + PyObject *tmp; + + tmp = PyString_FromString(render_modes[i]->name); + PyDict_SetItemString(info, "name", tmp); + Py_DECREF(tmp); + + tmp = PyString_FromString(render_modes[i]->description); + PyDict_SetItemString(info, "description", tmp); + Py_DECREF(tmp); + + return info; + } + } + + Py_DECREF(info); + Py_RETURN_NONE; +} diff --git a/src/rendermodes.h b/src/rendermodes.h index d26091d..3027a7f 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -29,7 +29,7 @@ * (see rendermode-night.c for a simple example derived from * the "lighting" mode) * - * * add a condition to get_render_mode() in rendermodes.c + * * add your mode to the list in rendermodes.c */ #ifndef __RENDERMODES_H_INCLUDED__ @@ -39,6 +39,11 @@ /* rendermode interface */ typedef struct { + /* the name of this mode */ + const char* name; + /* the short description of this render mode */ + const char* description; + /* the size of the local storage for this rendermode */ unsigned int data_size; @@ -53,6 +58,9 @@ typedef struct { /* figures out the render mode to use from the given ChunkRenderer */ RenderModeInterface *get_render_mode(RenderState *state); +/* python bindings */ +PyObject *get_render_modes(PyObject *self, PyObject *args); +PyObject *get_render_mode_info(PyObject *self, PyObject *args); /* individual rendermode interface declarations follow */ From 88d4ebe5ae1c949b44a8fbb18e41b56ab17a3be8 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 29 Mar 2011 05:24:25 -0400 Subject: [PATCH 141/213] added --list-rendermodes option --- overviewer.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/overviewer.py b/overviewer.py index 0e012fe..b16f835 100755 --- a/overviewer.py +++ b/overviewer.py @@ -57,14 +57,17 @@ def main(): cpus = multiprocessing.cpu_count() except NotImplementedError: cpus = 1 - + + avail_rendermodes = c_overviewer.get_render_modes() + parser = ConfigOptionParser(usage=helptext, config="settings.py") parser.add_option("-V", "--version", dest="version", help="Displays version information and then exits", action="store_true") parser.add_option("-p", "--processes", dest="procs", help="How many worker processes to start. Default %s" % cpus, default=cpus, action="store", type="int") parser.add_option("-z", "--zoom", dest="zoom", help="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", action="store", type="int", configFileOnly=True) parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true", commandLineOnly=True) parser.add_option("--chunklist", dest="chunklist", help="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.") - parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render type: normal (default), lighting, night, or spawn.", type="choice", choices=["normal", "lighting", "night", "spawn"], required=True, default="normal", listify=True) + parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render types, separated by commas. Use --list-rendermodes to list them all.", type="choice", choices=avail_rendermodes, required=True, default=avail_rendermodes[0], listify=True) + parser.add_option("--list-rendermodes", dest="list_rendermodes", action="store_true", help="List available render modes and exit.", commandLineOnly=True) parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.", configFileOnly=True ) parser.add_option("--optimize-img", dest="optimizeimg", help="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%", configFileOnly=True) parser.add_option("--web-assets-hook", dest="web_assets_hook", help="If provided, run this function after the web assets have been copied, but before actual tile rendering begins. It should accept a QuadtreeGen object as its only argument.", action="store", metavar="SCRIPT", type="function", configFileOnly=True) @@ -88,6 +91,13 @@ def main(): except: pass sys.exit(0) + + if options.list_rendermodes: + rendermode_info = map(c_overviewer.get_render_mode_info, avail_rendermodes) + name_width = max(map(lambda i: len(i['name']), rendermode_info)) + for info in rendermode_info: + print "{name:{0}} {description}".format(name_width, **info) + sys.exit(0) if len(args) < 1: print "You need to give me your world number or directory" From 9b36d21c37b263252957b81a1bde10fdd29a523f Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 29 Mar 2011 05:29:37 -0400 Subject: [PATCH 142/213] moved non-config, global variables out of config.js --- config.js | 7 ------- web_assets/functions.js | 7 +++++++ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/config.js b/config.js index 9296853..6be0893 100644 --- a/config.js +++ b/config.js @@ -50,10 +50,3 @@ var mapTypeData=[ var mapTypeData = {maptypedata}; -// Please leave the following variables here: -var markerCollection = {}; // holds groups of markers - -var map; - -var markersInit = false; -var regionsInit = false; diff --git a/web_assets/functions.js b/web_assets/functions.js index 174a43a..3487d5b 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -1,3 +1,10 @@ +var markerCollection = {}; // holds groups of markers + +var map; + +var markersInit = false; +var regionsInit = false; + var prevInfoWindow = null; function prepareSignMarker(marker, item) { From 9c25c6259cc07b3a09e657e479e729c6511573e4 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 29 Mar 2011 05:48:40 -0400 Subject: [PATCH 143/213] added center to config.js, lets you set map center in world coords By default, it is set to spawn. Also I changed defaultZoom to 2, which looks better (the world used to show up as *tiny* by default). --- config.js | 5 ++++- googlemap.py | 3 +++ web_assets/functions.js | 8 ++++++-- world.py | 1 + 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/config.js b/config.js index 6be0893..78dfd7d 100644 --- a/config.js +++ b/config.js @@ -2,8 +2,11 @@ var config = { fileExt: '{imgformat}', tileSize: 384, - defaultZoom: 1, + defaultZoom: 2, maxZoom: {maxzoom}, + // center on this point, in world coordinates, ex: + //center: [0,0,0], + center: {spawn_coords}, cacheMinutes: 0, // Change this to have browsers automatically request new images every x minutes bg_color: '#1A1A1A', debug: false diff --git a/googlemap.py b/googlemap.py index 65fd8e8..2321049 100644 --- a/googlemap.py +++ b/googlemap.py @@ -94,6 +94,9 @@ class MapGen(object): config = config.replace( "{imgformat}", str(imgformat)) + config = config.replace("{spawn_coords}", + json.dumps(list(self.world.spawn))) + # create generated map type data, from given quadtrees maptypedata = map(lambda q: {'label' : q.rendermode.capitalize(), 'path' : q.tiledir}, self.quadtrees) diff --git a/web_assets/functions.js b/web_assets/functions.js index 3487d5b..0ec7eae 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -256,8 +256,12 @@ function initialize() { var query = location.search.substring(1); - var lat = 0.5; - var lng = 0.5; + var defaultCenter = fromWorldToLatLng(config.center[0], + config.center[1], + config.center[2]); + var lat = defaultCenter.lat(); + var lng = defaultCenter.lng(); + var zoom = config.defaultZoom; var pairs = query.split("&"); for (var i=0; i Date: Tue, 29 Mar 2011 20:45:10 +0200 Subject: [PATCH 144/213] Fix a bug of the type "What I was thinking!?" in _build_full_block --- textures.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/textures.py b/textures.py index fa1ac68..cc1db08 100644 --- a/textures.py +++ b/textures.py @@ -297,8 +297,6 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None if side2 != None : side2 = transform_image_side(side2, blockID) - side2 = side2.transpose(Image.FLIP_LEFT_RIGHT) - side2 = side2.transpose(Image.FLIP_TOP_BOTTOM) # Darken this side. sidealpha2 = side2.split()[3] From f240da29f471ee00b35c59e976cbb297cb9aebce Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Tue, 29 Mar 2011 21:11:22 +0200 Subject: [PATCH 145/213] Re-fix the bug "what I was thinking?!" Improve the looking of all torches, and add orientation for them. --- textures.py | 67 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/textures.py b/textures.py index cc1db08..f7aff0c 100644 --- a/textures.py +++ b/textures.py @@ -285,7 +285,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None # first back sides if side1 != None : side1 = transform_image_side(side1, blockID) - side1 = side1.transpose(Image.FLIP_TOP_BOTTOM) + side1 = side1.transpose(Image.FLIP_LEFT_RIGHT) # Darken this side. sidealpha = side1.split()[3] @@ -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, 80, -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, 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 # 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,115, 99, -1, 66, 67, # door,ladder left out. Minecart rail orientation, redstone torches + -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 # 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, 80, -1, 65, 4, 25,101, 98, 24, -1, -1, 86, -1, -1, -1, + 36, 37, -1, -1, 65, 4, 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,115, 99, -1, 66, 67, + -1, -1, -1, 16, -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 ] @@ -1002,6 +1002,58 @@ def generate_special_texture(blockID, data): #~ composite.alpha_over(img, top, (0,2), top) return (img.convert("RGB"), img.split()[3]) + if blockID in (50,75,76): # torch, off redstone torch, on redstone torch + + # choose the proper texture + if blockID == 50: # torch + small = terrain_images[80] + elif blockID == 75: # off redstone torch + small = terrain_images[115] + else: # on redstone torch + small = terrain_images[99] + + # compose a torch bigger than the normal + # (better for doing transformations) + torch = Image.new("RGBA", (16,16), (38,92,255,0)) + composite.alpha_over(torch,small,(-4,-3)) + composite.alpha_over(torch,small,(-5,-2)) + composite.alpha_over(torch,small,(-3,-2)) + + # angle of inclination of the texture + rotation = 15 + + if data == 1: # pointing south + torch = torch.rotate(-rotation, Image.NEAREST) # nearest filter is more nitid. + img = _build_full_block(None, None, None, torch, None, None, blockID) + + elif data == 2: # pointing north + torch = torch.rotate(rotation, Image.NEAREST) + img = _build_full_block(None, None, torch, None, None, None, blockID) + + elif data == 3: # pointing west + torch = torch.rotate(rotation, Image.NEAREST) + img = _build_full_block(None, torch, None, None, None, None, blockID) + + elif data == 4: # pointing east + torch = torch.rotate(-rotation, Image.NEAREST) + img = _build_full_block(None, None, None, None, torch, None, blockID) + + elif data == 5: # standing on the floor + # compose a "3d torch". + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + small_crop = small.crop((2,2,14,14)) + slice = small_crop.copy() + ImageDraw.Draw(slice).rectangle((6,0,12,12),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(slice).rectangle((0,0,4,12),outline=(0,0,0,0),fill=(0,0,0,0)) + + composite.alpha_over(img, slice, (6,4)) + composite.alpha_over(img, small_crop, (5,5)) + composite.alpha_over(img, small_crop, (6,5)) + composite.alpha_over(img, slice, (6,6)) + + return (img.convert("RGB"), img.split()[3]) + return None @@ -1076,7 +1128,7 @@ def getBiomeData(worlddir, chunkX, chunkY): # This set holds block ids that require special pre-computing. These are typically # things that require ancillary data to render properly (i.e. ladder plus orientation) -special_blocks = set([66,59,61,62, 65,64,71,91,86,2,18,85,17,23,35,51,43,44,9,55,58,92]) +special_blocks = set([66,59,61,62, 65,64,71,91,86,2,18,85,17,23,35,51,43,44,9,55,50,58,75,76,92]) # this is a map of special blockIDs to a list of all # possible values for ancillary data that it might have. @@ -1101,6 +1153,9 @@ special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unkn special_map[55] = range(128) # redstone wire special_map[58] = (0,) # crafting table special_map[92] = range(6) # cake! +special_map[50] = (1,2,3,4,5) # torch +special_map[75] = (1,2,3,4,5) # off redstone torch +special_map[76] = (1,2,3,4,5) # on redstone torch # apparently pumpkins and jack-o-lanterns have ancillary data, but it's unknown # what that data represents. For now, assume that the range for data is 0 to 5 From 5e4383cd666832dab5f480401ce2622c527d6595 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Wed, 30 Mar 2011 20:35:55 -0400 Subject: [PATCH 146/213] Print out the tilesets being rendered --- overviewer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/overviewer.py b/overviewer.py index b16f835..1963ebd 100755 --- a/overviewer.py +++ b/overviewer.py @@ -184,6 +184,8 @@ def main(): w = world.World(worlddir, useBiomeData=useBiomeData) w.go(options.procs) + logging.info("Rending the following tilesets: %s", ",".join(options.rendermode)) + # create the quadtrees # TODO chunklist q = [] From 3f62beb1b0acdd018e599ee69131b7cadbd3fb05 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Wed, 30 Mar 2011 20:49:27 -0400 Subject: [PATCH 147/213] Converted some print statements to logging statments --- configParser.py | 18 +++++++++++------- overviewer.py | 8 ++++---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/configParser.py b/configParser.py index cbdbc4c..88925d7 100644 --- a/configParser.py +++ b/configParser.py @@ -1,6 +1,7 @@ from optparse import OptionParser import sys import os.path +import logging class OptionsResults(object): pass @@ -22,10 +23,12 @@ class ConfigOptionParser(object): self.requiredArgs = [] def display_config(self): + logging.info("Using the following settings:") for x in self.configVars: n = x['dest'] print "%s: %r" % (n, self.configResults.__dict__[n]) + def add_option(self, *args, **kwargs): if kwargs.get("configFileOnly", False) and kwargs.get("commandLineOnly", False): @@ -71,14 +74,14 @@ class ConfigOptionParser(object): except NameError, ex: import traceback traceback.print_exc() - print "\nError parsing %s. Please check the trackback above" % self.configFile + logging.error("Error parsing %s. Please check the trackback above" % self.configFile) sys.exit(1) except SyntaxError, ex: import traceback traceback.print_exc() tb = sys.exc_info()[2] #print tb.tb_frame.f_code.co_filename - print "\nError parsing %s. Please check the trackback above" % self.configFile + logging.error("Error parsing %s. Please check the trackback above" % self.configFile) sys.exit(1) #print l.keys() @@ -89,7 +92,7 @@ class ConfigOptionParser(object): n = a['dest'] if a.get('commandLineOnly', False): if n in l.keys(): - print "Error: %s can only be specified on the command line. It is not valid in the config file" % n + logging.error("Error: %s can only be specified on the command line. It is not valid in the config file" % n) sys.exit(1) configResults.__dict__[n] = l.get(n) @@ -113,7 +116,8 @@ class ConfigOptionParser(object): for a in self.configVars: n = a['dest'] if configResults.__dict__[n] == None and a.get('required',False): - raise Exception("%s is required" % n) + logging.error("%s is required" % n) + sys.exit(1) # sixth, check types for a in self.configVars: @@ -128,7 +132,7 @@ class ConfigOptionParser(object): try: configResults.__dict__[n] = self.checkType(configResults.__dict__[n], a) except ValueError, ex: - print "There was a problem converting the value '%s' to type %s for config parameter '%s'" % (configResults.__dict__[n], a['type'], n) + logging.error("There was a problem converting the value '%s' to type %s for config parameter '%s'" % (configResults.__dict__[n], a['type'], n)) import traceback #traceback.print_exc() sys.exit(1) @@ -153,7 +157,7 @@ class ConfigOptionParser(object): return long(value) elif a['type'] == "choice": if value not in a['choices']: - print "The value '%s' is not valid for config parameter '%s'" % (value, a['dest']) + logging.error("The value '%s' is not valid for config parameter '%s'" % (value, a['dest'])) sys.exit(1) return value elif a['type'] == "float": @@ -164,5 +168,5 @@ class ConfigOptionParser(object): if not callable(value): raise ValueError("Not callable") else: - print "Unknown type!" + logging.error("Unknown type!") sys.exit(1) diff --git a/overviewer.py b/overviewer.py index 1963ebd..c715a4c 100755 --- a/overviewer.py +++ b/overviewer.py @@ -100,7 +100,7 @@ def main(): sys.exit(0) if len(args) < 1: - print "You need to give me your world number or directory" + logging.error("You need to give me your world number or directory") parser.print_help() list_worlds() sys.exit(1) @@ -113,7 +113,7 @@ def main(): # if there are no worlds found at all, exit now if not worlds: parser.print_help() - print "\nInvalid world path" + logging.error("Invalid world path") sys.exit(1) try: @@ -126,12 +126,12 @@ def main(): except KeyError: # it's not a number, name, or path parser.print_help() - print "Invalid world name or path" + logging.error("Invalid world name or path") sys.exit(1) except KeyError: # it was an invalid number parser.print_help() - print "Invalid world number" + logging.error("Invalid world number") sys.exit(1) if len(args) != 2: From 744a65f1e2eed0d283a07f38e7fac9c70cdd3144 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Wed, 30 Mar 2011 21:29:10 -0400 Subject: [PATCH 148/213] Provide some better errors messages to the user. If the c_overviewer module is present but will not import, provide a different error than if the modules doesn't exist at all. Also, added a new mechanism to force users to re-build their extension if needed (see the top of overviewer.h) --- overviewer.py | 28 ++++++++++++++++++++++++++++ src/main.c | 8 ++++++++ src/overviewer.h | 4 ++++ util.py | 9 ++++++--- 4 files changed, 46 insertions(+), 3 deletions(-) diff --git a/overviewer.py b/overviewer.py index a161f22..7858b1f 100755 --- a/overviewer.py +++ b/overviewer.py @@ -29,17 +29,45 @@ import multiprocessing import time import logging import util +import platform logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s") +this_dir = util.get_program_path() + # make sure the c_overviewer extension is available try: import c_overviewer except ImportError: + ## try to find the build extension + ext = os.path.join(this_dir, "c_overviewer.%s" % ("pyd" if platform.system() == "Windows" else "so")) + if os.path.exists(ext): + print "Something has gone wrong importing the c_overviewer extension. Please" + print "make sure it is up-to-date (clean and rebuild)" + sys.exit(1) + print "You need to compile the c_overviewer module to run Minecraft Overviewer." print "Run `python setup.py build`, or see the README for details." sys.exit(1) +if hasattr(sys, "frozen"): + pass # we don't bother with a compat test since it should always be in sync +elif "extension_version" in dir(c_overviewer): + # check to make sure the binary matches the headers + if os.path.exists(os.path.join(this_dir, "src", "overviewer.h")): + with open(os.path.join(this_dir, "src", "overviewer.h")) as f: + lines = f.readlines() + lines = filter(lambda x: x.startswith("#define OVERVIEWER_EXTENSION_VERSION"), lines) + if lines: + l = lines[0] + if int(l.split()[2].strip()) != c_overviewer.extension_version(): + print "Please rebuild your c_overviewer module. It is out of date!" + sys.exit(1) +else: + print "Please rebuild your c_overviewer module. It is out of date!" + sys.exit(1) + + import optimizeimages import world import quadtree diff --git a/src/main.c b/src/main.c index a8c6075..9d82cfc 100644 --- a/src/main.c +++ b/src/main.c @@ -17,14 +17,22 @@ #include "overviewer.h" +PyObject *get_extension_version(PyObject *self, PyObject *args) { + + return Py_BuildValue("i", OVERVIEWER_EXTENSION_VERSION); +} + static PyMethodDef COverviewerMethods[] = { {"alpha_over", alpha_over_wrap, METH_VARARGS, "alpha over composite function"}, {"render_loop", chunk_render, METH_VARARGS, "Renders stuffs"}, + {"extension_version", get_extension_version, METH_VARARGS, + "Returns the extension version"}, {NULL, NULL, 0, NULL} /* Sentinel */ }; + PyMODINIT_FUNC initc_overviewer(void) { diff --git a/src/overviewer.h b/src/overviewer.h index 3475fdf..3be3bc2 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -24,6 +24,10 @@ #ifndef __OVERVIEWER_H_INCLUDED__ #define __OVERVIEWER_H_INCLUDED__ +// increment this value if you've made a change to the c extesion +// and want to force users to rebuild +#define OVERVIEWER_EXTENSION_VERSION 2 + /* Python PIL, and numpy headers */ #include #include diff --git a/util.py b/util.py index f39798a..83f5383 100644 --- a/util.py +++ b/util.py @@ -34,11 +34,14 @@ def get_program_path(): def findGitVersion(): - if os.path.exists(".git"): - with open(os.path.join(".git","HEAD")) as f: + this_dir = get_program_path() + if os.path.exists(os.path.join(this_dir,".git")): + with open(os.path.join(this_dir,".git","HEAD")) as f: data = f.read().strip() if data.startswith("ref: "): - with open(os.path.join(".git", data[5:])) as g: + if not os.path.exists(os.path.join(this_dir,data[5:])): + return data + with open(os.path.join(this_dir, ".git", data[5:])) as g: return g.read().strip() else: return data From 6f1bb4ef8d420e7cc8a51ec5f793fa18bc760cbc Mon Sep 17 00:00:00 2001 From: Xon Date: Fri, 1 Apr 2011 16:09:34 +0800 Subject: [PATCH 149/213] Tweaked get_chunks_in_range to only look up the region object if it has changed in the inner-loop --- quadtree.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/quadtree.py b/quadtree.py index 2437b33..ac1c26c 100644 --- a/quadtree.py +++ b/quadtree.py @@ -222,6 +222,10 @@ class QuadtreeGen(object): unconvert_coords = self.world.unconvert_coords #get_region_path = self.world.get_region_path get_region = self.world.regionfiles.get + regionx = None + regiony = None + c = None + mcr = None for row in xrange(rowstart-16, rowend+1): for col in xrange(colstart, colend+1): # due to how chunks are arranged, we can only allow @@ -230,12 +234,18 @@ class QuadtreeGen(object): if row % 2 != col % 2: continue - # return (col, row, chunkx, chunky, regionpath) chunkx, chunky = unconvert_coords(col, row) - #c = get_region_path(chunkx, chunky) - _, _, c, mcr = get_region((chunkx//32, chunky//32),(None,None,None,None)); + + regionx_ = chunkx//32 + regiony_ = chunky//32 + if regionx_ != regionx or regiony_ != regiony: + regionx = regionx_ + regiony = regiony_ + _, _, c, mcr = get_region((regionx, regiony),(None,None,None,None)) + if c is not None and mcr.chunkExists(chunkx,chunky): chunklist.append((col, row, chunkx, chunky, c)) + return chunklist def get_worldtiles(self): From 84c3ab397bfeb3dcccd648874af67b49099822c4 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Fri, 1 Apr 2011 20:26:11 +0200 Subject: [PATCH 150/213] Clean textures.py, remove old comments, add new ones. The generate_special_texture function is in ascending blockid order. --- textures.py | 885 ++++++++++++++++++++++++++-------------------------- 1 file changed, 451 insertions(+), 434 deletions(-) diff --git a/textures.py b/textures.py index f7aff0c..13adb12 100644 --- a/textures.py +++ b/textures.py @@ -429,242 +429,45 @@ load_water() def generate_special_texture(blockID, data): """Generates a special texture, such as a correctly facing minecraft track""" #print "%s has ancillary data: %X" %(blockID, data) - # TODO torches, redstone torches, crops, ladders, stairs, - # levers, doors, buttons, and signs all need to be handled here (and in chunkpy) - if blockID == 66: # minetrack: - raw_straight = terrain_images[128] - raw_corner = terrain_images[112] - - ## use transform_image to scale and shear - if data == 0: - track = transform_image(raw_straight, blockID) - elif data == 6: - track = transform_image(raw_corner, blockID) - elif data == 7: - track = transform_image(raw_corner.rotate(270), blockID) - elif data == 8: - # flip - track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM).rotate(90), - blockID) - elif data == 9: - track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM), - blockID) - elif data == 1: - track = transform_image(raw_straight.rotate(90), blockID) - - #slopes - elif data == 2: # slope going up in +x direction - track = transform_image_slope(raw_straight,blockID) - track = track.transpose(Image.FLIP_LEFT_RIGHT) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, track, (2,0), track) - # the 2 pixels move is needed to fit with the adjacent tracks - return (img.convert("RGB"), img.split()[3]) - - elif data == 3: # slope going up in -x direction - # tracks are sprites, in this case we are seeing the "side" of - # the sprite, so draw a line to make it beautiful. - img = Image.new("RGBA", (24,24), (38,92,255,0)) - ImageDraw.Draw(img).line([(11,11),(23,17)],fill=(164,164,164)) - # grey from track texture (exterior grey). - # the track doesn't start from image corners, be carefull drawing the line! - return (img.convert("RGB"), img.split()[3]) - - elif data == 4: # slope going up in -y direction - track = transform_image_slope(raw_straight,blockID) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, track, (0,0), track) - return (img.convert("RGB"), img.split()[3]) - - elif data == 5: # slope going up in +y direction - # same as "data == 3" - img = Image.new("RGBA", (24,24), (38,92,255,0)) - ImageDraw.Draw(img).line([(1,17),(12,11)],fill=(164,164,164)) - return (img.convert("RGB"), img.split()[3]) - - - else: # just in case - track = transform_image(raw_straight, blockID) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, track, (0,12), track) - - return (img.convert("RGB"), img.split()[3]) - - if blockID == 58: # crafting table - top = terrain_images[43] - side3 = terrain_images[43+16] - side4 = terrain_images[43+16+1] - - img = _build_full_block(top, None, None, side3, side4, None, 58) - return (img.convert("RGB"), img.split()[3]) - - if blockID == 59: # crops - raw_crop = terrain_images[88+data] - crop1 = transform_image(raw_crop, blockID) - crop2 = transform_image_side(raw_crop, blockID) - crop3 = crop2.transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, crop1, (0,12), crop1) - composite.alpha_over(img, crop2, (6,3), crop2) - composite.alpha_over(img, crop3, (6,3), crop3) - return (img.convert("RGB"), img.split()[3]) - - if blockID == 61: #furnace - top = transform_image(terrain_images[62]) - side1 = transform_image_side(terrain_images[45]) - side2 = transform_image_side(terrain_images[44]).transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - composite.alpha_over(img, side1, (0,6), side1) - composite.alpha_over(img, side2, (12,6), side2) - composite.alpha_over(img, top, (0,0), top) - return (img.convert("RGB"), img.split()[3]) - - if blockID in (86,91): # jack-o-lantern - top = transform_image(terrain_images[102]) - frontID = 119 if blockID == 86 else 120 - side1 = transform_image_side(terrain_images[frontID]) - side2 = transform_image_side(terrain_images[118]).transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - composite.alpha_over(img, side1, (0,6), side1) - composite.alpha_over(img, side2, (12,6), side2) - composite.alpha_over(img, top, (0,0), top) - return (img.convert("RGB"), img.split()[3]) - - if blockID == 62: # lit furnace - top = transform_image(terrain_images[62]) - side1 = transform_image_side(terrain_images[45]) - side2 = transform_image_side(terrain_images[45+16]).transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - composite.alpha_over(img, side1, (0,6), side1) - composite.alpha_over(img, side2, (12,6), side2) - composite.alpha_over(img, top, (0,0), top) - return (img.convert("RGB"), img.split()[3]) - - if blockID == 23: # dispenser - top = transform_image(terrain_images[62]) - side1 = transform_image_side(terrain_images[46]) - side2 = transform_image_side(terrain_images[45]).transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - composite.alpha_over(img, side1, (0,6), side1) - composite.alpha_over(img, side2, (12,6), side2) - composite.alpha_over(img, top, (0,0), top) - return (img.convert("RGB"), img.split()[3]) - - - - if blockID == 65: # ladder - raw_texture = terrain_images[83] - #print "ladder is facing: %d" % data - if data == 5: - # normally this ladder would be obsured by the block it's attached to - # but since ladders can apparently be placed on transparent blocks, we - # have to render this thing anyway. same for data == 2 - tex = transform_image_side(raw_texture) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, tex, (0,6), tex) - return (img.convert("RGB"), img.split()[3]) - if data == 2: - tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, tex, (12,6), tex) - return (img.convert("RGB"), img.split()[3]) - if data == 3: - tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, tex, (0,0), tex) - return (img.convert("RGB"), img.split()[3]) - if data == 4: - tex = transform_image_side(raw_texture) - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, tex, (12,0), tex) - return (img.convert("RGB"), img.split()[3]) - - if blockID in (64,71): #wooden door, or iron door - if data & 0x8 == 0x8: # top of the door - raw_door = terrain_images[81 if blockID == 64 else 82] - else: # bottom of the door - raw_door = terrain_images[97 if blockID == 64 else 98] - - # if you want to render all doors as closed, then force - # force swung to be False - if data & 0x4 == 0x4: - swung=True - else: - swung=False - - # mask out the high bits to figure out the orientation - img = Image.new("RGBA", (24,24), (38,92,255,0)) - if (data & 0x03) == 0: - if not swung: - tex = transform_image_side(raw_door) - composite.alpha_over(img, tex, (0,6), tex) - else: - # flip first to set the doornob on the correct side - tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) - tex = tex.transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, tex, (0,0), tex) - - if (data & 0x03) == 1: - if not swung: - tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, tex, (0,0), tex) - else: - tex = transform_image_side(raw_door) - composite.alpha_over(img, tex, (12,0), tex) - - if (data & 0x03) == 2: - if not swung: - tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) - composite.alpha_over(img, tex, (12,0), tex) - else: - tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, tex, (12,6), tex) - - if (data & 0x03) == 3: - if not swung: - tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)).transpose(Image.FLIP_LEFT_RIGHT) - composite.alpha_over(img, tex, (12,6), tex) - else: - tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) - composite.alpha_over(img, tex, (0,6), tex) - - return (img.convert("RGB"), img.split()[3]) + # TODO ladders, stairs, levers, buttons, and signs + # all need to behandled here (and in chunkpy) if blockID == 2: # grass top = tintTexture(terrain_images[0],(115,175,71)) img = _build_block(top, terrain_images[3], 2) return (img.convert("RGB"), img.split()[3]) - - if blockID == 51: # fire - firetexture = _load_image("fire.png") - side1 = transform_image_side(firetexture) - side2 = transform_image_side(firetexture).transpose(Image.FLIP_LEFT_RIGHT) - - img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, side1, (12,0), side1) - composite.alpha_over(img, side2, (0,0), side2) - composite.alpha_over(img, side1, (0,6), side1) - composite.alpha_over(img, side2, (12,6), side2) + if blockID == 9: # spring water, flowing water and waterfall water + + watertexture = _load_image("water.png") - return (img.convert("RGB"), img.split()[3]) - - if blockID == 18: # leaves - t = tintTexture(terrain_images[52], (37, 118, 25)) - img = _build_block(t, t, 18) - return (img.convert("RGB"), img.split()[3]) + if (data & 0b10000) == 16: + top = watertexture + + else: top = None + + if (data & 0b0001) == 1: + side1 = watertexture # top left + else: side1 = None + if (data & 0b1000) == 8: + side2 = watertexture # top right + else: side2 = None + + if (data & 0b0010) == 2: + side3 = watertexture # bottom left + else: side3 = None + + if (data & 0b0100) == 4: + side4 = watertexture # bottom right + else: side4 = None + + img = _build_full_block(top,side1,side2,side3,side4) + + return (img.convert("RGB"),img.split()[3]) + + if blockID == 17: # wood: normal, birch and pines top = terrain_images[21] if data == 0: @@ -679,7 +482,26 @@ def generate_special_texture(blockID, data): side = terrain_images[117] img = _build_block(top, side, 17) return (img.convert("RGB"), img.split()[3]) + + + if blockID == 18: # leaves + t = tintTexture(terrain_images[52], (37, 118, 25)) + img = _build_block(t, t, 18) + return (img.convert("RGB"), img.split()[3]) + if blockID == 23: # dispenser + top = transform_image(terrain_images[62]) + side1 = transform_image_side(terrain_images[46]) + side2 = transform_image_side(terrain_images[45]).transpose(Image.FLIP_LEFT_RIGHT) + + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + composite.alpha_over(img, side1, (0,6), side1) + composite.alpha_over(img, side2, (12,6), side2) + composite.alpha_over(img, top, (0,0), top) + return (img.convert("RGB"), img.split()[3]) + + if blockID == 35: # wool if data == 0: # white top = side = terrain_images[64] @@ -746,7 +568,370 @@ def generate_special_texture(blockID, data): img = _build_block(top, side, 35) return (img.convert("RGB"), img.split()[3]) + + if blockID in (43,44): # slab and double-slab + if data == 0: # stone slab + top = terrain_images[6] + side = terrain_images[5] + img = _build_block(top, side, blockID) + return (img.convert("RGB"), img.split()[3]) + + if data == 1: # stone slab + top = terrain_images[176] + side = terrain_images[192] + img = _build_block(top, side, blockID) + return (img.convert("RGB"), img.split()[3]) + + if data == 2: # wooden slab + top = side = terrain_images[4] + img = _build_block(top, side, blockID) + return (img.convert("RGB"), img.split()[3]) + + if data == 3: # cobblestone slab + top = side = terrain_images[16] + img = _build_block(top, side, blockID) + return (img.convert("RGB"), img.split()[3]) + + + if blockID in (50,75,76): # torch, off redstone torch, on redstone torch + + # choose the proper texture + if blockID == 50: # torch + small = terrain_images[80] + elif blockID == 75: # off redstone torch + small = terrain_images[115] + else: # on redstone torch + small = terrain_images[99] + + # compose a torch bigger than the normal + # (better for doing transformations) + torch = Image.new("RGBA", (16,16), (38,92,255,0)) + composite.alpha_over(torch,small,(-4,-3)) + composite.alpha_over(torch,small,(-5,-2)) + composite.alpha_over(torch,small,(-3,-2)) + + # angle of inclination of the texture + rotation = 15 + + if data == 1: # pointing south + torch = torch.rotate(-rotation, Image.NEAREST) # nearest filter is more nitid. + img = _build_full_block(None, None, None, torch, None, None, blockID) + + elif data == 2: # pointing north + torch = torch.rotate(rotation, Image.NEAREST) + img = _build_full_block(None, None, torch, None, None, None, blockID) + + elif data == 3: # pointing west + torch = torch.rotate(rotation, Image.NEAREST) + img = _build_full_block(None, torch, None, None, None, None, blockID) + + elif data == 4: # pointing east + torch = torch.rotate(-rotation, Image.NEAREST) + img = _build_full_block(None, None, None, None, torch, None, blockID) + + elif data == 5: # standing on the floor + # compose a "3d torch". + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + small_crop = small.crop((2,2,14,14)) + slice = small_crop.copy() + ImageDraw.Draw(slice).rectangle((6,0,12,12),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(slice).rectangle((0,0,4,12),outline=(0,0,0,0),fill=(0,0,0,0)) + + composite.alpha_over(img, slice, (6,4)) + composite.alpha_over(img, small_crop, (5,5)) + composite.alpha_over(img, small_crop, (6,5)) + composite.alpha_over(img, slice, (6,6)) + + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 51: # fire + firetexture = _load_image("fire.png") + side1 = transform_image_side(firetexture) + side2 = transform_image_side(firetexture).transpose(Image.FLIP_LEFT_RIGHT) + + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + composite.alpha_over(img, side1, (12,0), side1) + composite.alpha_over(img, side2, (0,0), side2) + + composite.alpha_over(img, side1, (0,6), side1) + composite.alpha_over(img, side2, (12,6), side2) + + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 55: # redstone wire + + if data & 0b1000000 == 64: # powered redstone wire + redstone_wire_t = terrain_images[165] + redstone_wire_t = tintTexture(redstone_wire_t,(255,0,0)) + + redstone_cross_t = terrain_images[164] + redstone_cross_t = tintTexture(redstone_cross_t,(255,0,0)) + + + else: # unpowered redstone wire + redstone_wire_t = terrain_images[165] + redstone_wire_t = tintTexture(redstone_wire_t,(48,0,0)) + + redstone_cross_t = terrain_images[164] + redstone_cross_t = tintTexture(redstone_cross_t,(48,0,0)) + + # generate an image per redstone direction + branch_top_left = redstone_cross_t.copy() + ImageDraw.Draw(branch_top_left).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_top_left).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_top_left).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + branch_top_right = redstone_cross_t.copy() + ImageDraw.Draw(branch_top_right).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_top_right).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_top_right).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + branch_bottom_right = redstone_cross_t.copy() + ImageDraw.Draw(branch_bottom_right).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_bottom_right).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_bottom_right).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + branch_bottom_left = redstone_cross_t.copy() + ImageDraw.Draw(branch_bottom_left).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_bottom_left).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(branch_bottom_left).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) + + # generate the bottom texture + if data & 0b111111 == 0: + bottom = redstone_cross_t.copy() + + elif data & 0b1111 == 10: #= 0b1010 redstone wire in the x direction + bottom = redstone_wire_t.copy() + + elif data & 0b1111 == 5: #= 0b0101 redstone wire in the y direction + bottom = redstone_wire_t.copy().rotate(90) + + else: + bottom = Image.new("RGBA", (16,16), (38,92,255,0)) + if (data & 0b0001) == 1: + composite.alpha_over(bottom,branch_top_left) + + if (data & 0b1000) == 8: + composite.alpha_over(bottom,branch_top_right) + + if (data & 0b0010) == 2: + composite.alpha_over(bottom,branch_bottom_left) + + if (data & 0b0100) == 4: + composite.alpha_over(bottom,branch_bottom_right) + + # check for going up redstone wire + if data & 0b100000 == 32: + side1 = redstone_wire_t.rotate(90) + else: + side1 = None + + if data & 0b010000 == 16: + side2 = redstone_wire_t.rotate(90) + else: + side2 = None + + img = _build_full_block(None,side1,side2,None,None,bottom) + + return (img.convert("RGB"),img.split()[3]) + + + if blockID == 58: # crafting table + top = terrain_images[43] + side3 = terrain_images[43+16] + side4 = terrain_images[43+16+1] + + img = _build_full_block(top, None, None, side3, side4, None, 58) + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 59: # crops + raw_crop = terrain_images[88+data] + crop1 = transform_image(raw_crop, blockID) + crop2 = transform_image_side(raw_crop, blockID) + crop3 = crop2.transpose(Image.FLIP_LEFT_RIGHT) + + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, crop1, (0,12), crop1) + composite.alpha_over(img, crop2, (6,3), crop2) + composite.alpha_over(img, crop3, (6,3), crop3) + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 61: #furnace + top = transform_image(terrain_images[62]) + side1 = transform_image_side(terrain_images[45]) + side2 = transform_image_side(terrain_images[44]).transpose(Image.FLIP_LEFT_RIGHT) + + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + composite.alpha_over(img, side1, (0,6), side1) + composite.alpha_over(img, side2, (12,6), side2) + composite.alpha_over(img, top, (0,0), top) + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 62: # lit furnace + top = transform_image(terrain_images[62]) + side1 = transform_image_side(terrain_images[45]) + side2 = transform_image_side(terrain_images[45+16]).transpose(Image.FLIP_LEFT_RIGHT) + + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + composite.alpha_over(img, side1, (0,6), side1) + composite.alpha_over(img, side2, (12,6), side2) + composite.alpha_over(img, top, (0,0), top) + return (img.convert("RGB"), img.split()[3]) + + + if blockID in (64,71): #wooden door, or iron door + if data & 0x8 == 0x8: # top of the door + raw_door = terrain_images[81 if blockID == 64 else 82] + else: # bottom of the door + raw_door = terrain_images[97 if blockID == 64 else 98] + + # if you want to render all doors as closed, then force + # force swung to be False + if data & 0x4 == 0x4: + swung=True + else: + swung=False + + # mask out the high bits to figure out the orientation + img = Image.new("RGBA", (24,24), (38,92,255,0)) + if (data & 0x03) == 0: + if not swung: + tex = transform_image_side(raw_door) + composite.alpha_over(img, tex, (0,6), tex) + else: + # flip first to set the doornob on the correct side + tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) + tex = tex.transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, tex, (0,0), tex) + + if (data & 0x03) == 1: + if not swung: + tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, tex, (0,0), tex) + else: + tex = transform_image_side(raw_door) + composite.alpha_over(img, tex, (12,0), tex) + + if (data & 0x03) == 2: + if not swung: + tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) + composite.alpha_over(img, tex, (12,0), tex) + else: + tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, tex, (12,6), tex) + + if (data & 0x03) == 3: + if not swung: + tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)).transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, tex, (12,6), tex) + else: + tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) + composite.alpha_over(img, tex, (0,6), tex) + + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 65: # ladder + raw_texture = terrain_images[83] + #print "ladder is facing: %d" % data + if data == 5: + # normally this ladder would be obsured by the block it's attached to + # but since ladders can apparently be placed on transparent blocks, we + # have to render this thing anyway. same for data == 2 + tex = transform_image_side(raw_texture) + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, tex, (0,6), tex) + return (img.convert("RGB"), img.split()[3]) + if data == 2: + tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, tex, (12,6), tex) + return (img.convert("RGB"), img.split()[3]) + if data == 3: + tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, tex, (0,0), tex) + return (img.convert("RGB"), img.split()[3]) + if data == 4: + tex = transform_image_side(raw_texture) + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, tex, (12,0), tex) + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 66: # minetrack: + + raw_straight = terrain_images[128] + raw_corner = terrain_images[112] + + ## use transform_image to scale and shear + if data == 0: + track = transform_image(raw_straight, blockID) + elif data == 6: + track = transform_image(raw_corner, blockID) + elif data == 7: + track = transform_image(raw_corner.rotate(270), blockID) + elif data == 8: + # flip + track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM).rotate(90), + blockID) + elif data == 9: + track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM), + blockID) + elif data == 1: + track = transform_image(raw_straight.rotate(90), blockID) + + #slopes + elif data == 2: # slope going up in +x direction + track = transform_image_slope(raw_straight,blockID) + track = track.transpose(Image.FLIP_LEFT_RIGHT) + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, track, (2,0), track) + # the 2 pixels move is needed to fit with the adjacent tracks + return (img.convert("RGB"), img.split()[3]) + + elif data == 3: # slope going up in -x direction + # tracks are sprites, in this case we are seeing the "side" of + # the sprite, so draw a line to make it beautiful. + img = Image.new("RGBA", (24,24), (38,92,255,0)) + ImageDraw.Draw(img).line([(11,11),(23,17)],fill=(164,164,164)) + # grey from track texture (exterior grey). + # the track doesn't start from image corners, be carefull drawing the line! + return (img.convert("RGB"), img.split()[3]) + + elif data == 4: # slope going up in -y direction + track = transform_image_slope(raw_straight,blockID) + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, track, (0,0), track) + return (img.convert("RGB"), img.split()[3]) + + elif data == 5: # slope going up in +y direction + # same as "data == 3" + img = Image.new("RGBA", (24,24), (38,92,255,0)) + ImageDraw.Draw(img).line([(1,17),(12,11)],fill=(164,164,164)) + return (img.convert("RGB"), img.split()[3]) + + + else: # just in case + track = transform_image(raw_straight, blockID) + + img = Image.new("RGBA", (24,24), (38,92,255,0)) + composite.alpha_over(img, track, (0,12), track) + + return (img.convert("RGB"), img.split()[3]) + + + if blockID == 85: # fences # create needed images for Big stick fence raw_texture = terrain_images[4] @@ -843,137 +1028,18 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"),img.split()[3]) - if blockID in (43,44): # slab and double-slab - - if data == 0: # stone slab - top = terrain_images[6] - side = terrain_images[5] - img = _build_block(top, side, blockID) - return (img.convert("RGB"), img.split()[3]) - - if data == 1: # stone slab - top = terrain_images[176] - side = terrain_images[192] - img = _build_block(top, side, blockID) - return (img.convert("RGB"), img.split()[3]) - - if data == 2: # wooden slab - top = side = terrain_images[4] - img = _build_block(top, side, blockID) - return (img.convert("RGB"), img.split()[3]) - - if data == 3: # cobblestone slab - top = side = terrain_images[16] - img = _build_block(top, side, blockID) - return (img.convert("RGB"), img.split()[3]) + if blockID in (86,91): # pumpkins, jack-o-lantern + top = transform_image(terrain_images[102]) + frontID = 119 if blockID == 86 else 120 + side1 = transform_image_side(terrain_images[frontID]) + side2 = transform_image_side(terrain_images[118]).transpose(Image.FLIP_LEFT_RIGHT) + img = Image.new("RGBA", (24,24), (38,92,255,0)) - if blockID == 9: # spring water, flowing water and waterfall water - - watertexture = _load_image("water.png") - - if (data & 0b10000) == 16: - top = watertexture - - else: top = None - - if (data & 0b0001) == 1: - side1 = watertexture # top left - else: side1 = None - - if (data & 0b1000) == 8: - side2 = watertexture # top right - else: side2 = None - - if (data & 0b0010) == 2: - side3 = watertexture # bottom left - else: side3 = None - - if (data & 0b0100) == 4: - side4 = watertexture # bottom right - else: side4 = None - - img = _build_full_block(top,side1,side2,side3,side4) - - return (img.convert("RGB"),img.split()[3]) - - - if blockID == 55: # redstone wire - - if data & 0b1000000 == 64: # powered redstone wire - redstone_wire_t = terrain_images[165] - redstone_wire_t = tintTexture(redstone_wire_t,(255,0,0)) - - redstone_cross_t = terrain_images[164] - redstone_cross_t = tintTexture(redstone_cross_t,(255,0,0)) - - - else: # unpowered redstone wire - redstone_wire_t = terrain_images[165] - redstone_wire_t = tintTexture(redstone_wire_t,(48,0,0)) - - redstone_cross_t = terrain_images[164] - redstone_cross_t = tintTexture(redstone_cross_t,(48,0,0)) - - # generate an image per redstone direction - branch_top_left = redstone_cross_t.copy() - ImageDraw.Draw(branch_top_left).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_top_left).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_top_left).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - branch_top_right = redstone_cross_t.copy() - ImageDraw.Draw(branch_top_right).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_top_right).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_top_right).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - branch_bottom_right = redstone_cross_t.copy() - ImageDraw.Draw(branch_bottom_right).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_bottom_right).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_bottom_right).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - branch_bottom_left = redstone_cross_t.copy() - ImageDraw.Draw(branch_bottom_left).rectangle((0,0,15,4),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_bottom_left).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(branch_bottom_left).rectangle((0,11,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - # generate the bottom texture - if data & 0b111111 == 0: - bottom = redstone_cross_t.copy() - - elif data & 0b1111 == 10: #= 0b1010 redstone wire in the x direction - bottom = redstone_wire_t.copy() - - elif data & 0b1111 == 5: #= 0b0101 redstone wire in the y direction - bottom = redstone_wire_t.copy().rotate(90) - - else: - bottom = Image.new("RGBA", (16,16), (38,92,255,0)) - if (data & 0b0001) == 1: - composite.alpha_over(bottom,branch_top_left) - - if (data & 0b1000) == 8: - composite.alpha_over(bottom,branch_top_right) - - if (data & 0b0010) == 2: - composite.alpha_over(bottom,branch_bottom_left) - - if (data & 0b0100) == 4: - composite.alpha_over(bottom,branch_bottom_right) - - # check for going up redstone wire - if data & 0b100000 == 32: - side1 = redstone_wire_t.rotate(90) - else: - side1 = None - - if data & 0b010000 == 16: - side2 = redstone_wire_t.rotate(90) - else: - side2 = None - - img = _build_full_block(None,side1,side2,None,None,bottom) - - return (img.convert("RGB"),img.split()[3]) + composite.alpha_over(img, side1, (0,6), side1) + composite.alpha_over(img, side2, (12,6), side2) + composite.alpha_over(img, top, (0,0), top) + return (img.convert("RGB"), img.split()[3]) if blockID == 92: # cake! (without bites, at the moment) @@ -1002,58 +1068,6 @@ def generate_special_texture(blockID, data): #~ composite.alpha_over(img, top, (0,2), top) return (img.convert("RGB"), img.split()[3]) - if blockID in (50,75,76): # torch, off redstone torch, on redstone torch - - # choose the proper texture - if blockID == 50: # torch - small = terrain_images[80] - elif blockID == 75: # off redstone torch - small = terrain_images[115] - else: # on redstone torch - small = terrain_images[99] - - # compose a torch bigger than the normal - # (better for doing transformations) - torch = Image.new("RGBA", (16,16), (38,92,255,0)) - composite.alpha_over(torch,small,(-4,-3)) - composite.alpha_over(torch,small,(-5,-2)) - composite.alpha_over(torch,small,(-3,-2)) - - # angle of inclination of the texture - rotation = 15 - - if data == 1: # pointing south - torch = torch.rotate(-rotation, Image.NEAREST) # nearest filter is more nitid. - img = _build_full_block(None, None, None, torch, None, None, blockID) - - elif data == 2: # pointing north - torch = torch.rotate(rotation, Image.NEAREST) - img = _build_full_block(None, None, torch, None, None, None, blockID) - - elif data == 3: # pointing west - torch = torch.rotate(rotation, Image.NEAREST) - img = _build_full_block(None, torch, None, None, None, None, blockID) - - elif data == 4: # pointing east - torch = torch.rotate(-rotation, Image.NEAREST) - img = _build_full_block(None, None, None, None, torch, None, blockID) - - elif data == 5: # standing on the floor - # compose a "3d torch". - img = Image.new("RGBA", (24,24), (38,92,255,0)) - - small_crop = small.crop((2,2,14,14)) - slice = small_crop.copy() - ImageDraw.Draw(slice).rectangle((6,0,12,12),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(slice).rectangle((0,0,4,12),outline=(0,0,0,0),fill=(0,0,0,0)) - - composite.alpha_over(img, slice, (6,4)) - composite.alpha_over(img, small_crop, (5,5)) - composite.alpha_over(img, small_crop, (6,5)) - composite.alpha_over(img, slice, (6,6)) - - return (img.convert("RGB"), img.split()[3]) - return None @@ -1127,46 +1141,49 @@ def getBiomeData(worlddir, chunkX, chunkY): # This set holds block ids that require special pre-computing. These are typically # things that require ancillary data to render properly (i.e. ladder plus orientation) +# A good source of information is: +# http://www.minecraftwiki.net/wiki/Data_values +# (when adding new blocks here and in generate_special_textures, +# please, keep the ascending order of blockid value) -special_blocks = set([66,59,61,62, 65,64,71,91,86,2,18,85,17,23,35,51,43,44,9,55,50,58,75,76,92]) +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]) # this is a map of special blockIDs to a list of all # possible values for ancillary data that it might have. + special_map = {} -special_map[66] = range(10) # minecrart tracks -special_map[59] = range(8) # crops -special_map[61] = range(6) # furnace -special_map[62] = range(6) # burning furnace -special_map[65] = (2,3,4,5) # ladder -special_map[64] = range(16) # wooden door -special_map[71] = range(16) # iron door -special_map[91] = range(5) # jack-o-lantern -special_map[86] = range(5) # pumpkin -special_map[85] = range(17) # fences + +special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values. special_map[17] = range(4) # wood: normal, birch and pine -special_map[23] = range(6) # dispensers +special_map[23] = range(6) # dispensers, orientation special_map[35] = range(16) # wool, colored and white -special_map[51] = range(16) # fire special_map[43] = range(4) # stone, sandstone, wooden and cobblestone double-slab special_map[44] = range(4) # stone, sandstone, wooden and cobblestone slab -special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values. -special_map[55] = range(128) # redstone wire -special_map[58] = (0,) # crafting table +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[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[66] = range(10) # minecrart tracks, orientation, slope +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 +special_map[85] = range(17) # fences, all the possible combination +special_map[86] = range(5) # pumpkin, orientation (not implemented) +special_map[91] = range(5) # jack-o-lantern, orientation (not implemented) special_map[92] = range(6) # cake! -special_map[50] = (1,2,3,4,5) # torch -special_map[75] = (1,2,3,4,5) # off redstone torch -special_map[76] = (1,2,3,4,5) # on redstone torch -# apparently pumpkins and jack-o-lanterns have ancillary data, but it's unknown -# what that data represents. For now, assume that the range for data is 0 to 5 -# like torches -special_map[2] = (0,) # grass -special_map[18] = range(16) # leaves -# grass and leaves are now graysacle in terrain.png +# grass and leaves are graysacle in terrain.png # we treat them as special so we can manually tint them # it is unknown how the specific tint (biomes) is calculated +special_map[2] = (0,) # grass +special_map[18] = range(16) # leaves, birch, normal or pine leaves (not implemented) -# leaves have ancilary data, but its meaning is unknown (age perhaps?) specialblockmap = {} From 81fd7d12ed0eb49858564d4bbe0d818254cd4086 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Fri, 1 Apr 2011 20:40:23 +0200 Subject: [PATCH 151/213] Add compatibility for wildgrass mod --- textures.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/textures.py b/textures.py index 13adb12..988bff2 100644 --- a/textures.py +++ b/textures.py @@ -1181,7 +1181,10 @@ special_map[92] = range(6) # cake! # grass and leaves are graysacle in terrain.png # we treat them as special so we can manually tint them # it is unknown how the specific tint (biomes) is calculated -special_map[2] = (0,) # grass +special_map[2] = range(11) # grass, grass has not ancildata but is used + # in the mod WildGrass, and this small fix + # shows the map as expected, and is harmless + # for normal maps special_map[18] = range(16) # leaves, birch, normal or pine leaves (not implemented) From a7714088c5bf3dc6382327bb69cf57373d729f2c Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Fri, 1 Apr 2011 23:21:01 +0200 Subject: [PATCH 152/213] another comment --- textures.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/textures.py b/textures.py index 988bff2..53eddab 100644 --- a/textures.py +++ b/textures.py @@ -1144,7 +1144,8 @@ def getBiomeData(worlddir, chunkX, chunkY): # A good source of information is: # http://www.minecraftwiki.net/wiki/Data_values # (when adding new blocks here and in generate_special_textures, -# please, keep the ascending order of blockid value) +# please, if possible, keep the ascending order of blockid value +# in special_map and in the generate_special_texture function) 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]) From 7463183b9c21491fd35612461f628a8906a57c78 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Fri, 1 Apr 2011 23:33:06 +0200 Subject: [PATCH 153/213] Remove duplicated comment... --- textures.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/textures.py b/textures.py index 53eddab..a94030c 100644 --- a/textures.py +++ b/textures.py @@ -1144,8 +1144,7 @@ def getBiomeData(worlddir, chunkX, chunkY): # A good source of information is: # http://www.minecraftwiki.net/wiki/Data_values # (when adding new blocks here and in generate_special_textures, -# please, if possible, keep the ascending order of blockid value -# in special_map and in the generate_special_texture function) +# 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]) From 3d78e21af79d680c384a2872f362ac9aa9bf5c12 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Fri, 1 Apr 2011 21:47:50 -0400 Subject: [PATCH 154/213] fixed contrib/testRender.py crashing when -l isn't specified --- contrib/testRender.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/testRender.py b/contrib/testRender.py index 7646dad..2ea0e87 100644 --- a/contrib/testRender.py +++ b/contrib/testRender.py @@ -131,7 +131,8 @@ try: print "... done" stats = get_stats(timelist) print stats - log.write("%s %s\n" % (commit, repr(stats))) + if log: + log.write("%s %s\n" % (commit, repr(stats))) except CalledProcessError, e: if options.fatal_errors: print From a30e4155978e719fede8960f2d49cfdaa1127de8 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Thu, 31 Mar 2011 22:37:18 -0400 Subject: [PATCH 155/213] Render edge lines --- setup.py | 1 + src/Draw.c | 902 ++++++++++++++++++++++++++++++++++++++++ src/rendermode-normal.c | 32 ++ 3 files changed, 935 insertions(+) create mode 100644 src/Draw.c diff --git a/setup.py b/setup.py index 62cc86e..fba44d4 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ except AttributeError: c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/endian.c'] c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c', 'src/rendermode-night.c', 'src/rendermode-spawn.c'] +c_overviewer_files += ['src/Draw.c'] c_overviewer_includes = ['src/overviewer.h', 'src/rendermodes.h'] setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], depends=c_overviewer_includes, extra_link_args=[])) # tell build_ext to build the extension in-place diff --git a/src/Draw.c b/src/Draw.c new file mode 100644 index 0000000..fca0518 --- /dev/null +++ b/src/Draw.c @@ -0,0 +1,902 @@ +/* + * The Python Imaging Library. + * $Id$ + * + * a simple drawing package for the Imaging library + * + * history: + * 1996-04-13 fl Created. + * 1996-04-30 fl Added transforms and polygon support. + * 1996-08-12 fl Added filled polygons. + * 1996-11-05 fl Fixed float/int confusion in polygon filler + * 1997-07-04 fl Support 32-bit images (C++ would have been nice) + * 1998-09-09 fl Eliminated qsort casts; improved rectangle clipping + * 1998-09-10 fl Fixed fill rectangle to include lower edge (!) + * 1998-12-29 fl Added arc, chord, and pieslice primitives + * 1999-01-10 fl Added some level 2 ("arrow") stuff (experimental) + * 1999-02-06 fl Added bitmap primitive + * 1999-07-26 fl Eliminated a compiler warning + * 1999-07-31 fl Pass ink as void* instead of int + * 2002-12-10 fl Added experimental RGBA-on-RGB drawing + * 2004-09-04 fl Support simple wide lines (no joins) + * 2005-05-25 fl Fixed line width calculation + * 2011-04-01 Modified for use in Minecraft-Overviewer + * + * Copyright (c) 1996-2006 by Fredrik Lundh + * Copyright (c) 1997-2006 by Secret Labs AB. + * + * This file is part of the Python Imaging Library + * + * By obtaining, using, and/or copying this software and/or its associated + * documentation, you agree that you have read, understood, and will comply + * with the following terms and conditions: + * + * Permission to use, copy, modify, and distribute this software and its + * associated documentation for any purpose and without fee is hereby granted, + * provided that the above copyright notice appears in all copies, and that + * both that copyright notice and this permission notice appear in supporting + * documentation, and that the name of Secret Labs AB or the author not be used + * in advertising or publicity pertaining to distribution of the software + * without specific, written prior permission. + * + * SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS + * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. + * IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR BE LIABLE FOR ANY SPECIAL, + * INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + * + */ + +/* FIXME: support fill/outline attribute for all filled shapes */ +/* FIXME: support zero-winding fill */ +/* FIXME: add drawing context, support affine transforms */ +/* FIXME: support clip window (and mask?) */ + +#include "Imaging.h" + +#include + +#define CEIL(v) (int) ceil(v) +#define FLOOR(v) ((v) >= 0.0 ? (int) (v) : (int) floor(v)) + +#define INK8(ink) (*(UINT8*)ink) +#define INK32(ink) (*(INT32*)ink) + +/* like (a * b + 127) / 255), but much faster on most platforms */ +#define MULDIV255(a, b, tmp)\ + (tmp = (a) * (b) + 128, ((((tmp) >> 8) + (tmp)) >> 8)) + +#define BLEND(mask, in1, in2, tmp1, tmp2)\ + (MULDIV255(in1, 255 - mask, tmp1) + MULDIV255(in2, mask, tmp2)) + +/* -------------------------------------------------------------------- */ +/* Primitives */ +/* -------------------------------------------------------------------- */ + +typedef struct { + /* edge descriptor for polygon engine */ + int d; + int x0, y0; + int xmin, ymin, xmax, ymax; + float dx; +} Edge; + +static inline void +point8(Imaging im, int x, int y, int ink) +{ + if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) + im->image8[y][x] = (UINT8) ink; +} + +static inline void +point32(Imaging im, int x, int y, int ink) +{ + if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) + im->image32[y][x] = ink; +} + +static inline void +point32rgba(Imaging im, int x, int y, int ink) +{ + unsigned int tmp1, tmp2; + + if (x >= 0 && x < im->xsize && y >= 0 && y < im->ysize) { + UINT8* out = (UINT8*) im->image[y]+x*4; + UINT8* in = (UINT8*) &ink; + out[0] = BLEND(in[3], out[0], in[0], tmp1, tmp2); + out[1] = BLEND(in[3], out[1], in[1], tmp1, tmp2); + out[2] = BLEND(in[3], out[2], in[2], tmp1, tmp2); + } +} + +static inline void +hline8(Imaging im, int x0, int y0, int x1, int ink) +{ + int tmp; + + if (y0 >= 0 && y0 < im->ysize) { + if (x0 > x1) + tmp = x0, x0 = x1, x1 = tmp; + if (x0 < 0) + x0 = 0; + else if (x0 >= im->xsize) + return; + if (x1 < 0) + return; + else if (x1 >= im->xsize) + x1 = im->xsize-1; + if (x0 <= x1) + memset(im->image8[y0] + x0, (UINT8) ink, x1 - x0 + 1); + } +} + +static inline void +hline32(Imaging im, int x0, int y0, int x1, int ink) +{ + int tmp; + INT32* p; + + if (y0 >= 0 && y0 < im->ysize) { + if (x0 > x1) + tmp = x0, x0 = x1, x1 = tmp; + if (x0 < 0) + x0 = 0; + else if (x0 >= im->xsize) + return; + if (x1 < 0) + return; + else if (x1 >= im->xsize) + x1 = im->xsize-1; + p = im->image32[y0]; + while (x0 <= x1) + p[x0++] = ink; + } +} + +static inline void +hline32rgba(Imaging im, int x0, int y0, int x1, int ink) +{ + int tmp; + unsigned int tmp1, tmp2; + + if (y0 >= 0 && y0 < im->ysize) { + if (x0 > x1) + tmp = x0, x0 = x1, x1 = tmp; + if (x0 < 0) + x0 = 0; + else if (x0 >= im->xsize) + return; + if (x1 < 0) + return; + else if (x1 >= im->xsize) + x1 = im->xsize-1; + if (x0 <= x1) { + UINT8* out = (UINT8*) im->image[y0]+x0*4; + UINT8* in = (UINT8*) &ink; + while (x0 <= x1) { + out[0] = BLEND(in[3], out[0], in[0], tmp1, tmp2); + out[1] = BLEND(in[3], out[1], in[1], tmp1, tmp2); + out[2] = BLEND(in[3], out[2], in[2], tmp1, tmp2); + x0++; out += 4; + } + } + } +} + +static inline void +line8(Imaging im, int x0, int y0, int x1, int y1, int ink) +{ + int i, n, e; + int dx, dy; + int xs, ys; + + /* normalize coordinates */ + dx = x1-x0; + if (dx < 0) + dx = -dx, xs = -1; + else + xs = 1; + dy = y1-y0; + if (dy < 0) + dy = -dy, ys = -1; + else + ys = 1; + + n = (dx > dy) ? dx : dy; + + if (dx == 0) + + /* vertical */ + for (i = 0; i < dy; i++) { + point8(im, x0, y0, ink); + y0 += ys; + } + + else if (dy == 0) + + /* horizontal */ + for (i = 0; i < dx; i++) { + point8(im, x0, y0, ink); + x0 += xs; + } + + else if (dx > dy) { + + /* bresenham, horizontal slope */ + n = dx; + dy += dy; + e = dy - dx; + dx += dx; + + for (i = 0; i < n; i++) { + point8(im, x0, y0, ink); + if (e >= 0) { + y0 += ys; + e -= dx; + } + e += dy; + x0 += xs; + } + + } else { + + /* bresenham, vertical slope */ + n = dy; + dx += dx; + e = dx - dy; + dy += dy; + + for (i = 0; i < n; i++) { + point8(im, x0, y0, ink); + if (e >= 0) { + x0 += xs; + e -= dy; + } + e += dx; + y0 += ys; + } + + } +} + +static inline void +line32(Imaging im, int x0, int y0, int x1, int y1, int ink) +{ + int i, n, e; + int dx, dy; + int xs, ys; + + /* normalize coordinates */ + dx = x1-x0; + if (dx < 0) + dx = -dx, xs = -1; + else + xs = 1; + dy = y1-y0; + if (dy < 0) + dy = -dy, ys = -1; + else + ys = 1; + + n = (dx > dy) ? dx : dy; + + if (dx == 0) + + /* vertical */ + for (i = 0; i < dy; i++) { + point32(im, x0, y0, ink); + y0 += ys; + } + + else if (dy == 0) + + /* horizontal */ + for (i = 0; i < dx; i++) { + point32(im, x0, y0, ink); + x0 += xs; + } + + else if (dx > dy) { + + /* bresenham, horizontal slope */ + n = dx; + dy += dy; + e = dy - dx; + dx += dx; + + for (i = 0; i < n; i++) { + point32(im, x0, y0, ink); + if (e >= 0) { + y0 += ys; + e -= dx; + } + e += dy; + x0 += xs; + } + + } else { + + /* bresenham, vertical slope */ + n = dy; + dx += dx; + e = dx - dy; + dy += dy; + + for (i = 0; i < n; i++) { + point32(im, x0, y0, ink); + if (e >= 0) { + x0 += xs; + e -= dy; + } + e += dx; + y0 += ys; + } + + } +} + +static inline void +line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink) +{ + int i, n, e; + int dx, dy; + int xs, ys; + + /* normalize coordinates */ + dx = x1-x0; + if (dx < 0) + dx = -dx, xs = -1; + else + xs = 1; + dy = y1-y0; + if (dy < 0) + dy = -dy, ys = -1; + else + ys = 1; + + n = (dx > dy) ? dx : dy; + + if (dx == 0) + + /* vertical */ + for (i = 0; i < dy; i++) { + point32rgba(im, x0, y0, ink); + y0 += ys; + } + + else if (dy == 0) + + /* horizontal */ + for (i = 0; i < dx; i++) { + point32rgba(im, x0, y0, ink); + x0 += xs; + } + + else if (dx > dy) { + + /* bresenham, horizontal slope */ + n = dx; + dy += dy; + e = dy - dx; + dx += dx; + + for (i = 0; i < n; i++) { + point32rgba(im, x0, y0, ink); + if (e >= 0) { + y0 += ys; + e -= dx; + } + e += dy; + x0 += xs; + } + + } else { + + /* bresenham, vertical slope */ + n = dy; + dx += dx; + e = dx - dy; + dy += dy; + + for (i = 0; i < n; i++) { + point32rgba(im, x0, y0, ink); + if (e >= 0) { + x0 += xs; + e -= dy; + } + e += dx; + y0 += ys; + } + + } +} + +static int +x_cmp(const void *x0, const void *x1) +{ + float diff = *((float*)x0) - *((float*)x1); + if (diff < 0) + return -1; + else if (diff > 0) + return 1; + else + return 0; +} + +static inline int +polygon8(Imaging im, int n, Edge *e, int ink, int eofill) +{ + int i, j; + float *xx; + int ymin, ymax; + float y; + + if (n <= 0) + return 0; + + /* Find upper and lower polygon boundary (within image) */ + + ymin = e[0].ymin; + ymax = e[0].ymax; + for (i = 1; i < n; i++) { + if (e[i].ymin < ymin) ymin = e[i].ymin; + if (e[i].ymax > ymax) ymax = e[i].ymax; + } + + if (ymin < 0) + ymin = 0; + if (ymax >= im->ysize) + ymax = im->ysize-1; + + /* Process polygon edges */ + + xx = malloc(n * sizeof(float)); + if (!xx) + return -1; + + for (;ymin <= ymax; ymin++) { + y = ymin+0.5F; + for (i = j = 0; i < n; i++) + if (y >= e[i].ymin && y <= e[i].ymax) { + if (e[i].d == 0) + hline8(im, e[i].xmin, ymin, e[i].xmax, ink); + else + xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0; + } + if (j == 2) { + if (xx[0] < xx[1]) + hline8(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink); + else + hline8(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink); + } else { + qsort(xx, j, sizeof(float), x_cmp); + for (i = 0; i < j-1 ; i += 2) + hline8(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink); + } + } + + free(xx); + + return 0; +} + +static inline int +polygon32(Imaging im, int n, Edge *e, int ink, int eofill) +{ + int i, j; + float *xx; + int ymin, ymax; + float y; + + if (n <= 0) + return 0; + + /* Find upper and lower polygon boundary (within image) */ + + ymin = e[0].ymin; + ymax = e[0].ymax; + for (i = 1; i < n; i++) { + if (e[i].ymin < ymin) ymin = e[i].ymin; + if (e[i].ymax > ymax) ymax = e[i].ymax; + } + + if (ymin < 0) + ymin = 0; + if (ymax >= im->ysize) + ymax = im->ysize-1; + + /* Process polygon edges */ + + xx = malloc(n * sizeof(float)); + if (!xx) + return -1; + + for (;ymin <= ymax; ymin++) { + y = ymin+0.5F; + for (i = j = 0; i < n; i++) { + if (y >= e[i].ymin && y <= e[i].ymax) { + if (e[i].d == 0) + hline32(im, e[i].xmin, ymin, e[i].xmax, ink); + else + xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0; + } + } + if (j == 2) { + if (xx[0] < xx[1]) + hline32(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink); + else + hline32(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink); + } else { + qsort(xx, j, sizeof(float), x_cmp); + for (i = 0; i < j-1 ; i += 2) + hline32(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink); + } + } + + free(xx); + + return 0; +} + +static inline int +polygon32rgba(Imaging im, int n, Edge *e, int ink, int eofill) +{ + int i, j; + float *xx; + int ymin, ymax; + float y; + + if (n <= 0) + return 0; + + /* Find upper and lower polygon boundary (within image) */ + + ymin = e[0].ymin; + ymax = e[0].ymax; + for (i = 1; i < n; i++) { + if (e[i].ymin < ymin) ymin = e[i].ymin; + if (e[i].ymax > ymax) ymax = e[i].ymax; + } + + if (ymin < 0) + ymin = 0; + if (ymax >= im->ysize) + ymax = im->ysize-1; + + /* Process polygon edges */ + + xx = malloc(n * sizeof(float)); + if (!xx) + return -1; + + for (;ymin <= ymax; ymin++) { + y = ymin+0.5F; + for (i = j = 0; i < n; i++) { + if (y >= e[i].ymin && y <= e[i].ymax) { + if (e[i].d == 0) + hline32rgba(im, e[i].xmin, ymin, e[i].xmax, ink); + else + xx[j++] = (y-e[i].y0) * e[i].dx + e[i].x0; + } + } + if (j == 2) { + if (xx[0] < xx[1]) + hline32rgba(im, CEIL(xx[0]-0.5), ymin, FLOOR(xx[1]+0.5), ink); + else + hline32rgba(im, CEIL(xx[1]-0.5), ymin, FLOOR(xx[0]+0.5), ink); + } else { + qsort(xx, j, sizeof(float), x_cmp); + for (i = 0; i < j-1 ; i += 2) + hline32rgba(im, CEIL(xx[i]-0.5), ymin, FLOOR(xx[i+1]+0.5), ink); + } + } + + free(xx); + + return 0; +} + +static inline void +add_edge(Edge *e, int x0, int y0, int x1, int y1) +{ + /* printf("edge %d %d %d %d\n", x0, y0, x1, y1); */ + + if (x0 <= x1) + e->xmin = x0, e->xmax = x1; + else + e->xmin = x1, e->xmax = x0; + + if (y0 <= y1) + e->ymin = y0, e->ymax = y1; + else + e->ymin = y1, e->ymax = y0; + + if (y0 == y1) { + e->d = 0; + e->dx = 0.0; + } else { + e->dx = ((float)(x1-x0)) / (y1-y0); + if (y0 == e->ymin) + e->d = 1; + else + e->d = -1; + } + + e->x0 = x0; + e->y0 = y0; +} + +typedef struct { + void (*point)(Imaging im, int x, int y, int ink); + void (*hline)(Imaging im, int x0, int y0, int x1, int ink); + void (*line)(Imaging im, int x0, int y0, int x1, int y1, int ink); + int (*polygon)(Imaging im, int n, Edge *e, int ink, int eofill); +} DRAW; + +DRAW draw8 = { point8, hline8, line8, polygon8 }; +DRAW draw32 = { point32, hline32, line32, polygon32 }; +DRAW draw32rgba = { point32rgba, hline32rgba, line32rgba, polygon32rgba }; + +/* -------------------------------------------------------------------- */ +/* Interface */ +/* -------------------------------------------------------------------- */ + +#define DRAWINIT()\ + if (im->image8) {\ + draw = &draw8;\ + ink = INK8(ink_);\ + } else {\ + draw = (op) ? &draw32rgba : &draw32; \ + ink = INK32(ink_);\ + } + +int +ImagingDrawPoint(Imaging im, int x0, int y0, const void* ink_, int op) +{ + DRAW* draw; + INT32 ink; + + DRAWINIT(); + + draw->point(im, x0, y0, ink); + + return 0; +} + +int +ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void* ink_, + int op) +{ + DRAW* draw; + INT32 ink; + + DRAWINIT(); + + draw->line(im, x0, y0, x1, y1, ink); + + return 0; +} + +int +ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, + const void* ink_, int width, int op) +{ + DRAW* draw; + INT32 ink; + + Edge e[4]; + + int dx, dy; + double d; + + DRAWINIT(); + + if (width <= 1) { + draw->line(im, x0, y0, x1, y1, ink); + return 0; + } + + dx = x1-x0; + dy = y1-y0; + + if (dx == 0 && dy == 0) { + draw->point(im, x0, y0, ink); + return 0; + } + + d = width / sqrt((float) (dx*dx + dy*dy)) / 2.0; + + dx = (int) floor(d * (y1-y0) + 0.5); + dy = (int) floor(d * (x1-x0) + 0.5); + + add_edge(e+0, x0 - dx, y0 + dy, x1 - dx, y1 + dy); + add_edge(e+1, x1 - dx, y1 + dy, x1 + dx, y1 - dy); + add_edge(e+2, x1 + dx, y1 - dy, x0 + dx, y0 - dy); + add_edge(e+3, x0 + dx, y0 - dy, x0 - dx, y0 + dy); + + draw->polygon(im, 4, e, ink, 0); + + return 0; +} + + + +/* -------------------------------------------------------------------- */ +/* standard shapes */ + +#define ARC 0 +#define CHORD 1 +#define PIESLICE 2 + + +/* -------------------------------------------------------------------- */ + +/* experimental level 2 ("arrow") graphics stuff. this implements + portions of the arrow api on top of the Edge structure. the + semantics are ok, except that "curve" flattens the bezier curves by + itself */ + +#if 1 /* ARROW_GRAPHICS */ + +struct ImagingOutlineInstance { + + float x0, y0; + + float x, y; + + int count; + Edge *edges; + + int size; + +}; + + + +void +ImagingOutlineDelete(ImagingOutline outline) +{ + if (!outline) + return; + + if (outline->edges) + free(outline->edges); + + free(outline); +} + + +static Edge* +allocate(ImagingOutline outline, int extra) +{ + Edge* e; + + if (outline->count + extra > outline->size) { + /* expand outline buffer */ + outline->size += extra + 25; + if (!outline->edges) + e = malloc(outline->size * sizeof(Edge)); + else + e = realloc(outline->edges, outline->size * sizeof(Edge)); + if (!e) + return NULL; + outline->edges = e; + } + + e = outline->edges + outline->count; + + outline->count += extra; + + return e; +} + +int +ImagingOutlineMove(ImagingOutline outline, float x0, float y0) +{ + outline->x = outline->x0 = x0; + outline->y = outline->y0 = y0; + + return 0; +} + +int +ImagingOutlineLine(ImagingOutline outline, float x1, float y1) +{ + Edge* e; + + e = allocate(outline, 1); + if (!e) + return -1; /* out of memory */ + + add_edge(e, (int) outline->x, (int) outline->y, (int) x1, (int) y1); + + outline->x = x1; + outline->y = y1; + + return 0; +} + +int +ImagingOutlineCurve(ImagingOutline outline, float x1, float y1, + float x2, float y2, float x3, float y3) +{ + Edge* e; + int i; + float xo, yo; + +#define STEPS 32 + + e = allocate(outline, STEPS); + if (!e) + return -1; /* out of memory */ + + xo = outline->x; + yo = outline->y; + + /* flatten the bezier segment */ + + for (i = 1; i <= STEPS; i++) { + + float t = ((float) i) / STEPS; + float t2 = t*t; + float t3 = t2*t; + + float u = 1.0F - t; + float u2 = u*u; + float u3 = u2*u; + + float x = outline->x*u3 + 3*(x1*t*u2 + x2*t2*u) + x3*t3 + 0.5; + float y = outline->y*u3 + 3*(y1*t*u2 + y2*t2*u) + y3*t3 + 0.5; + + add_edge(e++, xo, yo, (int) x, (int) y); + + xo = x, yo = y; + + } + + outline->x = xo; + outline->y = yo; + + return 0; +} + +int +ImagingOutlineCurve2(ImagingOutline outline, float cx, float cy, + float x3, float y3) +{ + /* add bezier curve based on three control points (as + in the Flash file format) */ + + return ImagingOutlineCurve( + outline, + (outline->x + cx + cx)/3, (outline->y + cy + cy)/3, + (cx + cx + x3)/3, (cy + cy + y3)/3, + x3, y3); +} + +int +ImagingOutlineClose(ImagingOutline outline) +{ + if (outline->x == outline->x0 && outline->y == outline->y0) + return 0; + return ImagingOutlineLine(outline, outline->x0, outline->y0); +} + + +int +ImagingDrawOutline(Imaging im, ImagingOutline outline, const void* ink_, + int fill, int op) +{ + DRAW* draw; + INT32 ink; + + DRAWINIT(); + + draw->polygon(im, outline->count, outline->edges, ink, 0); + + return 0; +} + +#endif diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c index e38118d..a5e9996 100644 --- a/src/rendermode-normal.c +++ b/src/rendermode-normal.c @@ -161,6 +161,38 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * tint_with_mask(state->img, r, g, b, facemask, state->imgx, state->imgy, 0, 0); } } + + + /* Draw some edge lines! */ + // draw.line(((imgx+12,imgy+increment), (imgx+22,imgy+5+increment)), fill=(0,0,0), width=1) + if (!is_transparent(state->block)) { + int increment=0; + if (state->block == 44) + increment=6; + else if (state->block == 78) + increment=9; + + Imaging img_i = imaging_python_to_c(state->img); + uint8_t ink[] = {0,0,0,40}; + if ((state->x == 15) && (state->up_right_blocks != Py_None) && is_transparent(getArrayByte3D(state->up_right_blocks, 0, state->y, state->z))) { + ImagingDrawLine(img_i, state->imgx+12, state->imgy+1+increment, state->imgx+22+1, state->imgy+5+1+increment, &ink, 1); + ImagingDrawLine(img_i, state->imgx+12, state->imgy+increment, state->imgx+22+1, state->imgy+5+increment, &ink, 1); + } else if ((state->x != 15) && is_transparent(getArrayByte3D(state->blocks, state->x+1, state->y, state->z))) { + ImagingDrawLine(img_i, state->imgx+12, state->imgy+1+increment, state->imgx+22+1, state->imgy+5+1+increment, &ink, 1); + ImagingDrawLine(img_i, state->imgx+12, state->imgy+increment, state->imgx+22+1, state->imgy+5+increment, &ink, 1); + } + // if y != 0 and blocks[x,y-1,z] == 0 + + // chunk boundries are annoying + if ((state->y == 0) && (state->up_left_blocks != Py_None) && is_transparent(getArrayByte3D(state->up_left_blocks, state->x, 15, state->z))) { + ImagingDrawLine(img_i, state->imgx, state->imgy+6+1+increment, state->imgx+12+1, state->imgy+1+increment, &ink, 1); + ImagingDrawLine(img_i, state->imgx, state->imgy+6+increment, state->imgx+12+1, state->imgy+increment, &ink, 1); + } else if ((state->y != 0) && is_transparent(getArrayByte3D(state->blocks, state->x, state->y-1, state->z))) { + // draw.line(((imgx,imgy+6+increment), (imgx+12,imgy+increment)), fill=(0,0,0), width=1) + ImagingDrawLine(img_i, state->imgx, state->imgy+6+1+increment, state->imgx+12+1, state->imgy+1+increment, &ink, 1); + ImagingDrawLine(img_i, state->imgx, state->imgy+6+increment, state->imgx+12+1, state->imgy+increment, &ink, 1); + } + } } RenderModeInterface rendermode_normal = { From 50a0186b4f02263ea80dcb72932e7cd4c087176f Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 2 Apr 2011 00:38:41 -0400 Subject: [PATCH 156/213] Bump extension version --- src/overviewer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overviewer.h b/src/overviewer.h index c978773..6eac848 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -26,7 +26,7 @@ // increment this value if you've made a change to the c extesion // and want to force users to rebuild -#define OVERVIEWER_EXTENSION_VERSION 2 +#define OVERVIEWER_EXTENSION_VERSION 3 /* Python PIL, and numpy headers */ #include From 7cf8add231ecc09f7e032407c0eb7976b0ca1ebb Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 2 Apr 2011 01:00:33 -0400 Subject: [PATCH 157/213] Fix win86_32 build issues --- src/rendermode-normal.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c index a5e9996..d4d17dd 100644 --- a/src/rendermode-normal.c +++ b/src/rendermode-normal.c @@ -166,14 +166,15 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * /* Draw some edge lines! */ // draw.line(((imgx+12,imgy+increment), (imgx+22,imgy+5+increment)), fill=(0,0,0), width=1) if (!is_transparent(state->block)) { + Imaging img_i = imaging_python_to_c(state->img); + unsigned char ink[] = {0,0,0,40}; + int increment=0; if (state->block == 44) increment=6; else if (state->block == 78) increment=9; - Imaging img_i = imaging_python_to_c(state->img); - uint8_t ink[] = {0,0,0,40}; if ((state->x == 15) && (state->up_right_blocks != Py_None) && is_transparent(getArrayByte3D(state->up_right_blocks, 0, state->y, state->z))) { ImagingDrawLine(img_i, state->imgx+12, state->imgy+1+increment, state->imgx+22+1, state->imgy+5+1+increment, &ink, 1); ImagingDrawLine(img_i, state->imgx+12, state->imgy+increment, state->imgx+22+1, state->imgy+5+increment, &ink, 1); From 6c1d85f69933eda5378da9a172221301bce8cc6b Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 2 Apr 2011 14:30:44 -0400 Subject: [PATCH 158/213] Don't crash if a biome-chunk is missing --- src/rendermode-normal.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c index d4d17dd..d5fb87d 100644 --- a/src/rendermode-normal.c +++ b/src/rendermode-normal.c @@ -49,17 +49,28 @@ rendermode_normal_start(void *data, RenderState *state) { self->biome_data = PyObject_CallMethod(state->textures, "getBiomeData", "OOO", worlddir, chunk_x_py, chunk_y_py); - self->foliagecolor = PyObject_GetAttrString(state->textures, "foliagecolor"); - self->grasscolor = PyObject_GetAttrString(state->textures, "grasscolor"); - - self->leaf_texture = PyObject_GetAttrString(state->textures, "biome_leaf_texture"); - self->grass_texture = PyObject_GetAttrString(state->textures, "biome_grass_texture"); - - facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks"); - /* borrowed reference, needs to be incref'd if we keep it */ - self->facemask_top = PyTuple_GetItem(facemasks_py, 0); - Py_INCREF(self->facemask_top); - Py_DECREF(facemasks_py); + if (self->biome_data == Py_None) { + self->biome_data = NULL; + self->foliagecolor = NULL; + self->grasscolor = NULL; + + self->leaf_texture = NULL; + self->grass_texture = NULL; + self->facemask_top = NULL; + } else { + + self->foliagecolor = PyObject_GetAttrString(state->textures, "foliagecolor"); + self->grasscolor = PyObject_GetAttrString(state->textures, "grasscolor"); + + self->leaf_texture = PyObject_GetAttrString(state->textures, "biome_leaf_texture"); + self->grass_texture = PyObject_GetAttrString(state->textures, "biome_grass_texture"); + + facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks"); + /* borrowed reference, needs to be incref'd if we keep it */ + self->facemask_top = PyTuple_GetItem(facemasks_py, 0); + Py_INCREF(self->facemask_top); + Py_DECREF(facemasks_py); + } } else { self->biome_data = NULL; self->foliagecolor = NULL; From 9e24d87ba2a4909d5078bc586b151e295abe8322 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 2 Apr 2011 15:32:24 -0400 Subject: [PATCH 159/213] Started to update the README Also added a dummy sample settings file (which needs to be expanded into something useful) --- README.rst | 97 ++++++++++++++++++---------------------------- sample.settings.py | 1 + 2 files changed, 38 insertions(+), 60 deletions(-) create mode 100644 sample.settings.py diff --git a/README.rst b/README.rst index 5acc920..2b07d45 100644 --- a/README.rst +++ b/README.rst @@ -32,7 +32,7 @@ Features * Renders efficiently in parallel, using as many simultaneous processes as you want! -* Utilizes 2 levels of caching to speed up subsequent renderings of your world. +* Utilizes caching to speed up subsequent renderings of your world. * Throw the output directory up on a web server to share your Minecraft world with everyone! @@ -46,6 +46,9 @@ This program requires: * Numpy * Either the Minecraft client installed, or a terrain.png file. See the `Textures`_ section below. +* A C compiler. + +If you download a binary package, then some or all of these may not be required. I develop and test this on Linux, but need help testing it on Windows and Mac. If something doesn't work, let me know. @@ -97,15 +100,14 @@ will use the biome data to tint grass and leaves automatically -- there is no command line option to turn this feature on. If this folder does not exist, then the Overviewer will use a static tinting for grass and leaves. -Compiling the C Extension (optional) ------------------------------------- -The C Extension for Overviewer is completely optional. It provides a higher -quality image compositing function that looks better on maps with lighting -enabled, and a slight performance boost. +Compiling the C Extension +------------------------- +The C Extension for Overviewer is no longer optional. In addition to providing +a higher quality image compositing function that looks better on maps with lighting +enabled, it now does the bulk of the rendering. -If you downloaded Overviewer as a binary package, this extension may be already -compiled for you. Overviewer emits a warning if the extension is not found, but -will still work fine. +If you downloaded Overviewer as a binary package, this extension will already be +compiled for you. If you have a C compiler and the Python development libraries set up, you can compile this extension like this:: @@ -119,6 +121,9 @@ you get errors complaining about them, you can get them from the PIL source, or at . Just put them in the same directory as "_composite.c". +For more detailed instructions, check the wiki: +https://github.com/brownan/Minecraft-Overviewer/wiki/Build-Instructions + Running ------- To generate a set of Google Map tiles, use the overviewer.py script like this:: @@ -130,11 +135,6 @@ set of image tiles for your world in the directory you choose. When it's done, you will find an index.html file in the same directory that you can use to view it. -**Important note about Caches** - -The Overviewer will put a cached image for every chunk *directly in your world -directory by default*. If you do not like this behavior, you can specify -another location with the --cachedir option. See below for details. Options ------- @@ -142,19 +142,6 @@ Options -h, --help Shows the list of options and exits ---cachedir=CACHEDIR - By default, the Overviewer will save in your world directory one image - file for every chunk in your world. If you do backups of your world, - you may not want these images in your world directory. - - Use this option to specify an alternate location to put the rendered - chunk images. You must specify this same directory each rendering so - that it doesn't have to render every chunk from scratch every time. - - Example:: - - python overviewer.py --cachedir= - --imgformat=FORMAT Set the output image format used for the tiles. The default is 'png', but 'jpg' is also supported. Note that regardless of what you choose, @@ -200,39 +187,22 @@ Options -d, --delete This option changes the mode of execution. No tiles are rendered, and - instead, cache files are deleted. + instead, files are deleted. - Explanation: The Overviewer keeps two levels of cache: it saves each - chunk rendered as a png, and it keeps a hash file along side each tile - in your output directory. Using these cache files allows the Overviewer - to skip rendering of any tile image that has not changed. + *Note*: Currently only the overviewer.dat file is deleted when you run with + this option - By default, the chunk images are saved in your world directory. This - example will remove them:: - - python overviewer.py -d - - You can also delete the tile cache as well. This will force a full - re-render, useful if you've changed texture packs and want your world - to look uniform. Here's an example:: - - python overviewer.py -d <# / path> - - Be warned, this will cause the next rendering of your map to take - significantly longer, since it is having to re-generate the files you just - deleted. - ---chunklist=CHUNKLIST - Use this option to specify manually a list of chunks to consider for - updating. Without this option, every chunk is checked for update and if - necessary, re-rendered. If this option points to a file containing, 1 per - line, the path to a chunk data file, then only those in the list will be - considered for update. +--regionlist=regionlist + Use this option to specify manually a list of regions to consider for + updating. Without this option, every chunk in every region is checked for + update and if necessary, re-rendered. If this option points to a file + containing, 1 per line, the path to a region data file, then only those + in the list will be considered for update. It's up to you to build such a list. On Linux or Mac, try using the "find" - command. You could, for example, output all chunk files that are older than + command. You could, for example, output all region files that are older than a certain date. Or perhaps you can incrementally update your map by passing - in a subset of chunks each time. It's up to you! + in a subset of regions each time. It's up to you! --lighting This option enables map lighting, using lighting information stored by @@ -255,6 +225,18 @@ Options The script should be executable, and it should accept one argument: the path to the output directory. + +Settings +-------- + +You can optionally store settings in a file named settings.py. It is a regular +python script, so you can use any python functions or modules you want. + +This section needs to be expanded + +For a sample settings file, look at sample.settings.py + + Viewing the Results ------------------- Within the output directory you will find two things: an index.html file, and a @@ -308,10 +290,5 @@ An incomplete list of things I want to do soon is: * Improve efficiency -* Rendering non-cube blocks, such as torches, flowers, mine tracks, fences, - doors, and the like. Right now they are either not rendered at all, or - rendered as if they were a cube, so it looks funny. - * Some kind of graphical interface. -* A Windows exe for easier access for Windows users. diff --git a/sample.settings.py b/sample.settings.py new file mode 100644 index 0000000..7ee5011 --- /dev/null +++ b/sample.settings.py @@ -0,0 +1 @@ +# TODO: put something useful in this file! From 4276176227150cf595e80d7f71dd54283f771f4d Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 2 Apr 2011 17:29:17 -0400 Subject: [PATCH 160/213] add support for a PIL_INCLUDE_DIR environment variable --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fba44d4..0a36bcf 100644 --- a/setup.py +++ b/setup.py @@ -49,12 +49,16 @@ try: except AttributeError: numpy_include = numpy.get_numpy_include() +try: + pil_include = os.environ['PIL_INCLUDE_DIR'].split(os.pathsep) +except: + pil_include = [] c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/endian.c'] c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c', 'src/rendermode-night.c', 'src/rendermode-spawn.c'] c_overviewer_files += ['src/Draw.c'] c_overviewer_includes = ['src/overviewer.h', 'src/rendermodes.h'] -setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], depends=c_overviewer_includes, extra_link_args=[])) +setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include] + pil_include, depends=c_overviewer_includes, extra_link_args=[])) # tell build_ext to build the extension in-place # (NOT in build/) setup_kwargs['options']['build_ext'] = {'inplace' : 1} From e5660bf925a818074410e73208221b9811526761 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 3 Apr 2011 00:15:48 -0400 Subject: [PATCH 161/213] fixed snow/half-step edge lines --- src/rendermode-normal.c | 44 ++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c index d5fb87d..1381eb0 100644 --- a/src/rendermode-normal.c +++ b/src/rendermode-normal.c @@ -176,33 +176,45 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * /* Draw some edge lines! */ // draw.line(((imgx+12,imgy+increment), (imgx+22,imgy+5+increment)), fill=(0,0,0), width=1) - if (!is_transparent(state->block)) { + if (state->block == 44 || state->block == 78 || !is_transparent(state->block)) { Imaging img_i = imaging_python_to_c(state->img); unsigned char ink[] = {0,0,0,40}; int increment=0; - if (state->block == 44) + if (state->block == 44) // half-step increment=6; - else if (state->block == 78) + else if (state->block == 78) // snow increment=9; - if ((state->x == 15) && (state->up_right_blocks != Py_None) && is_transparent(getArrayByte3D(state->up_right_blocks, 0, state->y, state->z))) { - ImagingDrawLine(img_i, state->imgx+12, state->imgy+1+increment, state->imgx+22+1, state->imgy+5+1+increment, &ink, 1); - ImagingDrawLine(img_i, state->imgx+12, state->imgy+increment, state->imgx+22+1, state->imgy+5+increment, &ink, 1); - } else if ((state->x != 15) && is_transparent(getArrayByte3D(state->blocks, state->x+1, state->y, state->z))) { - ImagingDrawLine(img_i, state->imgx+12, state->imgy+1+increment, state->imgx+22+1, state->imgy+5+1+increment, &ink, 1); - ImagingDrawLine(img_i, state->imgx+12, state->imgy+increment, state->imgx+22+1, state->imgy+5+increment, &ink, 1); + if ((state->x == 15) && (state->up_right_blocks != Py_None)) { + unsigned char side_block = getArrayByte3D(state->up_right_blocks, 0, state->y, state->z); + if (side_block != state->block && is_transparent(side_block)) { + ImagingDrawLine(img_i, state->imgx+12, state->imgy+1+increment, state->imgx+22+1, state->imgy+5+1+increment, &ink, 1); + ImagingDrawLine(img_i, state->imgx+12, state->imgy+increment, state->imgx+22+1, state->imgy+5+increment, &ink, 1); + } + } else if (state->x != 15) { + unsigned char side_block = getArrayByte3D(state->blocks, state->x+1, state->y, state->z); + if (side_block != state->block && is_transparent(side_block)) { + ImagingDrawLine(img_i, state->imgx+12, state->imgy+1+increment, state->imgx+22+1, state->imgy+5+1+increment, &ink, 1); + ImagingDrawLine(img_i, state->imgx+12, state->imgy+increment, state->imgx+22+1, state->imgy+5+increment, &ink, 1); + } } // if y != 0 and blocks[x,y-1,z] == 0 // chunk boundries are annoying - if ((state->y == 0) && (state->up_left_blocks != Py_None) && is_transparent(getArrayByte3D(state->up_left_blocks, state->x, 15, state->z))) { - ImagingDrawLine(img_i, state->imgx, state->imgy+6+1+increment, state->imgx+12+1, state->imgy+1+increment, &ink, 1); - ImagingDrawLine(img_i, state->imgx, state->imgy+6+increment, state->imgx+12+1, state->imgy+increment, &ink, 1); - } else if ((state->y != 0) && is_transparent(getArrayByte3D(state->blocks, state->x, state->y-1, state->z))) { - // draw.line(((imgx,imgy+6+increment), (imgx+12,imgy+increment)), fill=(0,0,0), width=1) - ImagingDrawLine(img_i, state->imgx, state->imgy+6+1+increment, state->imgx+12+1, state->imgy+1+increment, &ink, 1); - ImagingDrawLine(img_i, state->imgx, state->imgy+6+increment, state->imgx+12+1, state->imgy+increment, &ink, 1); + if ((state->y == 0) && (state->up_left_blocks != Py_None)) { + unsigned char side_block = getArrayByte3D(state->up_left_blocks, state->x, 15, state->z); + if (side_block != state->block && is_transparent(side_block)) { + ImagingDrawLine(img_i, state->imgx, state->imgy+6+1+increment, state->imgx+12+1, state->imgy+1+increment, &ink, 1); + ImagingDrawLine(img_i, state->imgx, state->imgy+6+increment, state->imgx+12+1, state->imgy+increment, &ink, 1); + } + } else if (state->y != 0) { + unsigned char side_block = getArrayByte3D(state->blocks, state->x, state->y-1, state->z); + if (side_block != state->block && is_transparent(side_block)) { + // draw.line(((imgx,imgy+6+increment), (imgx+12,imgy+increment)), fill=(0,0,0), width=1) + ImagingDrawLine(img_i, state->imgx, state->imgy+6+1+increment, state->imgx+12+1, state->imgy+1+increment, &ink, 1); + ImagingDrawLine(img_i, state->imgx, state->imgy+6+increment, state->imgx+12+1, state->imgy+increment, &ink, 1); + } } } } From c428fcbd70709afead2cfa1d1eafc8bfeb65ec6c Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 4 Apr 2011 02:03:38 -0400 Subject: [PATCH 162/213] changed testRender.py to work with ./overviewer.py and ./gmap.py (sorta) --- contrib/testRender.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) 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 From be62a0b4648491b7a208b97370f92aa6449bf706 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Wed, 6 Apr 2011 22:31:00 -0400 Subject: [PATCH 163/213] Added new sample settings file --- sample.settings.py | 150 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 1 deletion(-) 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 + + From 22fc77308e3f2010f91a32b3e7b4a9f0e55ce3a8 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 10 Apr 2011 03:44:06 -0400 Subject: [PATCH 164/213] fixed minor inlining error in commit d547727 fixes regression in issue #320 --- chunk.py | 32 -------------------------------- quadtree.py | 12 +++++++----- 2 files changed, 7 insertions(+), 37 deletions(-) diff --git a/chunk.py b/chunk.py index a59d90e..977fdfa 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/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) From a2b1f238d9c72f3a8506f9f901a87af7731101b3 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 10 Apr 2011 04:38:42 -0400 Subject: [PATCH 165/213] changed markers.js to output one marker per line this has been requested *many* times, because it makes it easier to (1) learn what format markers.js uses, and (2) write shell scripts that process markers.js. It shouldn't break existing processors that use json loaders, and does not drastically increase file size (just one extra byte per ~70). --- googlemap.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/googlemap.py b/googlemap.py index 2321049..f6f271e 100644 --- a/googlemap.py +++ b/googlemap.py @@ -144,7 +144,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 From dd19e88f15c701ae8af8c68285a3246de8844f5a Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Mon, 11 Apr 2011 00:15:20 +0200 Subject: [PATCH 166/213] Add special case for wooden and cobblestone stairs in textures.py. Fix a wrong comment in ladder. Add more comments in _buil_full_block. --- textures.py | 93 ++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 82 insertions(+), 11 deletions(-) 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 From a2782a0499269f512bf232b3c07b7da0f0c7014a Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Tue, 12 Apr 2011 23:55:17 -0400 Subject: [PATCH 167/213] MSVC is strict about variable declaration --- src/rendermode-overlay.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/rendermode-overlay.c b/src/rendermode-overlay.c index 69d7b04..2a15302 100644 --- a/src/rendermode-overlay.c +++ b/src/rendermode-overlay.c @@ -74,6 +74,7 @@ static void rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { RenderModeOverlay *self = (RenderModeOverlay *)data; unsigned char r, g, b, a; + PyObject *top_block_py, *block_py; /* clear the draw space -- set alpha to 0 within mask */ tint_with_mask(state->img, 255, 255, 255, 0, mask, state->imgx, state->imgy, 0, 0); @@ -86,7 +87,7 @@ rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject } /* check to be sure this block is solid/fluid */ - PyObject *top_block_py = PyInt_FromLong(top_block); + top_block_py = PyInt_FromLong(top_block); if (PySequence_Contains(self->solid_blocks, top_block_py) || PySequence_Contains(self->fluid_blocks, top_block_py)) { @@ -98,7 +99,7 @@ rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject } /* check to be sure this block is solid/fluid */ - PyObject *block_py = PyInt_FromLong(state->block); + block_py = PyInt_FromLong(state->block); if (!PySequence_Contains(self->solid_blocks, block_py) && !PySequence_Contains(self->fluid_blocks, block_py)) { From 70bf8a7fb24bfb0e9aaa09b8211446434f23bcb9 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 13 Apr 2011 09:14:27 -0400 Subject: [PATCH 168/213] README update for settings.py, --rendermodes --- README.rst | 138 +++++++++++++++++++++++++++-------------------------- 1 file changed, 71 insertions(+), 67 deletions(-) diff --git a/README.rst b/README.rst index 2b07d45..616e8d2 100644 --- a/README.rst +++ b/README.rst @@ -8,14 +8,19 @@ http://github.com/brownan/Minecraft-Overviewer Generates large resolution images of a Minecraft map. In short, this program reads in Minecraft world files and renders very large -resolution images. It performs a similar function to the existing Minecraft -Cartographer program but with a slightly different goal in mind: to generate -large resolution images such that one can zoom in and see details. +resolution images that can be viewed through a Google Maps interface. It +performs a similar function to the existing Minecraft Cartographer program but +with a slightly different goal in mind: to generate large resolution images +such that one can zoom in and see details. See some examples here! http://github.com/brownan/Minecraft-Overviewer/wiki/Map-examples -(To contact me, send me a message on Github) +Further documentation may be found at +https://github.com/brownan/Minecraft-Overviewer/wiki/Documentation + +To contact the developers and other users, go to the site at the top of this +README, or go to #overviewer on irc.freenode.net. Features ======== @@ -56,6 +61,12 @@ If something doesn't work, let me know. Using the Overviewer ==================== +For a quick-start guide, see +https://github.com/brownan/Minecraft-Overviewer/wiki/Quick-Start-Guide + +If you are upgrading from an older Overviewer to the new DTT code, see +https://github.com/brownan/Minecraft-Overviewer/wiki/DTT-Upgrade-Guide + Disclaimers ----------- Before you dive into using this, just be aware that, for large maps, there is a @@ -89,7 +100,7 @@ you can use the Overviewer: texture packs out there. Biome Tinting -~~~~~~~~~~~~~ +------------- With the Halloween update, biomes were added to Minecraft. In order to get biome-accurate tinting, the Overviewer can use biome data produced by the Minecraft Biome Extractor tool. This tool can be downloaded from: @@ -102,12 +113,12 @@ then the Overviewer will use a static tinting for grass and leaves. Compiling the C Extension ------------------------- -The C Extension for Overviewer is no longer optional. In addition to providing -a higher quality image compositing function that looks better on maps with lighting -enabled, it now does the bulk of the rendering. +The C Extension for Overviewer is no longer optional. In addition to +providing a higher quality image compositing function that looks better on +maps with lighting enabled, it now does the bulk of the rendering. -If you downloaded Overviewer as a binary package, this extension will already be -compiled for you. +If you downloaded Overviewer as a binary package, this extension will already +be compiled for you. If you have a C compiler and the Python development libraries set up, you can compile this extension like this:: @@ -119,7 +130,7 @@ look for a package named 'python-dev', 'python-devel' or similar. Also, some Python distributions do not install "Imaging.h" and "ImPlatform.h" properly. If you get errors complaining about them, you can get them from the PIL source, or at . Just put them in -the same directory as "_composite.c". +the same directory as "overviewer.py". For more detailed instructions, check the wiki: https://github.com/brownan/Minecraft-Overviewer/wiki/Build-Instructions @@ -142,12 +153,6 @@ Options -h, --help Shows the list of options and exits ---imgformat=FORMAT - Set the output image format used for the tiles. The default is 'png', - but 'jpg' is also supported. Note that regardless of what you choose, - Overviewer will still use PNG for cached images to avoid recompression - artifacts. - -p PROCS, --processes=PROCS Adding the "-p" option will utilize more cores during processing. This can speed up rendering quite a bit. The default is set to the same @@ -157,34 +162,6 @@ Options python overviewer.py -p 5 --z ZOOM, --zoom=ZOOM - The Overviewer by default will detect how many zoom levels are required - to show your entire map. This option sets it manually. - - *You do not normally need to set this option!* - - This is equivalent to setting the dimensions of the highest zoom level. It - does not actually change how the map is rendered, but rather *how much of - the map is rendered.* (Calling this option "zoom" may be a bit misleading, - I know) - - To be precise, it sets the width and height of the highest zoom level, in - tiles. A zoom level of z means the highest zoom level of your map will be - 2^z by 2^z tiles. - - This option map be useful if you have some outlier chunks causing your map - to be too large, or you want to render a smaller portion of your map, - instead of rendering everything. - - This will render your map with 7 zoom levels:: - - python overviewer.py -z 7 - - Remember that each additional zoom level adds 4 times as many tiles as - the last. This can add up fast, zoom level 10 has over a million tiles. - Tiles with no content will not be rendered, but they still take a small - amount of time to process. - -d, --delete This option changes the mode of execution. No tiles are rendered, and instead, files are deleted. @@ -204,38 +181,65 @@ Options a certain date. Or perhaps you can incrementally update your map by passing in a subset of regions each time. It's up to you! ---lighting - This option enables map lighting, using lighting information stored by - Minecraft inside the chunks. This will make your map prettier, at the cost - of update speed. - - Note that for existing, unlit maps, you may want to clear your cache - (with -d) before updating the map to use lighting. Otherwise, only updated - chunks will have lighting enabled. +--rendermodes=MODE1[,MODE2,...] + Use this option to specify which render mode to use, such as lighting or + night. Use --list-rendermodes to get a list of available rendermodes, and + a short description of each. If you provide more than one mode (separated + by commas), Overviewer will render all of them at once, and provide a + toggle on the resulting map to switch between them. ---night - This option enables --lighting, and renders the world at night. - ---web-assets-hook=HOOK - This option lets you specify a script to run after the web assets have been - copied into the output directory, but before any tile rendering takes - place. This is an ideal time to do any custom postprocessing for markers.js - or other web assets. - - The script should be executable, and it should accept one argument: - the path to the output directory. +--list-rendermodes + List the available render modes, and a short description of each. Settings -------- - You can optionally store settings in a file named settings.py. It is a regular python script, so you can use any python functions or modules you want. -This section needs to be expanded +For a sample settings file, look at 'sample.settings.py'. Note that this file +is not meant to be used directly, but instead it should be used as a +collection of examples to guide writing your own. -For a sample settings file, look at sample.settings.py +Here's a (possibly incomplete) list of available settings, which are available +in settings.py. Note that you can also set command-line options in a similar +way. +imgformat=FORMAT + Set the output image format used for the tiles. The default is 'png', + but 'jpg' is also supported. + +zoom=ZOOM + The Overviewer by default will detect how many zoom levels are required + to show your entire map. This option sets it manually. + + *You do not normally need to set this option!* + + This is equivalent to setting the dimensions of the highest zoom level. It + does not actually change how the map is rendered, but rather *how much of + the map is rendered.* (Calling this option "zoom" may be a bit misleading, + I know) + + To be precise, it sets the width and height of the highest zoom level, in + tiles. A zoom level of z means the highest zoom level of your map will be + 2^z by 2^z tiles. + + This option map be useful if you have some outlier chunks causing your map + to be too large, or you want to render a smaller portion of your map, + instead of rendering everything. + + Remember that each additional zoom level adds 4 times as many tiles as + the last. This can add up fast, zoom level 10 has over a million tiles. + Tiles with no content will not be rendered, but they still take a small + amount of time to process. + +web_assets_hook + This option lets you define a function to run after the web assets have + been copied into the output directory, but before any tile rendering takes + place. This is an ideal time to do any custom postprocessing for + markers.js or other web assets. + + This function should accept one argument: a QuadtreeGen object. Viewing the Results ------------------- From bbf52a77aefdc4c43aeea971bb4907b40f7340c5 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 13 Apr 2011 11:15:19 -0400 Subject: [PATCH 169/213] added CONTRIBUTORS.rst --- CONTRIBUTORS.rst | 53 ++++++++++++++++++++++++++++++++++++++++++++++++ README.rst | 2 +- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTORS.rst diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst new file mode 100644 index 0000000..e251b41 --- /dev/null +++ b/CONTRIBUTORS.rst @@ -0,0 +1,53 @@ +============ +Contributors +============ + +This file contains a list of every person who has contributed code to +Overviewer. It was created from the git commit log, and should include +everyone, but we may have missed a few. + +Not currently included (but hopefully soon) are countless testers and bug +reporters that helped fixed the many bugs that have popped up in the course of +development. + +--------------- +Original Author +--------------- + + * Andrew Brown + +------------------------- +Long-term Contributions +------------------------- + +These contributors have made many changes, over a fairly long time span, or +for many different parts of the code. + + * Alejandro Aguilera + * Andrew Chin + * Aaron Griffith + * Alex Headley + * Alex Jurkiewicz + * Xon + +------------------------ +Short-term Contributions +------------------------ + +These contributors have made specific changes for a particular bug fix or +feature. + + * arrai + * Kyle Brantley + * cbarber + * Alex Cline + * Stephen Fluin + * Benjamin Herr + * Ryan Hitchman + * Jenny + * Michael Jensen + * Ryan McCue + * Morlok8k + * Gregory Short + * Sam Steele + * timwolla diff --git a/README.rst b/README.rst index 616e8d2..1d0e9ab 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ ==================== Minecraft Overviewer ==================== -By Andrew Brown and contributors +By Andrew Brown and contributors (see CONTRIBUTORS.rst). http://github.com/brownan/Minecraft-Overviewer From 4036c4a55000aee2f1aead11fece339a16bc4c6d Mon Sep 17 00:00:00 2001 From: Michael Writhe Date: Wed, 13 Apr 2011 10:56:24 -0700 Subject: [PATCH 170/213] indentation and comments added. --- web_assets/functions.js | 262 ++++++++++++++++++++-------------------- 1 file changed, 130 insertions(+), 132 deletions(-) diff --git a/web_assets/functions.js b/web_assets/functions.js index 7bb170f..6c9bda7 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -1,14 +1,15 @@ +// var def +var map; // god of the overviewer... bow before the google api. var markerCollection = {}; // holds groups of markers - -var map; - -var markersInit = false; -var regionsInit = false; +var markersInit = false; // only have to load the markers once, this just makes sure we only do it once +var regionsInit = false; // same thing for the regions var prevInfoWindow = null; +// add a popup info window to the marker and then the marker to the map. +// marker is the clickable image on the map with all data. +// item is just the same item in the markers.js function prepareSignMarker(marker, item) { - var c = "

" + item.msg.replace(/\n/g,"
") + "

"; var infowindow = new google.maps.InfoWindow({content: c }); @@ -17,51 +18,53 @@ function prepareSignMarker(marker, item) { prevInfoWindow.close() infowindow.open(map,marker); prevInfoWindow = infowindow - }); + }); } +// reusable function for making drop down menus. +// title = string +// items = array function createDropDown(title, items) { var control = document.createElement("DIV"); control.id = "customControl"; // let's let a style sheet do most of the styling here + var controlText = document.createElement("DIV"); + controlText.innerHTML = title; + var controlBorder = document.createElement("DIV"); controlBorder.id="top"; control.appendChild(controlBorder); - - var controlText = document.createElement("DIV"); - controlBorder.appendChild(controlText); - controlText.innerHTML = title; - var dropdownDiv = document.createElement("DIV"); - - - $(controlText).click(function() { - $(dropdownDiv).toggle(); - - }); - - dropdownDiv.id="dropDown"; control.appendChild(dropdownDiv); dropdownDiv.innerHTML=""; + // add the functionality to toggle visibility of the items + $(controlText).click(function() { + $(dropdownDiv).toggle(); + }); + + // add that control box we've made back to the map. map.controls[google.maps.ControlPosition.TOP_RIGHT].push(control); for (idx in items) { + // create the visible elements of the item var item = items[idx]; - //console.log(item); + //console.log(item); // debug var d = document.createElement("div"); var n = document.createElement("input"); n.type="checkbox"; + // give it a name $(n).data("label",item.label); jQuery(n).click(function(e) { - item.action(idx, item.label, e.target.checked); - }); + item.action(idx, item.label, e.target.checked); + }); + // if its checked, its gotta do something, do that here. if (item.checked) { n.checked = true; item.action(idx, item.label, item.checked); @@ -74,32 +77,28 @@ function createDropDown(title, items) { } } +// need to define the controls including the compass and layer controls. top right! +// input variables are for chumps... and reusable functions. this is neither. function drawMapControls() { - // viewstate link + // viewstate link (little link to where you're looking at the map, normally bottom left) var viewStateDiv = document.createElement('DIV'); - - // - viewStateDiv.id="link"; - - - map.controls[google.maps.ControlPosition.BOTTOM_RIGHT].push(viewStateDiv); - + // add it to the map, bottom left. + map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(viewStateDiv); // compass rose, in the top right corner var compassDiv = document.createElement('DIV'); - compassDiv.style.padding = '5px'; - var compassImg = document.createElement('IMG'); compassImg.src="compass.png"; compassDiv.appendChild(compassImg); - compassDiv.index = 0; + // add it to the map, top right. map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv); + // only need to create the control if there are items in the list. as definned in config.js if (signGroups.length > 0) { // signpost display control @@ -107,9 +106,9 @@ function drawMapControls() { for (idx in signGroups) { var item = signGroups[idx]; items.push({"label": item.label, "checked": item.checked, - "action": function(n, l, checked) { - jQuery.each(markerCollection[l], function(i,elem) {elem.setVisible(checked);}); - }}); + "action": function(n, l, checked) { + jQuery.each(markerCollection[l], function(i,elem) {elem.setVisible(checked);}); + }}); } createDropDown("Signposts", items); } @@ -121,26 +120,26 @@ function drawMapControls() { for (idx in overlayMapTypes) { var overlay = overlayMapTypes[idx]; items.push({"label": overlay.name, "checked": false, - "action": function(i, l, checked) { - if (checked) { - map.overlayMapTypes.push(overlay); - } else { - var idx_to_delete = -1; - map.overlayMapTypes.forEach(function(e, j) { - if (e == overlay) { - idx_to_delete = j; - } - }); - if (idx_to_delete >= 0) { - map.overlayMapTypes.removeAt(idx_to_delete); - } + "action": function(i, l, checked) { + if (checked) { + map.overlayMapTypes.push(overlay); + } else { + var idx_to_delete = -1; + map.overlayMapTypes.forEach(function(e, j) { + if (e == overlay) { idx_to_delete = j; } + }); + if (idx_to_delete >= 0) { + map.overlayMapTypes.removeAt(idx_to_delete); } - }}); + } + }}); } createDropDown("Overlays", items); } } +// will be recoded by pi, currently always displays all regions all the time. +// parse the data as definned in the regions.js function initRegions() { if (regionsInit) { return; } @@ -180,12 +179,11 @@ function initRegions() { } } - - +// will initalize all the markers data as found in markers.js +// may need to be reviewed by agrif or someone else... little finicky right now. function initMarkers() { - if (markersInit) { return; } - - markersInit = true; + if (markersInit) { return; } // oh, we've already done this? nevermind, exit the function. + markersInit = true; // now that we've started, dont have to do it twice. // first, give all collections an empty array to work with for (i in signGroups) { @@ -206,8 +204,7 @@ function initMarkers() { map: map, title: jQuery.trim(item.msg), icon: iconURL - }); - + }); continue; } @@ -220,11 +217,11 @@ function initMarkers() { if (testfunc(item)) { matched = true; - if (item.type == 'sign') { iconURL = 'signpost_icon.png';} + // can add custom types of images for externally definned item types, like 'command' here. + if (item.type == 'sign') { iconURL = 'signpost_icon.png'; } - //console.log(signGroup.icon); - if (signGroup.icon) { iconURL = signGroup.icon; - } + //console.log(signGroup.icon); //debug + if (signGroup.icon) { iconURL = signGroup.icon; } var converted = fromWorldToLatLng(item.x, item.y, item.z); var marker = new google.maps.Marker({position: converted, @@ -232,7 +229,7 @@ function initMarkers() { title: jQuery.trim(item.msg), icon: iconURL, visible: false - }); + }); markerCollection[label].push(marker); @@ -271,7 +268,7 @@ function initMarkers() { } } - +// update the link in the viewstate. function makeLink() { var a=location.href.substring(0,location.href.lastIndexOf(location.search)) + "?lat=" + map.getCenter().lat().toFixed(6) @@ -280,6 +277,7 @@ function makeLink() { document.getElementById("link").innerHTML = a; } +// load the map up and add all the functions relevant stuff to the map. function initialize() { var query = location.search.substring(1); @@ -364,87 +362,87 @@ function initialize() { } - // 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() { +// 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) { +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) { +MCMapProjection.prototype.fromPointToLatLng = function(point) { var lng = point.x * this.inverseTileSize; var lat = point.y * this.inverseTileSize; return new google.maps.LatLng(lat, lng); - }; +}; - // helper to get map LatLng from world coordinates - // takes arguments in X, Y, Z order - // (arguments are *out of order*, because within the function we use - // the axes like the rest of Minecraft Overviewer -- with the Z and Y - // flipped from normal minecraft usage.) - function fromWorldToLatLng(x, z, y) - { +// helper to get map LatLng from world coordinates +// takes arguments in X, Y, Z order +// (arguments are *out of order*, because within the function we use +// the axes like the rest of Minecraft Overviewer -- with the Z and Y +// flipped from normal minecraft usage.) +function fromWorldToLatLng(x, z, y) +{ // the width and height of all the highest-zoom tiles combined, inverted var perPixel = 1.0 / (config.tileSize * Math.pow(2, config.maxZoom)); - + // This information about where the center column is may change with a different // drawing implementation -- check it again after any drawing overhauls! - + // point (0, 0, 127) is at (0.5, 0.0) of tile (tiles/2 - 1, tiles/2) // so the Y coordinate is at 0.5, and the X is at 0.5 - ((tileSize / 2) / (tileSize * 2^maxZoom)) // or equivalently, 0.5 - (1 / 2^(maxZoom + 1)) var lng = 0.5 - (1.0 / Math.pow(2, config.maxZoom + 1)); var lat = 0.5; - + // the following metrics mimic those in ChunkRenderer.chunk_render in "chunk.py" // or, equivalently, chunk_render in src/iterate.c - + // each block on X axis adds 12px to x and subtracts 6px from y lng += 12 * x * perPixel; lat -= 6 * x * perPixel; - + // each block on Y axis adds 12px to x and adds 6px to y lng += 12 * y * perPixel; lat += 6 * y * perPixel; - + // each block down along Z adds 12px to y lat += 12 * (128 - z) * perPixel; // add on 12 px to the X coordinate to center our point lng += 12 * perPixel; - + return new google.maps.LatLng(lat, lng); - } +} function getTileUrlGenerator(path, path_base) { - return function(tile, zoom) { - var url = path; - var url_base = ( path_base ? path_base : '' ); - 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); + return function(tile, zoom) { + var url = path; + var url_base = ( path_base ? path_base : '' ); + 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; - if(config.cacheMinutes > 0) { - var d = new Date(); - url += '?c=' + Math.floor(d.getTime() / (1000 * 60 * config.cacheMinutes)); - } - return(url_base + url); - } + url = url + '.' + config.fileExt; + if(config.cacheMinutes > 0) { + var d = new Date(); + url += '?c=' + Math.floor(d.getTime() / (1000 * 60 * config.cacheMinutes)); + } + return(url_base + url); + } } var MCMapOptions = new Array; @@ -453,39 +451,39 @@ var mapTypeIdDefault = null; var mapTypeIds = []; var overlayMapTypes = []; for (idx in mapTypeData) { - var view = mapTypeData[idx]; + var view = mapTypeData[idx]; - MCMapOptions[view.label] = { - getTileUrl: getTileUrlGenerator(view.path, view.base), - tileSize: new google.maps.Size(config.tileSize, config.tileSize), - maxZoom: config.maxZoom, - minZoom: 0, - isPng: !(config.fileExt.match(/^png$/i) == null) - }; + MCMapOptions[view.label] = { + getTileUrl: getTileUrlGenerator(view.path, view.base), + tileSize: new google.maps.Size(config.tileSize, config.tileSize), + maxZoom: config.maxZoom, + minZoom: 0, + isPng: !(config.fileExt.match(/^png$/i) == null) + }; - MCMapType[view.label] = new google.maps.ImageMapType(MCMapOptions[view.label]); - MCMapType[view.label].name = view.label; - MCMapType[view.label].alt = "Minecraft " + view.label + " Map"; - MCMapType[view.label].projection = new MCMapProjection(); - - if (view.overlay) { - overlayMapTypes.push(MCMapType[view.label]); - } else { + MCMapType[view.label] = new google.maps.ImageMapType(MCMapOptions[view.label]); + MCMapType[view.label].name = view.label; + MCMapType[view.label].alt = "Minecraft " + view.label + " Map"; + MCMapType[view.label].projection = new MCMapProjection(); + + if (view.overlay) { + overlayMapTypes.push(MCMapType[view.label]); + } else { if (mapTypeIdDefault == null) { - mapTypeIdDefault = 'mcmap' + view.label; + mapTypeIdDefault = 'mcmap' + view.label; } mapTypeIds.push('mcmap' + view.label); } } - function CoordMapType() { - } +function CoordMapType() { +} - function CoordMapType(tileSize) { +function CoordMapType(tileSize) { this.tileSize = tileSize; - } +} - CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) { +CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) { var div = ownerDocument.createElement('DIV'); div.innerHTML = "(" + coord.x + ", " + coord.y + ", " + zoom + ")"; div.innerHTML += "
"; @@ -497,4 +495,4 @@ for (idx in mapTypeData) { div.style.borderWidth = '1px'; div.style.borderColor = '#AAAAAA'; return div; - }; +}; \ No newline at end of file From 1b74ab4b4513854bc27150d45235702e9932375a Mon Sep 17 00:00:00 2001 From: Michael Writhe Date: Wed, 13 Apr 2011 14:45:24 -0600 Subject: [PATCH 171/213] Added the spawn button --- web_assets/functions.js | 31 +++++++++++++++++++++++++++++++ web_assets/style.css | 7 +++++++ 2 files changed, 38 insertions(+) diff --git a/web_assets/functions.js b/web_assets/functions.js index 6c9bda7..6cc1efb 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -77,6 +77,31 @@ function createDropDown(title, items) { } } +function HomeControl(controlDiv, map) { + + controlDiv.style.padding = '5px'; + + // Set CSS for the control border + var controlUI = document.createElement('DIV'); + control.id='customControl'; + //controlUI.className='controlUI'; + controlUI.title = 'Click to set the map to Spawn'; + controlDiv.appendChild(controlUI); + + // Set CSS for the control interior + var controlText = document.createElement('DIV'); + //controlText.className='controlText'; + controlText.innerHTML = 'Spawn'; + controlUI.appendChild(controlText); + + // Setup the click event listeners: simply set the map to map center as definned below + google.maps.event.addDomListener(controlUI, 'click', function() { + map.panTo(defaultCenter); + }); + +} + + // need to define the controls including the compass and layer controls. top right! // input variables are for chumps... and reusable functions. this is neither. function drawMapControls() { @@ -96,6 +121,12 @@ function drawMapControls() { compassDiv.index = 0; // add it to the map, top right. map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv); + + // Spawn button + var homeControlDiv = document.createElement('DIV'); + var homeControl = new HomeControl(homeControlDiv, map); + homeControlDiv.index = 1; + map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv); // only need to create the control if there are items in the list. as definned in config.js diff --git a/web_assets/style.css b/web_assets/style.css index 32389bf..8585eb3 100644 --- a/web_assets/style.css +++ b/web_assets/style.css @@ -40,6 +40,13 @@ body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; } display: none; } +#customControl > div#button { + border: 1px solid #000; + font-size: 12px; + background-color: #fff; + display: none; +} + #link { background-color: #fff; /* fallback */ From 33bfdca28a9b508b427f50ed8a077e061c3f6747 Mon Sep 17 00:00:00 2001 From: Michael Writhe Date: Wed, 13 Apr 2011 15:55:49 -0600 Subject: [PATCH 172/213] fixed css references for Spawn Button --- web_assets/functions.js | 42 ++++++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/web_assets/functions.js b/web_assets/functions.js index 6cc1efb..c1430b7 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -2,7 +2,8 @@ var map; // god of the overviewer... bow before the google api. var markerCollection = {}; // holds groups of markers var markersInit = false; // only have to load the markers once, this just makes sure we only do it once -var regionsInit = false; // same thing for the regions +var regionCollection = {}; // holds groups of regions +var regionsInit = false; // only have to load the regions once, this just makes sure we only do it once var prevInfoWindow = null; @@ -82,21 +83,22 @@ function HomeControl(controlDiv, map) { controlDiv.style.padding = '5px'; // Set CSS for the control border - var controlUI = document.createElement('DIV'); - control.id='customControl'; - //controlUI.className='controlUI'; - controlUI.title = 'Click to set the map to Spawn'; - controlDiv.appendChild(controlUI); + var control = document.createElement('DIV'); + control.id='top'; + control.title = 'Click to set the map to Spawn'; + controlDiv.appendChild(control); // Set CSS for the control interior var controlText = document.createElement('DIV'); - //controlText.className='controlText'; controlText.innerHTML = 'Spawn'; - controlUI.appendChild(controlText); + controlText.id='button'; + control.appendChild(controlText); // Setup the click event listeners: simply set the map to map center as definned below - google.maps.event.addDomListener(controlUI, 'click', function() { - map.panTo(defaultCenter); + google.maps.event.addDomListener(control, 'click', function() { + map.panTo(fromWorldToLatLng(config.center[0], + config.center[1], + config.center[2])); }); } @@ -125,6 +127,7 @@ function drawMapControls() { // Spawn button var homeControlDiv = document.createElement('DIV'); var homeControl = new HomeControl(homeControlDiv, map); + homeControlDiv.id = "customControl"; homeControlDiv.index = 1; map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv); @@ -173,11 +176,23 @@ function drawMapControls() { // parse the data as definned in the regions.js function initRegions() { if (regionsInit) { return; } - regionsInit = true; + + /* remove comment on this if we decide to add regionGroups in the config.js + this would let us selectivley show groups of regions based on the name of the region, or flags set. + could be good... + + for (i in regionGroups) { + regionCollection[regionGroups[i].label] = []; + } + remove next line if this is kept. + */ + regionCollection['All Regions'] = []; for (i in regionData) { var region = regionData[i]; + + // pull all the points out of the regions file. var converted = new google.maps.MVCArray(); for (j in region.path) { var point = region.path[j]; @@ -185,7 +200,7 @@ function initRegions() { } if (region.closed) { - new google.maps.Polygon({clickable: false, + var region = new google.maps.Polygon({clickable: false, geodesic: false, map: map, strokeColor: region.color, @@ -197,7 +212,7 @@ function initRegions() { paths: converted }); } else { - new google.maps.Polyline({clickable: false, + var region = new google.maps.Polyline({clickable: false, geodesic: false, map: map, strokeColor: region.color, @@ -207,6 +222,7 @@ function initRegions() { path: converted }); } + regionCollection['All Regions'].push(region); //if we add groups to config.js this will need to be changed. } } From b89ea8db8c16523de47318f139f468c6e5fdf0fa Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Wed, 13 Apr 2011 19:53:03 +0200 Subject: [PATCH 173/213] Working cave rendermode without tinting. --- chunk.py | 16 +++- setup.py | 2 +- src/rendermode-cave.c | 190 ++++++++++++++++++++++++++++++++++++++++++ src/rendermodes.c | 1 + src/rendermodes.h | 14 ++++ 5 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 src/rendermode-cave.c diff --git a/chunk.py b/chunk.py index 977fdfa..9db1194 100644 --- a/chunk.py +++ b/chunk.py @@ -284,7 +284,14 @@ class ChunkRenderer(object): self._load_up_right() return self._up_right_blocks up_right_blocks = property(_load_up_right_blocks) - + + def _load_up_right_skylight(self): + """Loads and returns lower-right skylight array""" + if not hasattr(self, "_up_right_skylight"): + self._load_up_right() + return self._up_right_skylight + up_right_skylight = property(_load_up_right_skylight) + def _load_up_left(self): """Loads and sets data from upper-left chunk""" chunk_path = self.world.get_region_path(self.chunkX, self.chunkY - 1) @@ -305,6 +312,13 @@ class ChunkRenderer(object): return self._up_left_blocks up_left_blocks = property(_load_up_left_blocks) + def _load_up_left_skylight(self): + """Loads and returns lower-right skylight array""" + if not hasattr(self, "_up_left_skylight"): + self._load_up_left() + return self._up_left_skylight + up_left_skylight = property(_load_up_left_skylight) + def generate_pseudo_ancildata(self,x,y,z,blockid, north_position = 0 ): """ Generates a pseudo ancillary data for blocks that depend of what are surrounded and don't have ancillary data diff --git a/setup.py b/setup.py index 0a36bcf..32360fb 100644 --- a/setup.py +++ b/setup.py @@ -55,7 +55,7 @@ except: pil_include = [] c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/endian.c'] -c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c', 'src/rendermode-night.c', 'src/rendermode-spawn.c'] +c_overviewer_files += ['src/rendermodes.c', 'src/rendermode-normal.c', 'src/rendermode-lighting.c', 'src/rendermode-night.c', 'src/rendermode-spawn.c', 'src/rendermode-cave.c'] c_overviewer_files += ['src/Draw.c'] c_overviewer_includes = ['src/overviewer.h', 'src/rendermodes.h'] setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include] + pil_include, depends=c_overviewer_includes, extra_link_args=[])) diff --git a/src/rendermode-cave.c b/src/rendermode-cave.c new file mode 100644 index 0000000..85d7d93 --- /dev/null +++ b/src/rendermode-cave.c @@ -0,0 +1,190 @@ +/* + * This file is part of the Minecraft Overviewer. + * + * Minecraft Overviewer 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. + * + * Minecraft Overviewer 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 the Overviewer. If not, see . + */ + +#include "overviewer.h" +#include +//~ +//~ /* figures out the black_coeff from a given skylight and blocklight, used in + //~ lighting calculations -- note this is *different* from the one in + //~ rendermode-lighting.c (the "skylight - 11" part) */ +//~ static float calculate_darkness(unsigned char skylight, unsigned char blocklight) { + //~ return 1.0f - powf(0.8f, 15.0 - MAX(blocklight, skylight - 11)); +//~ } + +static int +rendermode_cave_occluded(void *data, RenderState *state) { + int x = state->x, y = state->y, z = state->z; + RenderModeCave* self; + self = (RenderModeCave *)data; + + /* check if the block is touching skylight */ + if (z != 127) { + + if (getArrayByte3D(self->skylight, x, y, z+1) != 0) { + return 1; + } + + if ((x == 15)) { + if (self->up_right_skylight != Py_None) { + if (getArrayByte3D(self->up_right_skylight, 0, y, z) != 0) { + return 1; + } + } + } else { + if (getArrayByte3D(self->skylight, x+1, y, z) != 0) { + return 1; + } + } + + if (x == 0) { + if (self->left_skylight != Py_None) { + if (getArrayByte3D(self->left_skylight, 15, y, z) != 0) { + return 1; + } + } + } else { + if (getArrayByte3D(self->skylight, x-1, y, z) != 0) { + return 1; + } + } + + if (y == 15) { + if (self->right_skylight != Py_None) { + if (getArrayByte3D(self->right_skylight, 0, y, z) != 0) { + return 1; + } + } + } else { + if (getArrayByte3D(self->skylight, x, y+1, z) != 0) { + return 1; + } + } + + if (y == 0) { + if (self->up_left_skylight != Py_None) { + if (getArrayByte3D(self->up_left_skylight, 15, y, z) != 0) { + return 1; + } + } + } else { + if (getArrayByte3D(self->skylight, x, y-1, z) != 0) { + return 1; + } + } + + /* check for normal occlusion */ + /* use ajacent chunks, if not you get blocks spreaded in chunk edges */ + if ( (x == 0) && (y != 15) ) { + if (state->left_blocks != Py_None) { + if (!is_transparent(getArrayByte3D(state->left_blocks, 15, y, z)) && + !is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) && + !is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) { + return 1; + } + } else { + return 1; + } + } + + if ( (x != 0) && (y == 15) ) { + if (state->right_blocks != Py_None) { + if (!is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) && + !is_transparent(getArrayByte3D(state->right_blocks, x, 0, z)) && + !is_transparent(getArrayByte3D(state->blocks, x, y, z+1))) { + return 1; + } + } else { + return 1; + } + } + + if ( (x == 0) && (y == 15) ) { + if ((state->left_blocks != Py_None) && + (state->right_blocks != Py_None)) { + if (!is_transparent(getArrayByte3D(state->left_blocks, 15, y, z)) && + !is_transparent(getArrayByte3D(state->right_blocks, x, 0, z)) && + !is_transparent(getArrayByte3D(state->blocks, x, y, z+1))) { + return 1; + } + } else { + return 1; + } + } + + if ( (x != 0) && (y != 15) && + !is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) && + !is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) && + !is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) { + return 1; + } + + } else { /* if z == 127 don't skip */ + return 1; + } + + return 0; +} + +static int +rendermode_cave_start(void *data, RenderState *state) { + RenderModeCave* self; + self = (RenderModeCave *)data; + + /* first, chain up */ + int ret = rendermode_normal.start(data, state); + if (ret != 0) + return ret; + + /* if there's skylight we are in the surface! */ + self->skylight = PyObject_GetAttrString(state->self, "skylight"); + self->left_skylight = PyObject_GetAttrString(state->self, "left_skylight"); + self->right_skylight = PyObject_GetAttrString(state->self, "right_skylight"); + self->up_left_skylight = PyObject_GetAttrString(state->self, "up_left_skylight"); + self->up_right_skylight = PyObject_GetAttrString(state->self, "up_right_skylight"); + + return 0; +} + +static void +rendermode_cave_finish(void *data, RenderState *state) { + RenderModeCave* self; + self = (RenderModeCave *)data; + + Py_DECREF(self->skylight); + Py_DECREF(self->left_skylight); + Py_DECREF(self->right_skylight); + Py_DECREF(self->up_left_skylight); + Py_DECREF(self->up_right_skylight); + + rendermode_normal.finish(data, state); +} + +static void +rendermode_cave_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { + /* nothing special to do */ + rendermode_normal.draw(data, state, src, mask); + +} + +RenderModeInterface rendermode_cave = { + "cave", "render only caves in normal mode", + sizeof(RenderModeCave), + rendermode_cave_start, + rendermode_cave_finish, + rendermode_cave_occluded, + rendermode_cave_draw, +}; diff --git a/src/rendermodes.c b/src/rendermodes.c index 0ef5550..08ac140 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -26,6 +26,7 @@ static RenderModeInterface *render_modes[] = { &rendermode_lighting, &rendermode_night, &rendermode_spawn, + &rendermode_cave, NULL }; diff --git a/src/rendermodes.h b/src/rendermodes.h index 3027a7f..babc551 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -119,4 +119,18 @@ typedef struct { } RenderModeSpawn; extern RenderModeInterface rendermode_spawn; +/* CAVE */ +typedef struct { + /* render blocks with lighting mode */ + RenderModeNormal parent; + /* data used to know where the surface is */ + PyObject *skylight; + PyObject *left_skylight; + PyObject *right_skylight; + PyObject *up_left_skylight; + PyObject *up_right_skylight; + +} RenderModeCave; +extern RenderModeInterface rendermode_cave; + #endif /* __RENDERMODES_H_INCLUDED__ */ From 1fb77cf6e6c15d53bc2bbec95794ce143f93691b Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Wed, 13 Apr 2011 20:10:44 +0200 Subject: [PATCH 174/213] Add code to remove lakes and seas. --- src/rendermode-cave.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/rendermode-cave.c b/src/rendermode-cave.c index 85d7d93..f532a00 100644 --- a/src/rendermode-cave.c +++ b/src/rendermode-cave.c @@ -27,7 +27,7 @@ static int rendermode_cave_occluded(void *data, RenderState *state) { - int x = state->x, y = state->y, z = state->z; + int x = state->x, y = state->y, z = state->z, dz = 0; RenderModeCave* self; self = (RenderModeCave *)data; @@ -132,10 +132,30 @@ rendermode_cave_occluded(void *data, RenderState *state) { return 1; } - } else { /* if z == 127 don't skip */ + } else { /* if z == 127 skip */ return 1; } + /* check for lakes and seas and don't render them */ + /* at this point of the code the block has no skylight + * and is not occluded, but a deep sea can fool these + * 2 tests */ + + if ((getArrayByte3D(state->blocks, x, y, z) == 9) || + (getArrayByte3D(state->blocks, x, y, z+1) == 9)) { + + for (dz = z+1; dz < 127; dz++) { + if (getArrayByte3D(self->skylight, x, y, dz) != 0) { + return 1; + } + if (getArrayByte3D(state->blocks, x, y, dz) != 9) { + /* we are out of the water! */ + return 0; + } + } + } + + return 0; } From 8165ce979d2f3411b840fd9d0161f230331944c8 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Thu, 14 Apr 2011 00:32:12 +0200 Subject: [PATCH 175/213] Add depth tinting to cave rendermode. --- chunk.py | 6 ++++-- src/rendermode-cave.c | 29 +++++++++++++++++++++++++---- src/rendermodes.h | 4 ++++ 3 files changed, 33 insertions(+), 6 deletions(-) diff --git a/chunk.py b/chunk.py index 9db1194..a5766d6 100644 --- a/chunk.py +++ b/chunk.py @@ -472,8 +472,10 @@ def generate_depthcolors(): g = 0 b = 0 for z in range(128): - img = Image.new("RGB", (24,24), (r,g,b)) - depth_colors.append(img) + depth_colors.append(r) + depth_colors.append(g) + depth_colors.append(b) + if z < 32: g += 7 elif z < 64: diff --git a/src/rendermode-cave.c b/src/rendermode-cave.c index f532a00..88a7d83 100644 --- a/src/rendermode-cave.c +++ b/src/rendermode-cave.c @@ -144,12 +144,13 @@ rendermode_cave_occluded(void *data, RenderState *state) { if ((getArrayByte3D(state->blocks, x, y, z) == 9) || (getArrayByte3D(state->blocks, x, y, z+1) == 9)) { - for (dz = z+1; dz < 127; dz++) { + for (dz = z+1; dz < 127; dz++) { /* go up and check for skylight */ if (getArrayByte3D(self->skylight, x, y, dz) != 0) { return 1; } if (getArrayByte3D(state->blocks, x, y, dz) != 9) { - /* we are out of the water! */ + /* we are out of the water! and there's no skylight + * , i.e. is a cave lake or something similar */ return 0; } } @@ -176,6 +177,10 @@ rendermode_cave_start(void *data, RenderState *state) { self->up_left_skylight = PyObject_GetAttrString(state->self, "up_left_skylight"); self->up_right_skylight = PyObject_GetAttrString(state->self, "up_right_skylight"); + /* colors for tinting */ + self->depth_colors = PyObject_GetAttrString(state->chunk, "depth_colors"); + + return 0; } @@ -190,14 +195,30 @@ rendermode_cave_finish(void *data, RenderState *state) { Py_DECREF(self->up_left_skylight); Py_DECREF(self->up_right_skylight); + Py_DECREF(self->depth_colors); + rendermode_normal.finish(data, state); } static void rendermode_cave_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { - /* nothing special to do */ + RenderModeCave* self; + self = (RenderModeCave *)data; + + int z = state->z; + int r = 0, g = 0, b = 0; + + /* draw the normal block */ rendermode_normal.draw(data, state, src, mask); - + + /* get the colors and tint and tint */ + /* TODO TODO for a nether mode there isn't tinting! */ + r = PyInt_AsLong(PyList_GetItem(self->depth_colors, 0 + z*3)); + g = PyInt_AsLong(PyList_GetItem(self->depth_colors, 1 + z*3)); + b = PyInt_AsLong(PyList_GetItem(self->depth_colors, 2 + z*3)); + + tint_with_mask(state->img, r, g, b, mask, state->imgx, state->imgy, 0, 0); + } RenderModeInterface rendermode_cave = { diff --git a/src/rendermodes.h b/src/rendermodes.h index babc551..70e9c46 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -123,12 +123,16 @@ extern RenderModeInterface rendermode_spawn; typedef struct { /* render blocks with lighting mode */ RenderModeNormal parent; + /* data used to know where the surface is */ PyObject *skylight; PyObject *left_skylight; PyObject *right_skylight; PyObject *up_left_skylight; PyObject *up_right_skylight; + + /* colors used for tinting */ + PyObject *depth_colors; } RenderModeCave; extern RenderModeInterface rendermode_cave; From 183d278f810b60eabf185fd2450c8e16f844b68b Mon Sep 17 00:00:00 2001 From: Michael Writhe Date: Wed, 13 Apr 2011 21:59:52 -0600 Subject: [PATCH 176/213] first steps to getting toggle for regions working --- config.js | 5 ++ web_assets/functions.js | 101 ++++++++++++++++++++++++++++------------ 2 files changed, 77 insertions(+), 29 deletions(-) diff --git a/config.js b/config.js index 43b4982..0954116 100644 --- a/config.js +++ b/config.js @@ -33,6 +33,11 @@ var signGroups = [ {label: "All", match: function(s) {return true}}, ]; +//piTODO: document this +var regionGroups = [ + {label: "WorldGuard", match: function(s) {return true}}, +]; + /* mapTypeData -- a list of alternate map renderings available. At least one rendering must be * listed. When more than one are provided, controls to switch between them are provided, with * the first one being the default. diff --git a/web_assets/functions.js b/web_assets/functions.js index c1430b7..ea59263 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -23,6 +23,21 @@ function prepareSignMarker(marker, item) { } +// add a popup info window to the region and then the region to the map. +// region is the clickable image on the map with all data. +// item is just the same item in the regions.js +function prepareRegionShape(shape, region) { + var c = "

" + region.label + "

"; + var infowindow = new google.maps.InfoWindow({content: c }); + google.maps.event.addListener(shape, 'click', function() { + if (prevInfoWindow) + prevInfoWindow.close() + infowindow.open(map,shape); + prevInfoWindow = infowindow + }); + +} + // reusable function for making drop down menus. // title = string // items = array @@ -85,7 +100,7 @@ function HomeControl(controlDiv, map) { // Set CSS for the control border var control = document.createElement('DIV'); control.id='top'; - control.title = 'Click to set the map to Spawn'; + control.title = 'Click to center the map on the Spawn'; controlDiv.appendChild(control); // Set CSS for the control interior @@ -147,6 +162,29 @@ function drawMapControls() { createDropDown("Signposts", items); } + // if there are any regions data, lets show the option to hide/show them. + if (regionGroups.length > 0) { + // region display control + + var items = []; + for (idx in regionGroups) { + var item = regionGroups[idx]; + items.push({"label": item.label, "checked": item.checked, + "action": function(n, l, checked) { + if (checked) { + jQuery.each(regionCollection[l], function(i,elem) { + elem.setVisible('visible'); + }); + } else { + jQuery.each(regionCollection[l], function(i,elem) { + elem.setVisible('hidden'); + }); + } + }}); + } + createDropDown("Regions", items); + } + if (overlayMapTypes.length > 0) { // overlay maps control @@ -178,16 +216,9 @@ function initRegions() { if (regionsInit) { return; } regionsInit = true; - /* remove comment on this if we decide to add regionGroups in the config.js - this would let us selectivley show groups of regions based on the name of the region, or flags set. - could be good... - for (i in regionGroups) { regionCollection[regionGroups[i].label] = []; } - remove next line if this is kept. - */ - regionCollection['All Regions'] = []; for (i in regionData) { var region = regionData[i]; @@ -198,31 +229,43 @@ function initRegions() { var point = region.path[j]; converted.push(fromWorldToLatLng(point.x, point.y, point.z)); } + + for (idx in regionGroups) { + var regionGroup = regionGroups[idx]; + var testfunc = regionGroup.match; + var label = regionGroup.label; - if (region.closed) { - var region = new google.maps.Polygon({clickable: false, - geodesic: false, - map: map, - strokeColor: region.color, - strokeOpacity: region.opacity, - strokeWeight: 2, - fillColor: region.color, - fillOpacity: region.opacity * 0.25, - zIndex: i, - paths: converted + if (region.closed) { + var shape = new google.maps.Polygon({ + name: region.label, + clickable: false, + geodesic: false, + map: map, + strokeColor: region.color, + strokeOpacity: region.opacity, + strokeWeight: 2, + fillColor: region.color, + fillOpacity: region.opacity * 0.25, + zIndex: i, + paths: converted }); - } else { - var region = new google.maps.Polyline({clickable: false, - geodesic: false, - map: map, - strokeColor: region.color, - strokeOpacity: region.opacity, - strokeWeight: 2, - zIndex: i, - path: converted + } else { + var shape = new google.maps.Polyline({ + name: region.label, + clickable: false, + geodesic: false, + map: map, + strokeColor: region.color, + strokeOpacity: region.opacity, + strokeWeight: 2, + zIndex: i, + path: converted }); + } + regionCollection[label].push(shape); + prepareRegionShape(shape, region); + } - regionCollection['All Regions'].push(region); //if we add groups to config.js this will need to be changed. } } From d51d863bed28d1c8eed8c685a30c168e279bbfeb Mon Sep 17 00:00:00 2001 From: Michael Writhe Date: Thu, 14 Apr 2011 08:30:56 -0600 Subject: [PATCH 177/213] toggle regions --- web_assets/functions.js | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/web_assets/functions.js b/web_assets/functions.js index ea59263..c109603 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -171,15 +171,9 @@ function drawMapControls() { var item = regionGroups[idx]; items.push({"label": item.label, "checked": item.checked, "action": function(n, l, checked) { - if (checked) { - jQuery.each(regionCollection[l], function(i,elem) { - elem.setVisible('visible'); - }); - } else { - jQuery.each(regionCollection[l], function(i,elem) { - elem.setVisible('hidden'); - }); - } + jQuery.each(regionCollection[l], function(i,elem) { + elem.setMap(checked ? map : null); + }); }}); } createDropDown("Regions", items); @@ -240,7 +234,7 @@ function initRegions() { name: region.label, clickable: false, geodesic: false, - map: map, + map: null, strokeColor: region.color, strokeOpacity: region.opacity, strokeWeight: 2, @@ -254,7 +248,7 @@ function initRegions() { name: region.label, clickable: false, geodesic: false, - map: map, + map: null, strokeColor: region.color, strokeOpacity: region.opacity, strokeWeight: 2, @@ -263,8 +257,6 @@ function initRegions() { }); } regionCollection[label].push(shape); - prepareRegionShape(shape, region); - } } } From 5cc1c55ff11871f07673ae777b1e0f27ea4a9146 Mon Sep 17 00:00:00 2001 From: Michael Writhe Date: Thu, 14 Apr 2011 08:32:49 -0600 Subject: [PATCH 178/213] regions toggle --- web_assets/functions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_assets/functions.js b/web_assets/functions.js index c109603..e2d1123 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -172,7 +172,7 @@ function drawMapControls() { items.push({"label": item.label, "checked": item.checked, "action": function(n, l, checked) { jQuery.each(regionCollection[l], function(i,elem) { - elem.setMap(checked ? map : null); + elem.setMap(checked ? map : null); // Thanks to LeastWeasel for this line! }); }}); } From eac56a6513a8f1942b962cae93d2050e3ec3a1fc Mon Sep 17 00:00:00 2001 From: Michael Writhe Date: Thu, 14 Apr 2011 09:32:47 -0600 Subject: [PATCH 179/213] clickable and now toggleable regions --- config.js | 20 ++++++++++++++++++-- web_assets/functions.js | 24 ++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/config.js b/config.js index 0954116..90415f4 100644 --- a/config.js +++ b/config.js @@ -33,9 +33,25 @@ var signGroups = [ {label: "All", match: function(s) {return true}}, ]; -//piTODO: document this +/* regionGroups -- A list of region groups. A region can fall into zero, one, or more than one + * group. See below for some examples. + * regions have been designed to work with the + * WorldGuard Overviewer Region importer at https://github.com/pironic/WG2OvR But your host must support php in order + * to run WG2OvR. You can also continue to use any other region format, but your regions + * must now have a label definned in the regions.js. + * + * Required: + * label : string. Displayed in the drop down menu control. + * clickable : boolean. Will determine if we should generate an experimental info window + * that shows details about the clicked region. + * match : function. Applied to each region (from region.js). It returns true if the region + * Should be part of the group. + * + * Optional: + * checked : boolean. Set to true to have the group visible by default + */ var regionGroups = [ - {label: "WorldGuard", match: function(s) {return true}}, + //{label: "WorldGuard", clickable: false, checked: false, match: function(s) {return true}}, ]; /* mapTypeData -- a list of alternate map renderings available. At least one rendering must be diff --git a/web_assets/functions.js b/web_assets/functions.js index e2d1123..1ad1714 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -219,20 +219,23 @@ function initRegions() { // pull all the points out of the regions file. var converted = new google.maps.MVCArray(); + var infoPoint = ""; for (j in region.path) { var point = region.path[j]; converted.push(fromWorldToLatLng(point.x, point.y, point.z)); + } for (idx in regionGroups) { var regionGroup = regionGroups[idx]; var testfunc = regionGroup.match; + var clickable = regionGroup.clickable var label = regionGroup.label; if (region.closed) { var shape = new google.maps.Polygon({ name: region.label, - clickable: false, + clickable: clickable, geodesic: false, map: null, strokeColor: region.color, @@ -246,7 +249,7 @@ function initRegions() { } else { var shape = new google.maps.Polyline({ name: region.label, - clickable: false, + clickable: clickable, geodesic: false, map: null, strokeColor: region.color, @@ -257,6 +260,23 @@ function initRegions() { }); } regionCollection[label].push(shape); + + if (clickable) { + // add the region infowindow popup + infowindow = new google.maps.InfoWindow(); + google.maps.event.addListener(shape, 'click', function(e,i) { + + var contentString = "Region: "+this.name+"
"; + contentString += "Clicked Location:
" + e.latLng.lat() + "," + e.latLng.lng() + "
"; + + // Replace our Info Window's content and position + infowindow.setContent(contentString); + infowindow.setPosition(e.latLng); + + infowindow.open(map); + + }); + } } } } From 8fa515fa5986edcc932ef8a56465cbd828006d22 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Thu, 14 Apr 2011 15:12:18 -0400 Subject: [PATCH 180/213] fixes for building cave mode on windows --- src/rendermode-cave.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/rendermode-cave.c b/src/rendermode-cave.c index 88a7d83..ae5b565 100644 --- a/src/rendermode-cave.c +++ b/src/rendermode-cave.c @@ -163,10 +163,11 @@ rendermode_cave_occluded(void *data, RenderState *state) { static int rendermode_cave_start(void *data, RenderState *state) { RenderModeCave* self; + int ret; self = (RenderModeCave *)data; /* first, chain up */ - int ret = rendermode_normal.start(data, state); + ret = rendermode_normal.start(data, state); if (ret != 0) return ret; @@ -203,10 +204,11 @@ rendermode_cave_finish(void *data, RenderState *state) { static void rendermode_cave_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { RenderModeCave* self; + int z, r, g, b; self = (RenderModeCave *)data; - int z = state->z; - int r = 0, g = 0, b = 0; + z = state->z; + r = 0, g = 0, b = 0; /* draw the normal block */ rendermode_normal.draw(data, state, src, mask); From 26c6b686b3f505e7366c65ac503362d96bc50480 Mon Sep 17 00:00:00 2001 From: Michael Writhe Date: Thu, 14 Apr 2011 14:14:31 -0600 Subject: [PATCH 181/213] region label is now optional --- config.js | 8 ++++---- web_assets/functions.js | 28 +++++++--------------------- 2 files changed, 11 insertions(+), 25 deletions(-) diff --git a/config.js b/config.js index 90415f4..ed28985 100644 --- a/config.js +++ b/config.js @@ -37,13 +37,13 @@ var signGroups = [ * group. See below for some examples. * regions have been designed to work with the * WorldGuard Overviewer Region importer at https://github.com/pironic/WG2OvR But your host must support php in order - * to run WG2OvR. You can also continue to use any other region format, but your regions - * must now have a label definned in the regions.js. + * to run WG2OvR. You can also continue to use any other region format. * * Required: * label : string. Displayed in the drop down menu control. * clickable : boolean. Will determine if we should generate an experimental info window - * that shows details about the clicked region. + * that shows details about the clicked region. + * NOTE: if a region (as definned in region.js) does not have a label, this will default to false. * match : function. Applied to each region (from region.js). It returns true if the region * Should be part of the group. * @@ -51,7 +51,7 @@ var signGroups = [ * checked : boolean. Set to true to have the group visible by default */ var regionGroups = [ - //{label: "WorldGuard", clickable: false, checked: false, match: function(s) {return true}}, + //{label: "All", clickable: false, checked: false, match: function(s) {return true}}, ]; /* mapTypeData -- a list of alternate map renderings available. At least one rendering must be diff --git a/web_assets/functions.js b/web_assets/functions.js index 1ad1714..3fb40d6 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -20,22 +20,6 @@ function prepareSignMarker(marker, item) { infowindow.open(map,marker); prevInfoWindow = infowindow }); - -} - -// add a popup info window to the region and then the region to the map. -// region is the clickable image on the map with all data. -// item is just the same item in the regions.js -function prepareRegionShape(shape, region) { - var c = "

" + region.label + "

"; - var infowindow = new google.maps.InfoWindow({content: c }); - google.maps.event.addListener(shape, 'click', function() { - if (prevInfoWindow) - prevInfoWindow.close() - infowindow.open(map,shape); - prevInfoWindow = infowindow - }); - } // reusable function for making drop down menus. @@ -232,6 +216,13 @@ function initRegions() { var clickable = regionGroup.clickable var label = regionGroup.label; + if(region.label) { + var name = region.label + } else { + var name = 'rawr'; + clickable = false; // if it doesn't have a name, we dont have to show it. + } + if (region.closed) { var shape = new google.maps.Polygon({ name: region.label, @@ -338,10 +329,7 @@ function initMarkers() { if (item.type == 'sign') { prepareSignMarker(marker, item); } - } - - } if (!matched) { @@ -365,8 +353,6 @@ function initMarkers() { prepareSignMarker(marker, item); } } - - } } From 428b3b937e86fb83361a5f4617a7a5a2f7206cd5 Mon Sep 17 00:00:00 2001 From: Michael Writhe Date: Thu, 14 Apr 2011 17:16:25 -0600 Subject: [PATCH 182/213] Issue brownan/master/323 implemented, icons --- web_assets/functions.js | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/web_assets/functions.js b/web_assets/functions.js index 3fb40d6..c89a95e 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -72,7 +72,7 @@ function createDropDown(title, items) { dropdownDiv.appendChild(d); d.appendChild(n) var textNode = document.createElement("text"); - textNode.innerHTML = item.label + "
"; + textNode.innerHTML = "" + item.label + "
"; d.appendChild(textNode); } } @@ -137,11 +137,20 @@ function drawMapControls() { var items = []; for (idx in signGroups) { - var item = signGroups[idx]; - items.push({"label": item.label, "checked": item.checked, + var signGroup = signGroups[idx]; + var iconURL = signGroup.icon; + if (!iconURL) { iconURL = 'signpost.png'; } + items.push({ + "label": signGroup.label, + "checked": signGroup.checked, + "icon": iconURL, "action": function(n, l, checked) { - jQuery.each(markerCollection[l], function(i,elem) {elem.setVisible(checked);}); - }}); + jQuery.each(markerCollection[l], function(i,elem) { + elem.setVisible(checked); + }); + //alert(signGroup.label); + } + }); } createDropDown("Signposts", items); } @@ -152,8 +161,8 @@ function drawMapControls() { var items = []; for (idx in regionGroups) { - var item = regionGroups[idx]; - items.push({"label": item.label, "checked": item.checked, + var regionGroup = regionGroups[idx]; + items.push({"label": regionGroup.label, "checked": regionGroup.checked, "action": function(n, l, checked) { jQuery.each(regionCollection[l], function(i,elem) { elem.setMap(checked ? map : null); // Thanks to LeastWeasel for this line! @@ -215,7 +224,7 @@ function initRegions() { var testfunc = regionGroup.match; var clickable = regionGroup.clickable var label = regionGroup.label; - + if(region.label) { var name = region.label } else { @@ -225,7 +234,7 @@ function initRegions() { if (region.closed) { var shape = new google.maps.Polygon({ - name: region.label, + name: name, clickable: clickable, geodesic: false, map: null, @@ -239,7 +248,7 @@ function initRegions() { }); } else { var shape = new google.maps.Polyline({ - name: region.label, + name: name, clickable: clickable, geodesic: false, map: null, @@ -282,6 +291,7 @@ function initMarkers() { for (i in signGroups) { markerCollection[signGroups[i].label] = []; } + for (i in markerData) { var item = markerData[i]; @@ -331,7 +341,7 @@ function initMarkers() { } } } - + if (!matched) { // is this signpost doesn't match any of the groups in config.js, add it automatically to the "__others__" group if (item.type == 'sign') { iconURL = 'signpost_icon.png';} @@ -353,6 +363,9 @@ function initMarkers() { prepareSignMarker(marker, item); } } + + + } } From 3f8b445f8e8b77a9872d953db6220f149da095fe Mon Sep 17 00:00:00 2001 From: Michael Writhe Date: Thu, 14 Apr 2011 17:25:03 -0600 Subject: [PATCH 183/213] corrected icons for signposts, regions and overlays dont have icons --- web_assets/functions.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/web_assets/functions.js b/web_assets/functions.js index c89a95e..09b0172 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -72,7 +72,12 @@ function createDropDown(title, items) { dropdownDiv.appendChild(d); d.appendChild(n) var textNode = document.createElement("text"); - textNode.innerHTML = "" + item.label + "
"; + if(item.icon) { + textNode.innerHTML = "" + item.label + "
"; + } else { + textNode.innerHTML = item.label + "
"; + } + d.appendChild(textNode); } } @@ -162,12 +167,15 @@ function drawMapControls() { var items = []; for (idx in regionGroups) { var regionGroup = regionGroups[idx]; - items.push({"label": regionGroup.label, "checked": regionGroup.checked, + items.push({ + "label": regionGroup.label, + "checked": regionGroup.checked, "action": function(n, l, checked) { - jQuery.each(regionCollection[l], function(i,elem) { - elem.setMap(checked ? map : null); // Thanks to LeastWeasel for this line! - }); - }}); + jQuery.each(regionCollection[l], function(i,elem) { + elem.setMap(checked ? map : null); // Thanks to LeastWeasel for this line! + }); + } + }); } createDropDown("Regions", items); } From 0e04becbe9d1cc0bec4bcb08615e3e44ae8197c9 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Thu, 14 Apr 2011 21:24:08 -0400 Subject: [PATCH 184/213] Fix missing path component in utils.py --- util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util.py b/util.py index 83f5383..7a0323d 100644 --- a/util.py +++ b/util.py @@ -39,7 +39,7 @@ def findGitVersion(): with open(os.path.join(this_dir,".git","HEAD")) as f: data = f.read().strip() if data.startswith("ref: "): - if not os.path.exists(os.path.join(this_dir,data[5:])): + if not os.path.exists(os.path.join(this_dir, ".git", data[5:])): return data with open(os.path.join(this_dir, ".git", data[5:])) as g: return g.read().strip() From af559f6b880d9fccac1ff34997c5038582f210d0 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Fri, 15 Apr 2011 09:02:39 -0700 Subject: [PATCH 185/213] bumped extension version for cave mode --- src/overviewer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/overviewer.h b/src/overviewer.h index 6eac848..db93a67 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -26,7 +26,7 @@ // increment this value if you've made a change to the c extesion // and want to force users to rebuild -#define OVERVIEWER_EXTENSION_VERSION 3 +#define OVERVIEWER_EXTENSION_VERSION 4 /* Python PIL, and numpy headers */ #include From 9cc01ddc218d633c363f8384c8cda3c14e89fbf2 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Fri, 15 Apr 2011 21:36:31 -0400 Subject: [PATCH 186/213] removed some blocks from solid_blocks, fixed overlays on snow, half-steps --- chunk.py | 2 +- src/rendermode-overlay.c | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/chunk.py b/chunk.py index dd06ffe..8ed85b8 100644 --- a/chunk.py +++ b/chunk.py @@ -120,7 +120,7 @@ transparent_blocks = set([0, 6, 8, 9, 18, 20, 37, 38, 39, 40, 44, 50, 51, 52, 53 # 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, 23, 24, 25, 35, 41, 42, 43, 44, 45, 46, 47, 48, 49, 53, 54, 56, 57, 58, 60, - 61, 62, 64, 65, 66, 67, 71, 73, 74, 78, 79, 80, 81, 82, 84, 86, 87, 88, 89, 91, 92]) + 61, 62, 67, 73, 74, 78, 79, 80, 81, 82, 84, 86, 87, 88, 89, 91]) # This set holds block ids that are fluid blocks fluid_blocks = set([8,9,10,11]) diff --git a/src/rendermode-overlay.c b/src/rendermode-overlay.c index 2a15302..4029ae9 100644 --- a/src/rendermode-overlay.c +++ b/src/rendermode-overlay.c @@ -76,6 +76,13 @@ rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject unsigned char r, g, b, a; PyObject *top_block_py, *block_py; + // exactly analogous to edge-line code for these special blocks + int increment=0; + if (state->block == 44) // half-step + increment=6; + else if (state->block == 78) // snow + increment=9; + /* clear the draw space -- set alpha to 0 within mask */ tint_with_mask(state->img, 255, 255, 255, 0, mask, state->imgx, state->imgy, 0, 0); @@ -114,8 +121,8 @@ rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject /* do the overlay */ if (a > 0) { - alpha_over(state->img, self->white_color, self->facemask_top, state->imgx, state->imgy, 0, 0); - tint_with_mask(state->img, r, g, b, a, self->facemask_top, state->imgx, state->imgy, 0, 0); + alpha_over(state->img, self->white_color, self->facemask_top, state->imgx, state->imgy + increment, 0, 0); + tint_with_mask(state->img, r, g, b, a, self->facemask_top, state->imgx, state->imgy + increment, 0, 0); } } From ad358fa7fdb08f890b532a71239254f188f5964d Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Fri, 15 Apr 2011 21:44:59 -0400 Subject: [PATCH 187/213] fixed spawn mode for chunk tops --- src/rendermode-spawn.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/rendermode-spawn.c b/src/rendermode-spawn.c index ebbdc25..0d5138e 100644 --- a/src/rendermode-spawn.c +++ b/src/rendermode-spawn.c @@ -23,6 +23,7 @@ static void get_color(void *data, RenderState *state, RenderModeSpawn* self = (RenderModeSpawn *)data; int x = state->x, y = state->y, z = state->z; + int z_light = z + 1; unsigned char blocklight, skylight; PyObject *block_py; @@ -34,10 +35,6 @@ static void get_color(void *data, RenderState *state, /* default to no overlay, until told otherwise */ *a = 0; - /* if we're at the top, skip */ - if (z == 127) - return; - block_py = PyInt_FromLong(state->block); if (PySequence_Contains(self->nospawn_blocks, block_py)) { /* nothing can spawn on this */ @@ -46,8 +43,12 @@ static void get_color(void *data, RenderState *state, } Py_DECREF(block_py); - blocklight = getArrayByte3D(self->blocklight, x, y, z+1); - skylight = getArrayByte3D(self->skylight, x, y, z+1); + /* if we're at the top, use the top-most light instead */ + if (z_light == 128) + z_light--; + + blocklight = getArrayByte3D(self->blocklight, x, y, z_light); + skylight = getArrayByte3D(self->skylight, x, y, z_light); if (MAX(blocklight, skylight) <= 7) { /* hostile mobs spawn in daylight */ From 1ef6efab8d3e070c88da7a614e4094bd3305fed0 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Sat, 16 Apr 2011 13:11:37 +0200 Subject: [PATCH 188/213] Change _iterate_regionfiles to use glob instead of os.walk. The function gets simpler in this way With this you can fool overviewer to render the nether just poiting the "DIM-1" instead the world dir. (and you also need to copy the level.dat to the DIM-1 directory, because nether has not spawn) --- world.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/world.py b/world.py index b1b0ca7..7f7bab1 100644 --- a/world.py +++ b/world.py @@ -16,6 +16,7 @@ import functools import os import os.path +from glob import glob import multiprocessing import Queue import sys @@ -287,12 +288,10 @@ class World(object): p = f.split(".") yield (int(p[1]), int(p[2]), join(self.worlddir, 'region', f)) else: - for dirpath, dirnames, filenames in os.walk(os.path.join(self.worlddir, 'region')): - if not dirnames and filenames and "DIM-1" not in dirpath: - for f in filenames: - if f.startswith("r.") and f.endswith(".mcr"): - p = f.split(".") - yield (int(p[1]), int(p[2]), join(dirpath, f)) + for path in glob(os.path.join(self.worlddir, 'region') + "/r.*.*.mcr"): + dirpath, f = os.path.split(path) + p = f.split(".") + yield (int(p[1]), int(p[2]), join(dirpath, f)) def get_save_dir(): """Returns the path to the local saves directory From f30a5db2a185564ee77a7ffd2aa96b68f6f77a79 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sat, 16 Apr 2011 11:18:46 -0400 Subject: [PATCH 189/213] destination directory will now be created recursively, if needed --- quadtree.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quadtree.py b/quadtree.py index d4018f6..51cb46e 100644 --- a/quadtree.py +++ b/quadtree.py @@ -69,7 +69,7 @@ class QuadtreeGen(object): # Make the destination dir if not os.path.exists(destdir): - os.mkdir(destdir) + os.makedirs(os.path.abspath(destdir)) if tiledir is None: tiledir = rendermode self.tiledir = tiledir From 558ebe0899efc305c86682dace8f4091b88a32b1 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sat, 16 Apr 2011 12:03:12 -0400 Subject: [PATCH 190/213] fixed for-loop scoping on createDropDown() closures --- web_assets/functions.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/web_assets/functions.js b/web_assets/functions.js index 09b0172..038f0c2 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -60,9 +60,11 @@ function createDropDown(title, items) { // give it a name $(n).data("label",item.label); - jQuery(n).click(function(e) { - item.action(idx, item.label, e.target.checked); - }); + jQuery(n).click((function(local_idx, local_item) { + return function(e) { + item.action(local_idx, local_item, e.target.checked); + }; + })(idx, item)); // if its checked, its gotta do something, do that here. if (item.checked) { @@ -149,11 +151,11 @@ function drawMapControls() { "label": signGroup.label, "checked": signGroup.checked, "icon": iconURL, - "action": function(n, l, checked) { - jQuery.each(markerCollection[l], function(i,elem) { + "action": function(n, item, checked) { + jQuery.each(markerCollection[item.label], function(i,elem) { elem.setVisible(checked); }); - //alert(signGroup.label); + //alert(item.label); } }); } @@ -170,8 +172,8 @@ function drawMapControls() { items.push({ "label": regionGroup.label, "checked": regionGroup.checked, - "action": function(n, l, checked) { - jQuery.each(regionCollection[l], function(i,elem) { + "action": function(n, item, checked) { + jQuery.each(regionCollection[item.label], function(i,elem) { elem.setMap(checked ? map : null); // Thanks to LeastWeasel for this line! }); } @@ -186,14 +188,14 @@ function drawMapControls() { var items = []; for (idx in overlayMapTypes) { var overlay = overlayMapTypes[idx]; - items.push({"label": overlay.name, "checked": false, - "action": function(i, l, checked) { + items.push({"label": overlay.name, "checked": false, "overlay": overlay, + "action": function(i, item, checked) { if (checked) { - map.overlayMapTypes.push(overlay); + map.overlayMapTypes.push(item.overlay); } else { var idx_to_delete = -1; map.overlayMapTypes.forEach(function(e, j) { - if (e == overlay) { idx_to_delete = j; } + if (e == item.overlay) { idx_to_delete = j; } }); if (idx_to_delete >= 0) { map.overlayMapTypes.removeAt(idx_to_delete); From 26b35906a41b86018bc54202feb776d19041cabf Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sat, 16 Apr 2011 19:20:21 -0400 Subject: [PATCH 191/213] fix for cave mode merge --- src/rendermode-cave.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rendermode-cave.c b/src/rendermode-cave.c index ae5b565..e51742c 100644 --- a/src/rendermode-cave.c +++ b/src/rendermode-cave.c @@ -219,7 +219,7 @@ rendermode_cave_draw(void *data, RenderState *state, PyObject *src, PyObject *ma g = PyInt_AsLong(PyList_GetItem(self->depth_colors, 1 + z*3)); b = PyInt_AsLong(PyList_GetItem(self->depth_colors, 2 + z*3)); - tint_with_mask(state->img, r, g, b, mask, state->imgx, state->imgy, 0, 0); + tint_with_mask(state->img, r, g, b, 255, mask, state->imgx, state->imgy, 0, 0); } From 05c4083b7e1d15ae79ab9de5adf8165b2ec28123 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sat, 16 Apr 2011 19:25:40 -0400 Subject: [PATCH 192/213] support for mixed png/jpg tilesets, and overlays with imgformat=jpg --- chunk.py | 4 ++++ config.js | 11 ++++++----- googlemap.py | 16 ++++++---------- quadtree.py | 10 +++++----- web_assets/functions.js | 9 +++++---- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/chunk.py b/chunk.py index baf9efc..0fdce6c 100644 --- a/chunk.py +++ b/chunk.py @@ -129,6 +129,10 @@ fluid_blocks = set([8,9,10,11]) # (glass, half blocks) nospawn_blocks = set([20,44]) +# overlay rendermodes +# FIXME hook this into render_modes in setup.py, somehow +overlay_rendermodes = ['spawn'] + class ChunkCorrupt(Exception): pass diff --git a/config.js b/config.js index ed28985..8627c1e 100644 --- a/config.js +++ b/config.js @@ -1,6 +1,5 @@ var config = { - fileExt: '{imgformat}', tileSize: 384, defaultZoom: 2, maxZoom: {maxzoom}, @@ -59,15 +58,17 @@ var regionGroups = [ * the first one being the default. * * Required: - * label : string. Displayed on the control. - * path : string. Location of the rendered tiles. + * label : string. Displayed on the control. + * path : string. Location of the rendered tiles. * Optional: - * base : string. Base of the url path for tile locations, useful for serving tiles from a different server than the js/html server. + * base : string. Base of the url path for tile locations, useful for serving tiles from a different server than the js/html server. + * imgformat : string. File extension used for these tiles. Defaults to png. + * overlay : bool. If true, this tile set will be treated like an overlay var mapTypeData=[ {'label': 'Unlit', 'path': 'tiles'}, // {'label': 'Day', 'path': 'lighting/tiles'}, -// {'label': 'Night', 'path': 'night/tiles'}, +// {'label': 'Night', 'path': 'night/tiles', 'imgformat': 'jpg'}, // {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'}, // {'label': 'Overlay', 'path': 'overlay/tiles', 'overlay': true} ]; diff --git a/googlemap.py b/googlemap.py index dde8ad7..0f883d3 100644 --- a/googlemap.py +++ b/googlemap.py @@ -23,6 +23,7 @@ from time import strftime, gmtime import json import util +from chunk import overlay_rendermodes """ This module has routines related to generating a Google Maps-based @@ -72,12 +73,11 @@ class MapGen(object): raise ValueError("there must be at least one quadtree to work on") self.destdir = quadtrees[0].destdir - self.imgformat = quadtrees[0].imgformat self.world = quadtrees[0].world self.p = quadtrees[0].p for i in quadtrees: - if i.destdir != self.destdir or i.imgformat != self.imgformat or i.world != self.world: - raise ValueError("all the given quadtrees must have the same destdir") + if i.destdir != self.destdir or i.world != self.world: + raise ValueError("all the given quadtrees must have the same destdir and world") self.quadtrees = quadtrees @@ -85,24 +85,20 @@ class MapGen(object): """Writes out config.js, marker.js, and region.js Copies web assets into the destdir""" zoomlevel = self.p - imgformat = self.imgformat configpath = os.path.join(util.get_program_path(), "config.js") config = open(configpath, 'r').read() config = config.replace( "{maxzoom}", str(zoomlevel)) - config = config.replace( - "{imgformat}", str(imgformat)) config = config.replace("{spawn_coords}", json.dumps(list(self.world.spawn))) # create generated map type data, from given quadtrees - # FIXME hook this into render_modes in setup.py, somehow - overlay_types = ['spawn'] maptypedata = map(lambda q: {'label' : q.rendermode.capitalize(), 'path' : q.tiledir, - 'overlay' : q.rendermode in overlay_types}, + 'overlay' : q.rendermode in overlay_rendermodes, + 'imgformat' : q.imgformat}, self.quadtrees) config = config.replace("{maptypedata}", json.dumps(maptypedata)) @@ -114,7 +110,7 @@ class MapGen(object): blank = Image.new("RGBA", (1,1)) tileDir = os.path.join(self.destdir, quadtree.tiledir) if not os.path.exists(tileDir): os.mkdir(tileDir) - blank.save(os.path.join(tileDir, "blank."+self.imgformat)) + blank.save(os.path.join(tileDir, "blank."+quadtree.imgformat)) # copy web assets into destdir: mirror_dir(os.path.join(util.get_program_path(), "web_assets"), self.destdir) diff --git a/quadtree.py b/quadtree.py index 51cb46e..40773e9 100644 --- a/quadtree.py +++ b/quadtree.py @@ -60,12 +60,12 @@ class QuadtreeGen(object): """ assert(imgformat) self.imgformat = imgformat - self.optimizeimg = optimizeimg - - self.lighting = rendermode in ("lighting", "night", "spawn") - self.night = rendermode in ("night", "spawn") - self.spawn = rendermode in ("spawn",) + self.optimizeimg = optimizeimg self.rendermode = rendermode + + # force png renderformat if we're using an overlay mode + if rendermode in chunk.overlay_rendermodes: + self.imgformat = "png" # Make the destination dir if not os.path.exists(destdir): diff --git a/web_assets/functions.js b/web_assets/functions.js index 038f0c2..9c8b46a 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -532,7 +532,7 @@ function fromWorldToLatLng(x, z, y) return new google.maps.LatLng(lat, lng); } -function getTileUrlGenerator(path, path_base) { +function getTileUrlGenerator(path, path_base, path_ext) { return function(tile, zoom) { var url = path; var url_base = ( path_base ? path_base : '' ); @@ -547,7 +547,7 @@ function getTileUrlGenerator(path, path_base) { url += '/' + (x + 2 * y); } } - url = url + '.' + config.fileExt; + url = url + '.' + path_ext; if(config.cacheMinutes > 0) { var d = new Date(); url += '?c=' + Math.floor(d.getTime() / (1000 * 60 * config.cacheMinutes)); @@ -563,13 +563,14 @@ var mapTypeIds = []; var overlayMapTypes = []; for (idx in mapTypeData) { var view = mapTypeData[idx]; + var imgformat = view.imgformat ? view.imgformat : 'png'; MCMapOptions[view.label] = { - getTileUrl: getTileUrlGenerator(view.path, view.base), + getTileUrl: getTileUrlGenerator(view.path, view.base, imgformat), tileSize: new google.maps.Size(config.tileSize, config.tileSize), maxZoom: config.maxZoom, minZoom: 0, - isPng: !(config.fileExt.match(/^png$/i) == null) + isPng: !(imgformat.match(/^png$/i) == null) }; MCMapType[view.label] = new google.maps.ImageMapType(MCMapOptions[view.label]); From 5294b88df9463cc8b8c46f1093d081c5332a43af Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sat, 16 Apr 2011 19:33:21 -0400 Subject: [PATCH 193/213] more fixes for spawn overlay at z == 127 --- src/rendermode-spawn.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/rendermode-spawn.c b/src/rendermode-spawn.c index 0d5138e..ff73780 100644 --- a/src/rendermode-spawn.c +++ b/src/rendermode-spawn.c @@ -43,12 +43,14 @@ static void get_color(void *data, RenderState *state, } Py_DECREF(block_py); - /* if we're at the top, use the top-most light instead */ - if (z_light == 128) - z_light--; + blocklight = getArrayByte3D(self->blocklight, x, y, MAX(127, z_light)); - blocklight = getArrayByte3D(self->blocklight, x, y, z_light); - skylight = getArrayByte3D(self->skylight, x, y, z_light); + /* if we're at the top, force 15 (brightest!) skylight */ + if (z_light == 128) { + skylight = 15; + } else { + skylight = getArrayByte3D(self->skylight, x, y, z_light); + } if (MAX(blocklight, skylight) <= 7) { /* hostile mobs spawn in daylight */ From 5c3b3be41861d08476d3f2261e03cba1dcf42e94 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sat, 16 Apr 2011 20:08:38 -0400 Subject: [PATCH 194/213] basic rendermode inheritance introspection (no more hacky overlay list) --- chunk.py | 4 -- googlemap.py | 4 +- quadtree.py | 3 +- src/main.c | 10 +++++ src/rendermode-cave.c | 1 + src/rendermode-lighting.c | 1 + src/rendermode-night.c | 1 + src/rendermode-normal.c | 1 + src/rendermode-overlay.c | 1 + src/rendermode-spawn.c | 1 + src/rendermodes.c | 84 ++++++++++++++++++++++++++++++++++++++- src/rendermodes.h | 10 ++++- 12 files changed, 111 insertions(+), 10 deletions(-) diff --git a/chunk.py b/chunk.py index 0fdce6c..baf9efc 100644 --- a/chunk.py +++ b/chunk.py @@ -129,10 +129,6 @@ fluid_blocks = set([8,9,10,11]) # (glass, half blocks) nospawn_blocks = set([20,44]) -# overlay rendermodes -# FIXME hook this into render_modes in setup.py, somehow -overlay_rendermodes = ['spawn'] - class ChunkCorrupt(Exception): pass diff --git a/googlemap.py b/googlemap.py index 0f883d3..b8416b5 100644 --- a/googlemap.py +++ b/googlemap.py @@ -23,7 +23,7 @@ from time import strftime, gmtime import json import util -from chunk import overlay_rendermodes +from c_overviewer import get_render_mode_inheritance """ This module has routines related to generating a Google Maps-based @@ -97,7 +97,7 @@ class MapGen(object): # create generated map type data, from given quadtrees maptypedata = map(lambda q: {'label' : q.rendermode.capitalize(), 'path' : q.tiledir, - 'overlay' : q.rendermode in overlay_rendermodes, + 'overlay' : 'overlay' in get_render_mode_inheritance(q.rendermode), 'imgformat' : q.imgformat}, self.quadtrees) config = config.replace("{maptypedata}", json.dumps(maptypedata)) diff --git a/quadtree.py b/quadtree.py index 40773e9..4b2cc68 100644 --- a/quadtree.py +++ b/quadtree.py @@ -34,6 +34,7 @@ from PIL import Image import nbt import chunk +from c_overviewer import get_render_mode_inheritance from optimizeimages import optimize_image import composite @@ -64,7 +65,7 @@ class QuadtreeGen(object): self.rendermode = rendermode # force png renderformat if we're using an overlay mode - if rendermode in chunk.overlay_rendermodes: + if 'overlay' in get_render_mode_inheritance(rendermode): self.imgformat = "png" # Make the destination dir diff --git a/src/main.c b/src/main.c index ddbe0af..4de7eec 100644 --- a/src/main.c +++ b/src/main.c @@ -25,14 +25,24 @@ PyObject *get_extension_version(PyObject *self, PyObject *args) { static PyMethodDef COverviewerMethods[] = { {"alpha_over", alpha_over_wrap, METH_VARARGS, "alpha over composite function"}, + {"render_loop", chunk_render, METH_VARARGS, "Renders stuffs"}, + {"get_render_modes", get_render_modes, METH_VARARGS, "returns available render modes"}, {"get_render_mode_info", get_render_mode_info, METH_VARARGS, "returns info for a particular render mode"}, + {"get_render_mode_parent", get_render_mode_parent, METH_VARARGS, + "returns parent for a particular render mode"}, + {"get_render_mode_inheritance", get_render_mode_inheritance, METH_VARARGS, + "returns inheritance chain for a particular render mode"}, + {"get_render_mode_children", get_render_mode_children, METH_VARARGS, + "returns (direct) children for a particular render mode"}, + {"extension_version", get_extension_version, METH_VARARGS, "Returns the extension version"}, + {NULL, NULL, 0, NULL} /* Sentinel */ }; diff --git a/src/rendermode-cave.c b/src/rendermode-cave.c index e51742c..f8334b2 100644 --- a/src/rendermode-cave.c +++ b/src/rendermode-cave.c @@ -225,6 +225,7 @@ rendermode_cave_draw(void *data, RenderState *state, PyObject *src, PyObject *ma RenderModeInterface rendermode_cave = { "cave", "render only caves in normal mode", + &rendermode_normal, sizeof(RenderModeCave), rendermode_cave_start, rendermode_cave_finish, diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index 628e5c4..83b38d3 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -229,6 +229,7 @@ rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject RenderModeInterface rendermode_lighting = { "lighting", "draw shadows from the lighting data", + &rendermode_normal, sizeof(RenderModeLighting), rendermode_lighting_start, rendermode_lighting_finish, diff --git a/src/rendermode-night.c b/src/rendermode-night.c index 1e54f05..46ca7c2 100644 --- a/src/rendermode-night.c +++ b/src/rendermode-night.c @@ -61,6 +61,7 @@ rendermode_night_draw(void *data, RenderState *state, PyObject *src, PyObject *m RenderModeInterface rendermode_night = { "night", "like \"lighting\", except at night", + &rendermode_lighting, sizeof(RenderModeNight), rendermode_night_start, rendermode_night_finish, diff --git a/src/rendermode-normal.c b/src/rendermode-normal.c index 983050c..9f139a3 100644 --- a/src/rendermode-normal.c +++ b/src/rendermode-normal.c @@ -221,6 +221,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * RenderModeInterface rendermode_normal = { "normal", "nothing special, just render the blocks", + NULL, sizeof(RenderModeNormal), rendermode_normal_start, rendermode_normal_finish, diff --git a/src/rendermode-overlay.c b/src/rendermode-overlay.c index 4029ae9..fb4e765 100644 --- a/src/rendermode-overlay.c +++ b/src/rendermode-overlay.c @@ -128,6 +128,7 @@ rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject RenderModeInterface rendermode_overlay = { "overlay", "base rendermode for informational overlays", + NULL, sizeof(RenderModeOverlay), rendermode_overlay_start, rendermode_overlay_finish, diff --git a/src/rendermode-spawn.c b/src/rendermode-spawn.c index ff73780..edd9c44 100644 --- a/src/rendermode-spawn.c +++ b/src/rendermode-spawn.c @@ -109,6 +109,7 @@ rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *m RenderModeInterface rendermode_spawn = { "spawn", "draws a red overlay where monsters can spawn at night", + &rendermode_overlay, sizeof(RenderModeSpawn), rendermode_spawn_start, rendermode_spawn_finish, diff --git a/src/rendermodes.c b/src/rendermodes.c index 08ac140..5ee7d54 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -98,5 +98,87 @@ PyObject *get_render_mode_info(PyObject *self, PyObject *args) { } Py_DECREF(info); - Py_RETURN_NONE; + return PyErr_Format(PyExc_ValueError, "invalid rendermode: \"%s\"", rendermode); +} + +/* bindings -- get parent's name */ +PyObject *get_render_mode_parent(PyObject *self, PyObject *args) { + const char *rendermode; + unsigned int i; + if (!PyArg_ParseTuple(args, "s", &rendermode)) + return NULL; + + for (i = 0; render_modes[i] != NULL; i++) { + if (strcmp(render_modes[i]->name, rendermode) == 0) { + if (render_modes[i]->parent) { + /* has parent */ + return PyString_FromString(render_modes[i]->parent->name); + } else { + /* no parent */ + Py_RETURN_NONE; + } + } + } + + return PyErr_Format(PyExc_ValueError, "invalid rendermode: \"%s\"", rendermode); +} + +/* bindings -- get list of inherited parents */ +PyObject *get_render_mode_inheritance(PyObject *self, PyObject *args) { + const char *rendermode; + PyObject *parents; + unsigned int i; + if (!PyArg_ParseTuple(args, "s", &rendermode)) + return NULL; + + parents = PyList_New(0); + if (!parents) + return NULL; + + RenderModeInterface *iface = NULL; + for (i = 0; render_modes[i] != NULL; i++) { + if (strcmp(render_modes[i]->name, rendermode) == 0) { + iface = render_modes[i]; + break; + } + } + + if (!iface) { + Py_DECREF(parents); + return PyErr_Format(PyExc_ValueError, "invalid rendermode: \"%s\"", rendermode); + } + + while (iface) { + PyObject *name = PyString_FromString(iface->name); + PyList_Append(parents, name); + Py_DECREF(name); + + iface = iface->parent; + } + + PyList_Reverse(parents); + return parents; +} + +/* bindings -- get list of (direct) children */ +PyObject *get_render_mode_children(PyObject *self, PyObject *args) { + const char *rendermode; + PyObject *children; + unsigned int i; + if (!PyArg_ParseTuple(args, "s", &rendermode)) + return NULL; + + children = PyList_New(0); + if (!children) + return NULL; + + for (i = 0; render_modes[i] != NULL; i++) { + if (render_modes[i]->parent && strcmp(render_modes[i]->parent->name, rendermode) == 0) { + PyObject *child_name = PyString_FromString(render_modes[i]->name); + PyList_Append(children, child_name); + Py_DECREF(child_name); + } + } + + return children; } diff --git a/src/rendermodes.h b/src/rendermodes.h index 980968c..fd0b479 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -38,12 +38,15 @@ #include /* rendermode interface */ -typedef struct { +typedef struct _RenderModeInterface RenderModeInterface; +struct _RenderModeInterface { /* the name of this mode */ const char* name; /* the short description of this render mode */ const char* description; + /* the rendermode this is derived from, or NULL */ + RenderModeInterface *parent; /* the size of the local storage for this rendermode */ unsigned int data_size; @@ -54,13 +57,16 @@ typedef struct { int (*occluded)(void *, RenderState *); /* last two arguments are img and mask, from texture lookup */ void (*draw)(void *, RenderState *, PyObject *, PyObject *); -} RenderModeInterface; +}; /* figures out the render mode to use from the given ChunkRenderer */ RenderModeInterface *get_render_mode(RenderState *state); /* python bindings */ PyObject *get_render_modes(PyObject *self, PyObject *args); PyObject *get_render_mode_info(PyObject *self, PyObject *args); +PyObject *get_render_mode_parent(PyObject *self, PyObject *args); +PyObject *get_render_mode_inheritance(PyObject *self, PyObject *args); +PyObject *get_render_mode_children(PyObject *self, PyObject *args); /* individual rendermode interface declarations follow */ From ed5f3eaad400763753923386ee7f5f864fae0bbd Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 17 Apr 2011 14:10:27 -0400 Subject: [PATCH 195/213] Stronger support for setting a background color Works by setting "bg_color='#rrggbb'" in settings.py. Works for both png and jpg imgformats --- config.js | 2 +- configParser.py | 3 ++- googlemap.py | 12 ++++++++---- overviewer.py | 7 +++++-- quadtree.py | 7 ++++--- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/config.js b/config.js index 78dfd7d..3ec0a84 100644 --- a/config.js +++ b/config.js @@ -8,7 +8,7 @@ //center: [0,0,0], center: {spawn_coords}, cacheMinutes: 0, // Change this to have browsers automatically request new images every x minutes - bg_color: '#1A1A1A', + bg_color: '{bg_color}', // You can set this in settings.py debug: false }; diff --git a/configParser.py b/configParser.py index 88925d7..5b275f0 100644 --- a/configParser.py +++ b/configParser.py @@ -4,7 +4,8 @@ import os.path import logging class OptionsResults(object): - pass + def get(self, *args): + return self.__dict__.get(*args) class ConfigOptionParser(object): def __init__(self, **kwargs): diff --git a/googlemap.py b/googlemap.py index f6f271e..630101a 100644 --- a/googlemap.py +++ b/googlemap.py @@ -59,14 +59,15 @@ def mirror_dir(src, dst, entities=None): # if this stills throws an error, let it propagate up class MapGen(object): - def __init__(self, quadtrees, skipjs=False, web_assets_hook=None): + def __init__(self, quadtrees, configInfo): """Generates a Google Maps interface for the given list of quadtrees. All of the quadtrees must have the same destdir, image format, and world. Note:tiledir for each quadtree should be unique. By default the tiledir is determined by the rendermode""" - self.skipjs = skipjs - self.web_assets_hook = web_assets_hook + self.skipjs = configInfo.get('skipjs', None) + self.web_assets_hook = configInfo.get('web_assets_hook', None) + self.bg_color = configInfo.get('bg_color') if not len(quadtrees) > 0: raise ValueError("there must be at least one quadtree to work on") @@ -96,6 +97,8 @@ class MapGen(object): config = config.replace("{spawn_coords}", json.dumps(list(self.world.spawn))) + + config = config.replace("{bg_color}", self.bg_color) # create generated map type data, from given quadtrees maptypedata = map(lambda q: {'label' : q.rendermode.capitalize(), @@ -105,9 +108,10 @@ class MapGen(object): with open(os.path.join(self.destdir, "config.js"), 'w') as output: output.write(config) + bgcolor = (int(self.bg_color[1:3],16), int(self.bg_color[3:5],16), int(self.bg_color[5:7],16), 0) + blank = Image.new("RGBA", (1,1), bgcolor) # Write a blank image for quadtree in self.quadtrees: - blank = Image.new("RGBA", (1,1)) tileDir = os.path.join(self.destdir, quadtree.tiledir) if not os.path.exists(tileDir): os.mkdir(tileDir) blank.save(os.path.join(tileDir, "blank."+self.imgformat)) diff --git a/overviewer.py b/overviewer.py index 6e755bd..925d241 100755 --- a/overviewer.py +++ b/overviewer.py @@ -97,6 +97,7 @@ def main(): parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render types, separated by commas. Use --list-rendermodes to list them all.", type="choice", choices=avail_rendermodes, required=True, default=avail_rendermodes[0], listify=True) parser.add_option("--list-rendermodes", dest="list_rendermodes", action="store_true", help="List available render modes and exit.", commandLineOnly=True) parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.", configFileOnly=True ) + parser.add_option("--bg_color", dest="bg_color", help="Configures the background color for the GoogleMap output. Specify in #RRGGBB format", configFileOnly=True, type="string", default="#1A1A1A") parser.add_option("--optimize-img", dest="optimizeimg", help="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%", configFileOnly=True) parser.add_option("--web-assets-hook", dest="web_assets_hook", help="If provided, run this function after the web assets have been copied, but before actual tile rendering begins. It should accept a QuadtreeGen object as its only argument.", action="store", metavar="SCRIPT", type="function", configFileOnly=True) parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.") @@ -214,10 +215,12 @@ def main(): logging.info("Rending the following tilesets: %s", ",".join(options.rendermode)) + bgcolor = (int(options.bg_color[1:3],16), int(options.bg_color[3:5],16), int(options.bg_color[5:7],16), 0) + # create the quadtrees # TODO chunklist q = [] - qtree_args = {'depth' : options.zoom, 'imgformat' : imgformat, 'optimizeimg' : optimizeimg} + qtree_args = {'depth' : options.zoom, 'imgformat' : imgformat, 'optimizeimg' : optimizeimg, 'bgcolor':bgcolor} for rendermode in options.rendermode: if rendermode == 'normal': qtree = quadtree.QuadtreeGen(w, destdir, rendermode=rendermode, tiledir='tiles', **qtree_args) @@ -229,7 +232,7 @@ def main(): r = rendernode.RenderNode(q) # write out the map and web assets - m = googlemap.MapGen(q, skipjs=options.skipjs, web_assets_hook=options.web_assets_hook) + m = googlemap.MapGen(q, configInfo=options) m.go(options.procs) # render the tiles! diff --git a/quadtree.py b/quadtree.py index 51cb46e..48d816f 100644 --- a/quadtree.py +++ b/quadtree.py @@ -48,7 +48,7 @@ def iterate_base4(d): return itertools.product(xrange(4), repeat=d) class QuadtreeGen(object): - def __init__(self, worldobj, destdir, depth=None, tiledir=None, imgformat=None, optimizeimg=None, rendermode="normal"): + def __init__(self, worldobj, destdir, bgcolor, depth=None, tiledir=None, imgformat=None, optimizeimg=None, rendermode="normal"): """Generates a quadtree from the world given into the given dest directory @@ -61,6 +61,7 @@ class QuadtreeGen(object): assert(imgformat) self.imgformat = imgformat self.optimizeimg = optimizeimg + self.bgcolor = bgcolor self.lighting = rendermode in ("lighting", "night", "spawn") self.night = rendermode in ("night", "spawn") @@ -320,7 +321,7 @@ class QuadtreeGen(object): #logging.debug("writing out innertile {0}".format(imgpath)) # Create the actual image now - img = Image.new("RGBA", (384, 384), (38,92,255,0)) + img = Image.new("RGBA", (384, 384), self.bgcolor) # we'll use paste (NOT alpha_over) for quadtree generation because # this is just straight image stitching, not alpha blending @@ -442,7 +443,7 @@ class QuadtreeGen(object): #logging.debug("writing out worldtile {0}".format(imgpath)) # Compile this image - tileimg = Image.new("RGBA", (width, height), (38,92,255,0)) + tileimg = Image.new("RGBA", (width, height), self.bgcolor) world = self.world rendermode = self.rendermode From c790ad51020d4b6899ebcadf8bc13895938b471d Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 18 Apr 2011 11:03:29 -0400 Subject: [PATCH 196/213] windows build fixes --- src/rendermodes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rendermodes.c b/src/rendermodes.c index 5ee7d54..43658e1 100644 --- a/src/rendermodes.c +++ b/src/rendermodes.c @@ -128,6 +128,7 @@ PyObject *get_render_mode_inheritance(PyObject *self, PyObject *args) { const char *rendermode; PyObject *parents; unsigned int i; + RenderModeInterface *iface = NULL; if (!PyArg_ParseTuple(args, "s", &rendermode)) return NULL; @@ -135,7 +136,6 @@ PyObject *get_render_mode_inheritance(PyObject *self, PyObject *args) { if (!parents) return NULL; - RenderModeInterface *iface = NULL; for (i = 0; render_modes[i] != NULL; i++) { if (strcmp(render_modes[i]->name, rendermode) == 0) { iface = render_modes[i]; From 5f6ceebc8156aec7eb9a8e5de0ae5d7f84687af1 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 18 Apr 2011 11:02:39 -0700 Subject: [PATCH 197/213] fixed MIN/MAX mixup that prevented spawn overlay from working --- src/rendermode-spawn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rendermode-spawn.c b/src/rendermode-spawn.c index edd9c44..498ed17 100644 --- a/src/rendermode-spawn.c +++ b/src/rendermode-spawn.c @@ -43,7 +43,7 @@ static void get_color(void *data, RenderState *state, } Py_DECREF(block_py); - blocklight = getArrayByte3D(self->blocklight, x, y, MAX(127, z_light)); + blocklight = getArrayByte3D(self->blocklight, x, y, MIN(127, z_light)); /* if we're at the top, force 15 (brightest!) skylight */ if (z_light == 128) { From 2d25d74bd774fedbb86da2c1d2a3f92b0bd4ba8b Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 18 Apr 2011 20:16:06 -0400 Subject: [PATCH 198/213] moved quadtree preprocessing out of RenderNode, fixes issue #326 --- overviewer.py | 7 ++++++- rendernode.py | 1 - 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/overviewer.py b/overviewer.py index 925d241..115644f 100755 --- a/overviewer.py +++ b/overviewer.py @@ -227,8 +227,12 @@ def main(): else: qtree = quadtree.QuadtreeGen(w, destdir, rendermode=rendermode, **qtree_args) q.append(qtree) + + # do quadtree-level preprocessing + for qtree in q: + qtree.go(options.procs) - #create the distributed render + # create the distributed render r = rendernode.RenderNode(q) # write out the map and web assets @@ -238,6 +242,7 @@ def main(): # render the tiles! r.go(options.procs) + # finish up the map m.finalize() diff --git a/rendernode.py b/rendernode.py index d762b73..9dce5cc 100644 --- a/rendernode.py +++ b/rendernode.py @@ -149,7 +149,6 @@ class RenderNode(object): total += 4**q.p if q.p > max_p: max_p = q.p - q.go(procs) self.max_p = max_p # Render the highest level of tiles from the chunks results = collections.deque() From be230000503737d3fd3fabaa69018b31271f644c Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Thu, 21 Apr 2011 00:49:19 +0200 Subject: [PATCH 199/213] Add the detector and powered rails to textures.py --- textures.py | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/textures.py b/textures.py index e3e714c..8a91c79 100644 --- a/textures.py +++ b/textures.py @@ -938,11 +938,27 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"), img.split()[3]) - if blockID == 66: # minetrack: - - raw_straight = terrain_images[128] - raw_corner = terrain_images[112] + if blockID in (27, 28, 66): # minetrack: + if blockID == 27: # powered rail + if data & 0x8 == 0: # unpowered + raw_straight = terrain_images[163] + raw_corner = terrain_images[112] # they don't exist but make the code + # much simplier + elif data & 0x8 == 0x8: # powered + raw_straight = terrain_images[179] + raw_corner = terrain_images[112] # leave corners for code simplicity + # filter the 'powered' bit + data = data & 0x7 + + elif blockID == 28: # detector rail + raw_straight = terrain_images[195] + raw_corner = terrain_images[112] # leave corners for code simplicity + + elif blockID == 66: # normal rail + raw_straight = terrain_images[128] + raw_corner = terrain_images[112] + ## use transform_image to scale and shear if data == 0: track = transform_image(raw_straight, blockID) @@ -1215,8 +1231,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, 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]) +special_blocks = set([ 2, 9, 17, 18, 23, 27, 28, 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. @@ -1226,6 +1243,8 @@ special_map = {} special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values. special_map[17] = range(4) # wood: normal, birch and pine 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 special_map[35] = range(16) # wool, colored and white special_map[43] = range(4) # stone, sandstone, wooden and cobblestone double-slab special_map[44] = range(4) # stone, sandstone, wooden and cobblestone slab From 3bad62f0af4d1b1604b8ac413b1d05eafefec2d7 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Thu, 21 Apr 2011 00:53:56 +0200 Subject: [PATCH 200/213] Add the new rail blocks to the transparent blocks list. --- chunk.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/chunk.py b/chunk.py index baf9efc..59ccae0 100644 --- a/chunk.py +++ b/chunk.py @@ -114,8 +114,9 @@ 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, 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, 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]) # 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, From 5a70c4686cf73e9b5c4c01552667114ec3093999 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Thu, 21 Apr 2011 01:25:36 +0200 Subject: [PATCH 201/213] Add saplings to generate_special_textures function. Also, remove all the torches from the _build_blocks. --- textures.py | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/textures.py b/textures.py index 8a91c79..850185c 100644 --- a/textures.py +++ b/textures.py @@ -226,8 +226,7 @@ def _build_block(top, side, blockID=None): ## special case for non-block things # TODO once torches are handled by generate_special_texture, remove # them from this list - if blockID in (37,38,6,39,40,50,83,75,76): ## flowers, sapling, mushrooms, regular torch, reeds, - # redstone torch on, redstone torch off + if blockID in (37,38,6,39,40,83): ## flowers, sapling, mushrooms, reeds # # instead of pasting these blocks at the cube edges, place them in the middle: # and omit the top @@ -348,7 +347,7 @@ def _build_blockimages(): # texture array (terrain_images), which comes from terrain.png's cells, left to right top to # bottom. # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - topids = [ -1, 1, 0, 2, 16, 4, 15, 17,205,205,237,237, 18, 19, 32, 33, + 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 # 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @@ -365,7 +364,7 @@ def _build_blockimages(): # And side textures of all block types # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 - sideids = [ -1, 1, 3, 2, 16, 4, 15, 17,205,205,237,237, 18, 19, 32, 33, + 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, # 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 @@ -438,6 +437,23 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"), img.split()[3]) + if blockID == 6: # saplings + if data == 1: # spruce sapling + toptexture = terrain_images[64] + sidetexture = terrain_images[64] + + if data == 2: # birch sapling + toptexture = terrain_images[80] + sidetexture = terrain_images[80] + + else: # usual and future saplings + toptexture = terrain_images[15] + sidetexture = terrain_images[15] + + img = _build_block(toptexture, sidetexture, blockID) + return (img.convert("RGB"),img.split()[3]) + + if blockID == 9: # spring water, flowing water and waterfall water watertexture = _load_image("water.png") @@ -1231,15 +1247,16 @@ 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, 27, 28, 35, 43, 44, 50, 51, 53, - 55, 58, 59, 61, 62, 64, 65, 66, 67, 71, 75, 76, 85, - 86, 91, 92]) +special_blocks = set([ 2, 6, 9, 17, 18, 23, 27, 28, 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. special_map = {} +special_map[6] = range(4) # 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. special_map[17] = range(4) # wood: normal, birch and pine special_map[23] = range(6) # dispensers, orientation From b513977a794c58d5fde3a9f2204169190c4bfee2 Mon Sep 17 00:00:00 2001 From: Alejandro Aguilera Date: Thu, 21 Apr 2011 01:28:14 +0200 Subject: [PATCH 202/213] Fix textures for saplings. --- textures.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/textures.py b/textures.py index 850185c..ac214b5 100644 --- a/textures.py +++ b/textures.py @@ -439,12 +439,12 @@ def generate_special_texture(blockID, data): if blockID == 6: # saplings if data == 1: # spruce sapling - toptexture = terrain_images[64] - sidetexture = terrain_images[64] + toptexture = terrain_images[63] + sidetexture = terrain_images[63] if data == 2: # birch sapling - toptexture = terrain_images[80] - sidetexture = terrain_images[80] + toptexture = terrain_images[79] + sidetexture = terrain_images[79] else: # usual and future saplings toptexture = terrain_images[15] From eea97d1b1c3a5f793f18f5248450456e22406a36 Mon Sep 17 00:00:00 2001 From: Alex Headley Date: Fri, 22 Apr 2011 11:11:14 -0400 Subject: [PATCH 203/213] added per-rendermode bg_color setting --- config.js | 1 - googlemap.py | 3 ++- web_assets/functions.js | 16 +++++++++++----- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/config.js b/config.js index 41ee9e4..6ccd3ee 100644 --- a/config.js +++ b/config.js @@ -7,7 +7,6 @@ //center: [0,0,0], center: {spawn_coords}, cacheMinutes: 0, // Change this to have browsers automatically request new images every x minutes - bg_color: '{bg_color}', // You can set this in settings.py debug: false }; diff --git a/googlemap.py b/googlemap.py index 9e04b22..868297c 100644 --- a/googlemap.py +++ b/googlemap.py @@ -95,11 +95,12 @@ class MapGen(object): config = config.replace("{spawn_coords}", json.dumps(list(self.world.spawn))) - config = config.replace("{bg_color}", self.bg_color) + #config = config.replace("{bg_color}", self.bg_color) # create generated map type data, from given quadtrees maptypedata = map(lambda q: {'label' : q.rendermode.capitalize(), 'path' : q.tiledir, + 'bg_color': self.bg_color, 'overlay' : 'overlay' in get_render_mode_inheritance(q.rendermode), 'imgformat' : q.imgformat}, self.quadtrees) diff --git a/web_assets/functions.js b/web_assets/functions.js index b8ace5e..639970a 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -477,7 +477,6 @@ function initialize() { }, mapTypeId: mapTypeIdDefault, streetViewControl: false, - backgroundColor: config.bg_color, }; map = new google.maps.Map(document.getElementById('mcmap'), mapOptions); @@ -501,9 +500,6 @@ function initialize() { map.mapTypes.set('mcmap' + MCMapType[idx].name, MCMapType[idx]); } - // We can now set the map to use the 'coordinate' map type - map.setMapTypeId(mapTypeIdDefault); - // initialize the markers and regions initMarkers(); initRegions(); @@ -518,7 +514,17 @@ function initialize() { google.maps.event.addListener(map, 'center_changed', function() { makeLink(); }); - + google.maps.event.addListener(map, 'maptypeid_changed', function() { + var newType = map.getMapTypeId(); + for(i in mapTypeData) { + if( 'mcmap' + mapTypeData[i].label == newType ) { + $('#mcmap').css('background-color', mapTypeData[i].bg_color); + break; + } + } + }); + // We can now set the map to use the 'coordinate' map type + map.setMapTypeId(mapTypeIdDefault); } From fa49aff4b0097711de4217dbd3cf080e9ee432f1 Mon Sep 17 00:00:00 2001 From: Alex Headley Date: Fri, 22 Apr 2011 11:19:02 -0400 Subject: [PATCH 204/213] added minzoom config.js setting --- config.js | 1 + googlemap.py | 2 ++ web_assets/functions.js | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index 6ccd3ee..839d724 100644 --- a/config.js +++ b/config.js @@ -2,6 +2,7 @@ var config = { tileSize: 384, defaultZoom: 2, + minZoom: {minzoom}, maxZoom: {maxzoom}, // center on this point, in world coordinates, ex: //center: [0,0,0], diff --git a/googlemap.py b/googlemap.py index 868297c..25b5c70 100644 --- a/googlemap.py +++ b/googlemap.py @@ -89,6 +89,8 @@ class MapGen(object): configpath = os.path.join(util.get_program_path(), "config.js") config = open(configpath, 'r').read() + config = config.replace( + "{minzoom}", str(0)) config = config.replace( "{maxzoom}", str(zoomlevel)) diff --git a/web_assets/functions.js b/web_assets/functions.js index 639970a..9fd7d0b 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -659,7 +659,7 @@ for (idx in mapTypeData) { getTileUrl: getTileUrlGenerator(view.path, view.base, imgformat), tileSize: new google.maps.Size(config.tileSize, config.tileSize), maxZoom: config.maxZoom, - minZoom: 0, + minZoom: config.minZoom, isPng: !(imgformat.match(/^png$/i) == null) }; From f9f98960a056ab69278b94aeca64ccd13b9bff7a Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Fri, 22 Apr 2011 23:24:57 -0400 Subject: [PATCH 205/213] Don't let a missing markers.js or region.js foul up everything Fixes #330 --- web_assets/functions.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/web_assets/functions.js b/web_assets/functions.js index 9fd7d0b..847fba9 100644 --- a/web_assets/functions.js +++ b/web_assets/functions.js @@ -211,6 +211,7 @@ function drawMapControls() { // parse the data as definned in the regions.js function initRegions() { if (regionsInit) { return; } + if (typeof(regionData) == "undefined") { return; } // skip this if we have no region.js file regionsInit = true; for (i in regionGroups) { @@ -295,6 +296,7 @@ function initRegions() { // may need to be reviewed by agrif or someone else... little finicky right now. function initMarkers() { if (markersInit) { return; } // oh, we've already done this? nevermind, exit the function. + if (typeof(markerData) == "undefined") { return; } // no markers.js file, so skip this. markersInit = true; // now that we've started, dont have to do it twice. // first, give all collections an empty array to work with From 48d99a674f9bcbedba98ae12a41990940e4e287c Mon Sep 17 00:00:00 2001 From: Alex Headley Date: Sat, 23 Apr 2011 14:02:00 -0400 Subject: [PATCH 206/213] moving style stuff out of index.html --- web_assets/index.html | 2 +- web_assets/style.css | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/web_assets/index.html b/web_assets/index.html index 89554e8..961dd75 100644 --- a/web_assets/index.html +++ b/web_assets/index.html @@ -14,6 +14,6 @@ -
+
diff --git a/web_assets/style.css b/web_assets/style.css index 8585eb3..19b9b34 100644 --- a/web_assets/style.css +++ b/web_assets/style.css @@ -1,6 +1,18 @@ -html { height: 100% } -body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; } -#mcmap { height: 100% } +html { + height: 100%; +} + +body { + height: 100%; + margin: 0px; + padding: 0px; + background-color: #000; +} + +#mcmap { + width: 100%; + height: 100%; +} .infoWindow { height: 100px; From df2117544d603c0331038aac1aef658b26525a95 Mon Sep 17 00:00:00 2001 From: aheadley Date: Sat, 23 Apr 2011 18:54:12 -0400 Subject: [PATCH 207/213] new js stuff ready for testing --- web_assets/index.html | 12 +- web_assets/overviewer.js | 706 +++++++++++++++++++++++++++++++++ web_assets/overviewerConfig.js | 38 ++ 3 files changed, 748 insertions(+), 8 deletions(-) create mode 100644 web_assets/overviewer.js create mode 100644 web_assets/overviewerConfig.js diff --git a/web_assets/index.html b/web_assets/index.html index 961dd75..14c6666 100644 --- a/web_assets/index.html +++ b/web_assets/index.html @@ -3,17 +3,13 @@ - - - - - + + + - +
diff --git a/web_assets/overviewer.js b/web_assets/overviewer.js new file mode 100644 index 0000000..3b10189 --- /dev/null +++ b/web_assets/overviewer.js @@ -0,0 +1,706 @@ +var overviewer = { + 'map': null, + 'collections': { + 'markerDatas': [], + 'markers': {}, + 'regionDatas': [], + 'regions': {}, + 'overlays': [], + 'mapTypes': {}, + 'infoWindow': null + }, + 'util': { + 'initialize': function() { + overviewer.util.initializeClassPrototypes(); + overviewer.util.initializeMapTypes(); + overviewer.util.initializeMap(); + overviewer.util.initializeMarkers(); + overviewer.util.initializeRegions(); + overviewer.util.createMapControls(); + }, + 'initializeClassPrototypes': function() { + overviewer.classes.MapProjection.prototype.fromLatLngToPoint = function(latLng) { + var x = latLng.lng() * overviewerConfig.CONST.tileSize; + var y = latLng.lat() * overviewerConfig.CONST.tileSize; + return new google.maps.Point(x, y); + }; + + overviewer.classes.MapProjection.prototype.fromPointToLatLng = function(point) { + var lng = point.x * this.inverseTileSize; + var lat = point.y * this.inverseTileSize; + return new google.maps.LatLng(lat, lng); + }; + + overviewer.classes.CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) { + var div = ownerDocument.createElement('DIV'); + div.innerHTML = '(' + coord.x + ', ' + coord.y + ', ' + zoom + ')'; + div.innerHTML += '
'; + div.innerHTML += overviewer.collections.mapTypes[0].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; + }; + }, + /** + * I think this was old code that was replaced by stuff that is now + * in initializeMap() + */ + 'initializeMapTypes': function() { + var mapOptions = {}; + for (i in overviewerConfig.mapTypes) { + var view = overviewerConfig.mapTypes[i]; + var imageFormat = view.imgformat ? view.imgformat : 'png'; + mapOptions[view.label] = { + 'getTileUrl': overviewer.gmap.getTileUrlGenerator(view.path, + view.base, imageFormat), + 'tileSize': new google.maps.Size( + overviewerConfig.CONST.tileSize, + overviewerConfig.CONST.tileSize), + 'maxZoom': overviewerConfig.map.maxZoom, + 'minZoom': overviewerConfig.map.minZoom, + 'isPng': imageFormat.toLowerCase() == 'png' + } + overviewer.collections.mapTypes[view.label] = new google.maps.ImageMapType( + mapOptions[view.label]); + overviewer.collections.mapTypes[view.label].name = view.label; + overviewer.collections.mapTypes[view.label].alt = 'Minecraft ' + + view.label + ' Map'; + overviewer.collections.mapTypes[view.label].projection = + new overviewer.classes.MapProjection(); + if (view.overlay) { + overviewer.collections.overlays.push( + overviewer.collections.mapTypes[view.label]); + } else { + overviewer.collections.mapTypeIds.push( + overviewerConfig.CONST.mapDivId + view.label); + } + } + }, + 'initilizeMap': function() { + var defaultCenter = overviewer.util.fromWorldToLatLng( + overviewerConfig.map.center[0], overviewerConfig.map.center[1], + overviewerConfig.map.center[2]); + var lat = defaultCenter.lat(); + var lng = defaultCenter.lng(); + var zoom = overviewerConfig.map.defaultZoom; + var mapcenter; + queryParams = overviewer.util.parseQueryString(); + if (queryParams.lat) { + lat = parseFloat(queryParams.lat); + } + if (queryParams.lng) { + lng = parseFloat(queryParams.lng); + } + if (queryParams.zoom) { + if (queryParams.zoom == 'max') { + zoom = overviewerConfig.map.maxZoom; + } else if (queryParams.zoom == 'min') { + zoom = overviewerConfig.map.minZoom; + } else { + zoom = parseInt(queryParams.zoom); + if (zoom < 0 && zoom + overvierConfig.map.maxZoom >= 0) { + //if zoom is negative, try to treat as "zoom out from max zoom" + zoom += overviewerConfig.map.maxZoom; + } else { + //fall back to default zoom + zoom = overviewerConfig.map.defaultZoom; + } + } + } + if (queryParams.x && queryParams.y && queryParams.z) { + mapcenter = overviewer.util.fromWorldToLatLng(queryParams.x, + queryParams.y, queryParams.z); + // Add a market indicating the user-supplied position + overviewer.collections.markers.push({ + 'msg': 'Coordinates ' + queryParams.x + ', ' + + queryParams.y + ', ' + queryParams.z, + 'y': parseFloat(queryParams.y), + 'x': parseFloat(queryParams.x), + 'z': parseFloat(queryParams.z), + 'type': 'querypos'}); + } else { + mapcenter = new google.maps.LatLng(lat, lng); + } + var mapOptions = { + zoom: zoom, + center: mapcenter, + navigationControl: overviewerConfig.map.controls.navigation, + scaleControl: false, + mapTypeControl: overviewer.collections.mapTypeIds.length > 1, + mapTypeControlOptions: { + mapTypeIds: overviewer.collections.mapTypeIds + }, + mapTypeId: overviewer.util.getDefaultMapTypeId(), + streetViewControl: false + }; + overviewer.map = new google.maps.Map(document.getElementById( + overviewerConfig.CONST.mapDivId), mapOptions); + + if (overviewerConfig.map.debug) { + overviewer.map.overlayMapTypes.insertAt(0, + new CoordMapType(new google.maps.Size( + overviewerConfig.CONST.tileSize, + overviewerConfig.CONST.tileSize))); + google.maps.event.addListener(overviewer.map, 'click', function(event) { + overviewer.util.debug('latLng: (' + event.latLng.lat() + + ', ' + event.latLng.lng() + ')'); + var pnt = overviewer.map.getProjection().fromLatLngToPoint(event.latLng); + overviewer.util.debug('point: ' + pnt); + var pxx = pnt.x * config.tileSize * Math.pow(2, config.maxZoom); + var pxy = pnt.y * config.tileSize * Math.pow(2, config.maxZoom); + overviewer.util.debug('pixel: (' + pxx + ', ' + pxy + ')'); + }); + } + + // Now attach the coordinate map type to the map's registry + for (i in overviewer.collections.mapTypes) { + overviewer.map.mapTypes.set(overviewerConfig.CONST.mapDivId + + overviewer.collections.mapTypes[i].name, + overviewer.collections.mapTypes[i]); + } + + // Make the link again whenever the map changes + google.maps.event.addListener(overviewer.map, 'zoom_changed', function() { + overviewer.util.setViewUrl(); + }); + google.maps.event.addListener(overviewer.map, 'center_changed', function() { + overviewer.util.setViewUrl(); + }); + google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function() { + var newType = overviewer.map.getMapTypeId(); + for(i in overviewerConfig.mapTypes) { + if( overviewerConfig.CONST.mapDivId + + overviewerConfig.mapTypes[i].label == newType ) { + $('#'+overviewerConfig.CONST.mapDivId).css( + 'background-color', overviewerConfig.mapTypes[i].bg_color); + break; + } + } + }); + // We can now set the map to use the 'coordinate' map type + overviewer.map.setMapTypeId(overviewer.util.getDefaultMapTypeId()); + }, + 'initializeMarkers': function() { + //first, give all collections an empty array to work with + for (i in overviewerConfig.objectGroups.markers) { + overviewer.collections.markers[ + overviewerConfig.objectGroups.markers[i].label] = []; + } + for (i in overviewer.collections.markerDatas) { + var markerData = overviewer.collections.markerDatas[i]; + for (j in markerData) { + var item = markerData[j]; + // a default: + var iconURL = ''; + if (item.type == 'spawn') { + // don't filter spawn, always display + var marker = new google.maps.Marker({ + 'position': overviewer.util.fromWorldToLatLng(item.x, + item.y, item.z), + 'map': overviewer.map, + 'title': jQuery.trim(item.msg), + 'icon': overviewerConfig.CONST.image.spawnMarker + }); + continue; + } + + if (item.type == 'querypos') { + // Set on page load if MC x/y/z coords are given in the + // query string + var marker = new google.maps.Marker({ + 'position': overviewer.util.fromWorldToLatLng(item.x, + item.y, item.z), + 'map': overviewer.map, + 'title': jQuery.trim(item.msg), + 'icon': overviewerConfig.CONST.image.queryMarker + }); + continue; + } + + var matched = false; + for (j in overviewerConfig.objectGroups.signs) { + var signGroup = overviewerConfig.objectGroups.signs[j]; + var label = signGroup.label; + if (signGroup.match(item)) { + matched = true; + // can add custom types of images for externally defined + // item types, like 'command' here. + if (item.type == 'sign') { + iconURL = overviewerConfig.CONST.image.signMarker; + } + overviewer.util.debug('Sign icon: ' + signGroup.icon); + if (signGroup.icon) { + iconURL = signGroup.icon; + } + var converted = fromWorldToLatLng(item.x, item.y, item.z); + var marker = new google.maps.Marker({ + 'position': overviewer.util.fromWorldToLatLng(item.x, + item.y, item.z), + 'map': overviewer.map, + 'title': jQuery.trim(item.msg), + 'icon': iconURL, + 'visible': false + }); + overviewer.collections.markers[label].push(marker); + if (item.type == 'sign') { + overviewer.util.createMarkerInfoWindow(marker); + } + } + } + + if (!matched) { + // is this signpost doesn't match any of the groups in + // config.js, add it automatically to the "__others__" group + if (item.type == 'sign') { + iconURL = overviewerConfig.CONST.image.signMarker; + } + var marker = new google.maps.Marker({position: converted, + 'position': overviewer.util.fromWorldToLatLng(item.x, + item.y, item.z), + 'map': overviewer.map, + 'title': jQuery.trim(item.msg), + 'icon': iconURL, + 'visible': false + }); + if (overviewer.collections.markers['__others__']) { + overviewer.collections.markers['__others__'].push(marker); + } else { + overviewer.collections.markers['__others__'] = [marker]; + } + if (item.type == 'sign') { + overviewer.util.createMarkerInfoWindow(marker, item); + } + } + } + } + }, + 'initializeRegions': function() { + for (i in overviewerConfig.objectGroups.regions) { + overviewer.collections.regions[overviewerConfig.objectGroups.regions[i].label] = []; + } + for (i in overviewer.collections.regionDatas) { + var regionData = overviewer.collections.regionDatas[i]; + for (j in regionData) { + var region = regionData[j]; + // pull all the points out of the regions file. + var converted = new google.maps.MVCArray(); + for (k in region.path) { + var point = region.path[k]; + converted.push(overviewer.util.fromWorldToLatLng( + point.x, point.y, point.z)); + + } + for (k in overviewerConfig.objectGroups.regions) { + var regionGroup = overviewerConfig.objectGroups.regions[k]; + var clickable = regionGroup.clickable; + var label = regionGroup.label; + + if(region.label) { + var name = region.label + } else { + var name = 'rawr'; + clickable = false; // if it doesn't have a name, we dont have to show it. + } + + if (region.closed) { + var shape = new google.maps.Polygon({ + 'name': name, + 'clickable': clickable, + 'geodesic': false, + 'map': null, + 'strokeColor': region.color, + 'strokeOpacity': region.opacity, + 'strokeWeight': overviewerConfig.CONST.regionStrokeWeight, + 'fillColor': region.color, + 'fillOpacity': region.opacity * 0.25, + 'zIndex': j, + 'paths': converted + }); + } else { + var shape = new google.maps.Polyline({ + 'name': name, + 'clickable': clickable, + 'geodesic': false, + 'map': null, + 'strokeColor': region.color, + 'strokeOpacity': region.opacity, + 'strokeWeight': overviewerConfig.CONST.regionStrokeWeight, + 'zIndex': j, + 'path': converted + }); + } + overviewer.collections.regions[label].push(shape); + + if (clickable) { + overviewer.util.createRegionInfoWindow(shape); + } + } + } + } + }, + 'debug': function(msg) { + if (overviewerConfig.map.debug) { + console.log(msg); + } + }, + 'parseQueryString': function() { + var results = {}; + var queryString = location.search.substring(1); + var pairs = queryString.split('&'); + for (i in pairs) { + var pos = pairs[i].indexOf('='); + var key = pairs[i].substring(0,pos).toLowerCase(); + var value = pairs[i].substring(pos+1).toLowerCase(); + overviewer.util.debug( 'Found GET paramter: ' + key + ' = ' + value); + results[key] = value; + } + return results; + }, + 'setViewUrl': function() { + var displayZoom = overviewer.map.getZoom(); + if (displayZoom == overviewerConfig.map.maxZoom) { + displayZoom = 'max'; + } else { + displayZoom -= overviewerConfig.map.maxZoom; + } + var point; + var point = overviewer.util.fromLatLngToWorld( + overviewer.map.getCenter().lat(), overviewer.map.getCenter().lng()); + var viewUrl = location.href.substring(0, location.href.lastIndexOf( + location.search)) + + '?x=' + Math.floor(point.x) + + '&y=' + Math.floor(point.y) + + '&z=' + Math.floor(point.z) + + '&zoom=' + displayZoom; + document.getElementById('link').innerHTML = viewUrl; + + }, + 'getDefaultMapTypeId': function() { + return overviewer.collections.mapTypeIds[0]; + }, + 'fromWorldToLatLng': function(x, z, y) { + // the width and height of all the highest-zoom tiles combined, + // inverted + var perPixel = 1.0 / (overviewerConfig.CONST.tileSize * + Math.pow(2, overviewerConfig.map.maxZoom)); + + // This information about where the center column is may change with + // a different drawing implementation -- check it again after any + // drawing overhauls! + + // point (0, 0, 127) is at (0.5, 0.0) of tile (tiles/2 - 1, tiles/2) + // so the Y coordinate is at 0.5, and the X is at 0.5 - + // ((tileSize / 2) / (tileSize * 2^maxZoom)) + // or equivalently, 0.5 - (1 / 2^(maxZoom + 1)) + var lng = 0.5 - (1.0 / Math.pow(2, overviewerConfig.map.maxZoom + 1)); + var lat = 0.5; + + // the following metrics mimic those in ChunkRenderer.chunk_render + // in "chunk.py" or, equivalently, chunk_render in src/iterate.c + + // each block on X axis adds 12px to x and subtracts 6px from y + lng += 12 * x * perPixel; + lat -= 6 * x * perPixel; + + // each block on Y axis adds 12px to x and adds 6px to y + lng += 12 * y * perPixel; + lat += 6 * y * perPixel; + + // each block down along Z adds 12px to y + lat += 12 * (128 - z) * perPixel; + + // add on 12 px to the X coordinate to center our point + lng += 12 * perPixel; + + return new google.maps.LatLng(lat, lng); + }, + 'fromLatLngToWorld': function(lat, lng) { + // Initialize world x/y/z object to be returned + var point = Array(); + point.x = 0; + point.y = 64; + point.z = 0; + + // the width and height of all the highest-zoom tiles combined, + // inverted + var perPixel = 1.0 / (overviewerConfig.CONST.tileSize * + Math.pow(2, overviewerConfig.map.maxZoom)); + + // Revert base positioning + // See equivalent code in fromWorldToLatLng() + lng -= 0.5 - (1.0 / Math.pow(2, overviewerConfig.map.maxZoom + 1)); + lat -= 0.5; + + // I'll admit, I plugged this into Wolfram Alpha: + // a = (x * 12 * r) + (z * 12 * r), b = (z * 6 * r) - (x * 6 * r) + // And I don't know the math behind solving for for X and Z given + // A (lng) and B (lat). But Wolfram Alpha did. :) I'd welcome + // suggestions for splitting this up into long form and documenting + // it. -RF + point.x = (lng - 2 * lat) / (24 * perPixel) + point.z = (lng + 2 * lat) / (24 * perPixel) + + // Adjust for the fact that we we can't figure out what Y is given + // only latitude and longitude, so assume Y=64. + point.x += 64 + 1; + point.z -= 64 + 2; + + return point; + }, + 'createMapControls': function() { + // viewstate link (little link to where you're looking at the map, + // normally bottom left) + var viewStateDiv = document.createElement('DIV'); + viewStateDiv.id='link'; + // add it to the map, bottom left. + map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(viewStateDiv); + + // compass rose, in the top right corner + var compassDiv = document.createElement('DIV'); + compassDiv.style.padding = '5px'; + var compassImg = document.createElement('IMG'); + compassImg.src = overviewerConfig.CONST.image.compass; + compassDiv.appendChild(compassImg); + compassDiv.index = 0; + // add it to the map, top right. + map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv); + + // Spawn button + var homeControlDiv = document.createElement('DIV'); + var homeControl = new overviewer.classes.HomeControl(homeControlDiv); + homeControlDiv.id = 'customControl'; + homeControlDiv.index = 1; + overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv); + + // only need to create the control if there are items in the list. + // as defined in config.js + if (overviewerConfig.objectGroups.signs.length > 0) { + // signpost display control + var items = []; + for (i in overviewerConfig.objectGroups.signs) { + var signGroup = overviewerConfig.objectGroups.signs[i]; + var iconURL = signGroup.icon; + if(!iconURL) { + iconURL = overviewerConfig.CONST.image.defaultMarker; + } + items.push({ + 'label': signGroup.label, + 'checked': signGroup.checked, + 'icon': iconURL, + 'action': function(n, item, checked) { + jQuery.each(overviewer.collections.markers[item.label], + function(i,elem) { + elem.setVisible(checked); + } + ); + overviewer.util.debug('Adding sign item: ' + item.label); + } + }); + } + overviewer.util.createDropDown('Signposts', items); + } + + // if there are any regions data, lets show the option to hide/show them. + if (overviewerConfig.objectGroups.regions.length > 0) { + // region display control + var items = []; + for (i in overviewerConfig.objectGroups.regions) { + var regionGroup = overviewerConfig.objectGroups.regions[i]; + items.push({ + 'label': regionGroup.label, + 'checked': regionGroup.checked, + 'action': function(n, item, checked) { + jQuery.each(overviewer.collections.regions[item.label], + function(i,elem) { + // Thanks to LeastWeasel for this line! + elem.setMap(checked ? overviewer.map : null); + } + ); + } + }); + } + overviewer.util.createDropDown('Regions', items); + } + + if (overviewer.collections.overlays.length > 0) { + // overlay maps control + var items = []; + for (i in overviewer.collections.overlays) { + var overlay = overviewer.collections.overlays[i]; + items.push({ + 'label': overlay.name, + 'checked': false, + 'overlay': overlay, + 'action': function(i, item, checked) { + if (checked) { + overviewer.map.overlayMapTypes.push(item.overlay); + } else { + var idx_to_delete = -1; + overviewer.map.overlayMapTypes.forEach(function(e, j) { + if (e == item.overlay) { idx_to_delete = j; } + }); + if (idx_to_delete >= 0) { + overviewer.map.overlayMapTypes.removeAt(idx_to_delete); + } + } + } + }); + } + overviewer.util.createDropDown('Overlays', items); + } + }, + 'createDropDown': function(title, items) { + var control = document.createElement('DIV'); + // let's let a style sheet do most of the styling here + control.id = 'customControl'; + + var controlText = document.createElement('DIV'); + controlText.innerHTML = title; + + var controlBorder = document.createElement('DIV'); + controlBorder.id='top'; + control.appendChild(controlBorder); + controlBorder.appendChild(controlText); + + var dropdownDiv = document.createElement('DIV'); + dropdownDiv.id='dropDown'; + control.appendChild(dropdownDiv); + dropdownDiv.innerHTML=''; + + // add the functionality to toggle visibility of the items + $(controlText).click(function() { + $(dropdownDiv).toggle(); + }); + + // add that control box we've made back to the map. + overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(control); + + for(i in items) { + // create the visible elements of the item + var item = items[i]; + overviewer.util.debug(item); + var itemDiv = document.createElement('div'); + var itemInput = document.createElement('input'); + itemInput.type='checkbox'; + + // give it a name + $(itemInput).data('label',item.label); + jQuery(itemInput).click((function(local_idx, local_item) { + return function(e) { + item.action(local_idx, local_item, e.target.checked); + }; + })(i, item)); + + // if its checked, its gotta do something, do that here. + if (item.checked) { + itemInput.checked = true; + item.action(i, item.label, item.checked); + } + dropdownDiv.appendChild(itemDiv); + itemDiv.appendChild(itemInput); + var textNode = document.createElement('text'); + if(item.icon) { + textNode.innerHTML = '' + item.label + '
'; + } else { + textNode.innerHTML = item.label + '
'; + } + + itemDiv.appendChild(textNode); + } + }, + 'createRegionInfoWindow': function(shape) { + var infowindow = new google.maps.InfoWindow(); + google.maps.event.addListener(shape, 'click', function(event, i) { + if (overviewer.collections.infoWindow) { + overviewer.collections.infoWindow.close(); + } + // Replace our Info Window's content and position + var contentString = 'Region: ' + shape.name + '
' + + 'Clicked Location:
' + event.latLng.lat() + ', ' + + event.latLng.lng() + '
'; + infowindow.setContent(contentString); + infowindow.setPosition(event.latLng); + infowindow.open(overviewer.map); + overviewer.collections.infoWindow = infowindow; + }); + }, + 'createMarkerInfoWindow': function(marker) { + var windowContent = '

' + marker.title.replace(/\n/g,'
') + '

'; + var infowindow = new google.maps.InfoWindow({ + 'content': windowContent + }); + google.maps.event.addListener(marker, 'click', function() { + if (overviewer.collections.infoWindow) { + overviewer.collections.infoWindow.close(); + } + infowindow.open(overviewer.map, marker); + overviewer.collections.infoWindow = infowindow; + }); + } + }, + 'classes': { + 'HomeControl': function(controlDiv) { + controlDiv.style.padding = '5px'; + // Set CSS for the control border + var control = document.createElement('DIV'); + control.id='top'; + control.title = 'Click to center the map on the Spawn'; + controlDiv.appendChild(control); + + // Set CSS for the control interior + var controlText = document.createElement('DIV'); + controlText.innerHTML = 'Spawn'; + controlText.id='button'; + control.appendChild(controlText); + + // Setup the click event listeners: simply set the map to map center + // as definned below + google.maps.event.addDomListener(control, 'click', function() { + overviewer.map.panTo(fromWorldToLatLng( + overviewerConfig.map.center[0], + overviewerConfig.map.center[1], + overviewerConfig.map.center[2])); + }); + }, + 'MapProjection' : function() { + this.inverseTileSize = 1.0 / overviewerConfig.CONST.tileSize; + }, + //Looks like this is only used for debugging + 'CoordMapType': function(tileSize) { + this.tileSize = tileSize; + } + }, + 'gmap': { + 'getTileUrlGenerator': function(path, pathBase, pathExt) { + return function(tile, zoom) { + var url = path; + var urlBase = ( pathBase ? pathBase : '' ); + 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 + '.' + pathExt; + if(overviewerConfig.map.cacheMinutes > 0) { + var d = new Date(); + url += '?c=' + Math.floor(d.getTime() / + (1000 * 60 * overviewerConfig.map.cacheMinutes)); + } + return(urlBase + url); + } + } + } +}; \ No newline at end of file diff --git a/web_assets/overviewerConfig.js b/web_assets/overviewerConfig.js new file mode 100644 index 0000000..a0e8f33 --- /dev/null +++ b/web_assets/overviewerConfig.js @@ -0,0 +1,38 @@ +var overviewerConfig = { + //These will probably never change + 'CONST': { + 'tileSize': 384, + 'image': { + 'defaultMarker': 'signpost.png', + 'signMarker': 'signpost_icon.png', + 'compass': 'compass.png', + 'spawnMarker': 'http://google-maps-icons.googlecode.com/files/home.png', + 'queryMarker': 'http://google-maps-icons.googlecode.com/files/regroup.png' + }, + 'mapDivId': 'mcmap', + 'regionStrokeWeight': 2 + }, + 'map': { + 'controls': { + 'navigation': true + }, + 'defaultZoom': 0, + 'minZoom': {minzoom}, + 'maxZoom': {maxzoom}, + 'center': {spawn_coords}, + 'cacheMinutes': 0, + 'debug': false, + }, + 'objectGroups': { + 'signs': [ + { + 'label': 'All', + 'match': function(sign) { + return true; + } + } + ], + 'regions': [] + }, + 'mapTypes': {maptypedata} +}; \ No newline at end of file From 37a7b28bc8104f4362852c8ce3fac59c03afe4ca Mon Sep 17 00:00:00 2001 From: Alex Headley Date: Sat, 23 Apr 2011 20:21:59 -0400 Subject: [PATCH 208/213] fixes from testing --- web_assets/overviewer.js | 70 ++++++++++++++++++---------------- web_assets/overviewerConfig.js | 12 +++++- 2 files changed, 48 insertions(+), 34 deletions(-) diff --git a/web_assets/overviewer.js b/web_assets/overviewer.js index 3b10189..4b0d986 100644 --- a/web_assets/overviewer.js +++ b/web_assets/overviewer.js @@ -7,6 +7,7 @@ var overviewer = { 'regions': {}, 'overlays': [], 'mapTypes': {}, + 'mapTypeIds': [], 'infoWindow': null }, 'util': { @@ -35,10 +36,10 @@ var overviewer = { var div = ownerDocument.createElement('DIV'); div.innerHTML = '(' + coord.x + ', ' + coord.y + ', ' + zoom + ')'; div.innerHTML += '
'; - div.innerHTML += overviewer.collections.mapTypes[0].getTileUrl(coord, zoom); + //div.innerHTML += overviewer.collections.mapTypes[0].getTileUrl(coord, zoom); div.style.width = this.tileSize.width + 'px'; div.style.height = this.tileSize.height + 'px'; - div.style.fontSize = '10'; + div.style.fontSize = '10px'; div.style.borderStyle = 'solid'; div.style.borderWidth = '1px'; div.style.borderColor = '#AAAAAA'; @@ -80,7 +81,7 @@ var overviewer = { } } }, - 'initilizeMap': function() { + 'initializeMap': function() { var defaultCenter = overviewer.util.fromWorldToLatLng( overviewerConfig.map.center[0], overviewerConfig.map.center[1], overviewerConfig.map.center[2]); @@ -142,7 +143,7 @@ var overviewer = { if (overviewerConfig.map.debug) { overviewer.map.overlayMapTypes.insertAt(0, - new CoordMapType(new google.maps.Size( + new overviewer.classes.CoordMapType(new google.maps.Size( overviewerConfig.CONST.tileSize, overviewerConfig.CONST.tileSize))); google.maps.event.addListener(overviewer.map, 'click', function(event) { @@ -150,8 +151,10 @@ var overviewer = { ', ' + event.latLng.lng() + ')'); var pnt = overviewer.map.getProjection().fromLatLngToPoint(event.latLng); overviewer.util.debug('point: ' + pnt); - var pxx = pnt.x * config.tileSize * Math.pow(2, config.maxZoom); - var pxy = pnt.y * config.tileSize * Math.pow(2, config.maxZoom); + var pxx = pnt.x * overviewerConfig.CONST.tileSize * + Math.pow(2, overviewerConfig.map.maxZoom); + var pxy = pnt.y * overviewerConfig.CONST.tileSize * + Math.pow(2, overviewerConfig.map.maxZoom); overviewer.util.debug('pixel: (' + pxx + ', ' + pxy + ')'); }); } @@ -186,9 +189,11 @@ var overviewer = { }, 'initializeMarkers': function() { //first, give all collections an empty array to work with - for (i in overviewerConfig.objectGroups.markers) { + for (i in overviewerConfig.objectGroups.signs) { + overviewer.util.debug('Found sign group: ' + + overviewerConfig.objectGroups.signs[i].label); overviewer.collections.markers[ - overviewerConfig.objectGroups.markers[i].label] = []; + overviewerConfig.objectGroups.signs[i].label] = []; } for (i in overviewer.collections.markerDatas) { var markerData = overviewer.collections.markerDatas[i]; @@ -236,7 +241,6 @@ var overviewer = { if (signGroup.icon) { iconURL = signGroup.icon; } - var converted = fromWorldToLatLng(item.x, item.y, item.z); var marker = new google.maps.Marker({ 'position': overviewer.util.fromWorldToLatLng(item.x, item.y, item.z), @@ -245,6 +249,7 @@ var overviewer = { 'icon': iconURL, 'visible': false }); + overviewer.util.debug(label); overviewer.collections.markers[label].push(marker); if (item.type == 'sign') { overviewer.util.createMarkerInfoWindow(marker); @@ -258,7 +263,7 @@ var overviewer = { if (item.type == 'sign') { iconURL = overviewerConfig.CONST.image.signMarker; } - var marker = new google.maps.Marker({position: converted, + var marker = new google.maps.Marker({ 'position': overviewer.util.fromWorldToLatLng(item.x, item.y, item.z), 'map': overviewer.map, @@ -457,7 +462,7 @@ var overviewer = { var viewStateDiv = document.createElement('DIV'); viewStateDiv.id='link'; // add it to the map, bottom left. - map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(viewStateDiv); + overviewer.map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(viewStateDiv); // compass rose, in the top right corner var compassDiv = document.createElement('DIV'); @@ -467,7 +472,7 @@ var overviewer = { compassDiv.appendChild(compassImg); compassDiv.index = 0; // add it to the map, top right. - map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv); + overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv); // Spawn button var homeControlDiv = document.createElement('DIV'); @@ -493,7 +498,7 @@ var overviewer = { 'icon': iconURL, 'action': function(n, item, checked) { jQuery.each(overviewer.collections.markers[item.label], - function(i,elem) { + function(i, elem) { elem.setVisible(checked); } ); @@ -514,14 +519,13 @@ var overviewer = { 'label': regionGroup.label, 'checked': regionGroup.checked, 'action': function(n, item, checked) { - jQuery.each(overviewer.collections.regions[item.label], - function(i,elem) { - // Thanks to LeastWeasel for this line! - elem.setMap(checked ? overviewer.map : null); - } - ); - } - }); + jQuery.each(overviewer.collections.regions[item.label], + function(i,elem) { + // Thanks to LeastWeasel for this line! + elem.setMap(checked ? overviewer.map : null); + }); + } + }); } overviewer.util.createDropDown('Regions', items); } @@ -536,17 +540,19 @@ var overviewer = { 'checked': false, 'overlay': overlay, 'action': function(i, item, checked) { - if (checked) { - overviewer.map.overlayMapTypes.push(item.overlay); - } else { - var idx_to_delete = -1; - overviewer.map.overlayMapTypes.forEach(function(e, j) { - if (e == item.overlay) { idx_to_delete = j; } - }); - if (idx_to_delete >= 0) { - overviewer.map.overlayMapTypes.removeAt(idx_to_delete); + if (checked) { + overviewer.map.overlayMapTypes.push(item.overlay); + } else { + var idx_to_delete = -1; + overviewer.map.overlayMapTypes.forEach(function(e, j) { + if (e == item.overlay) { + idx_to_delete = j; } + }); + if (idx_to_delete >= 0) { + overviewer.map.overlayMapTypes.removeAt(idx_to_delete); } + } } }); } @@ -662,7 +668,7 @@ var overviewer = { // Setup the click event listeners: simply set the map to map center // as definned below google.maps.event.addDomListener(control, 'click', function() { - overviewer.map.panTo(fromWorldToLatLng( + overviewer.map.panTo(overviewer.util.fromWorldToLatLng( overviewerConfig.map.center[0], overviewerConfig.map.center[1], overviewerConfig.map.center[2])); @@ -703,4 +709,4 @@ var overviewer = { } } } -}; \ No newline at end of file +}; diff --git a/web_assets/overviewerConfig.js b/web_assets/overviewerConfig.js index a0e8f33..841841e 100644 --- a/web_assets/overviewerConfig.js +++ b/web_assets/overviewerConfig.js @@ -32,7 +32,15 @@ var overviewerConfig = { } } ], - 'regions': [] + 'regions': [ + { + 'label': 'All', + 'clickable':true, + 'match': function(region) { + return true; + } + } + ] }, 'mapTypes': {maptypedata} -}; \ No newline at end of file +}; From 1c92776e41d5923a798ae7fd2486f6e4b56ea02c Mon Sep 17 00:00:00 2001 From: aheadley Date: Sat, 23 Apr 2011 21:46:41 -0400 Subject: [PATCH 209/213] fixed minor bugs, added comments, made python aware of new files, removed old js files --- config.js | 78 ---- googlemap.py | 12 +- overviewerConfig.js | 138 +++++++ quadtree.py | 4 +- web_assets/functions.js | 702 --------------------------------- web_assets/overviewer.js | 173 +++++++- web_assets/overviewerConfig.js | 46 --- 7 files changed, 313 insertions(+), 840 deletions(-) delete mode 100644 config.js create mode 100644 overviewerConfig.js delete mode 100644 web_assets/functions.js delete mode 100644 web_assets/overviewerConfig.js diff --git a/config.js b/config.js deleted file mode 100644 index 839d724..0000000 --- a/config.js +++ /dev/null @@ -1,78 +0,0 @@ - - var config = { - tileSize: 384, - defaultZoom: 2, - minZoom: {minzoom}, - maxZoom: {maxzoom}, - // center on this point, in world coordinates, ex: - //center: [0,0,0], - center: {spawn_coords}, - cacheMinutes: 0, // Change this to have browsers automatically request new images every x minutes - debug: false - }; - - -/* signGroups -- A list of signpost groups. A signpost can fall into zero, one, or more than one - * group. See below for some examples. - * - * Required: - * label : string. Displayed in the drop down menu control. - * match : function. Applied to each marker (from markers.js). It is returns true if the marker - * Should be part of the group. - * - * Optional: - * checked : boolean. Set to true to have the group visible by default - * icon : string. Used to specify an icon url. - */ -var signGroups = [ -// {label: "'To'", checked: false, match: function(s) {return s.msg.match(/to/)}}, -// {label: "Storage", match: function(s) {return s.msg.match(/storage/i) || s.msg.match(/dirt/i) || s.msg.match(/sand/)}}, -// {label: "Below Sealevel", match: function(s) { return s.y<64;}}, -// {label: "Info", match: function(s) { return s.msg.match("\\[info\\]");}, icon:"http://google-maps-icons.googlecode.com/files/info.png"}, - {label: "All", match: function(s) {return true}}, -]; - -/* regionGroups -- A list of region groups. A region can fall into zero, one, or more than one - * group. See below for some examples. - * regions have been designed to work with the - * WorldGuard Overviewer Region importer at https://github.com/pironic/WG2OvR But your host must support php in order - * to run WG2OvR. You can also continue to use any other region format. - * - * Required: - * label : string. Displayed in the drop down menu control. - * clickable : boolean. Will determine if we should generate an experimental info window - * that shows details about the clicked region. - * NOTE: if a region (as definned in region.js) does not have a label, this will default to false. - * match : function. Applied to each region (from region.js). It returns true if the region - * Should be part of the group. - * - * Optional: - * checked : boolean. Set to true to have the group visible by default - */ -var regionGroups = [ - //{label: "All", clickable: false, checked: false, match: function(s) {return true}}, -]; - -/* mapTypeData -- a list of alternate map renderings available. At least one rendering must be - * listed. When more than one are provided, controls to switch between them are provided, with - * the first one being the default. - * - * Required: - * label : string. Displayed on the control. - * path : string. Location of the rendered tiles. - * Optional: - * base : string. Base of the url path for tile locations, useful for serving tiles from a different server than the js/html server. - * imgformat : string. File extension used for these tiles. Defaults to png. - * overlay : bool. If true, this tile set will be treated like an overlay - -var mapTypeData=[ - {'label': 'Unlit', 'path': 'tiles'}, -// {'label': 'Day', 'path': 'lighting/tiles'}, -// {'label': 'Night', 'path': 'night/tiles', 'imgformat': 'jpg'}, -// {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'}, -// {'label': 'Overlay', 'path': 'overlay/tiles', 'overlay': true} -]; - */ - -var mapTypeData = {maptypedata}; - diff --git a/googlemap.py b/googlemap.py index 25b5c70..af56fbe 100644 --- a/googlemap.py +++ b/googlemap.py @@ -86,7 +86,7 @@ class MapGen(object): """Writes out config.js, marker.js, and region.js Copies web assets into the destdir""" zoomlevel = self.p - configpath = os.path.join(util.get_program_path(), "config.js") + configpath = os.path.join(util.get_program_path(), "overviewerConfig.js") config = open(configpath, 'r').read() config = config.replace( @@ -108,7 +108,7 @@ class MapGen(object): self.quadtrees) config = config.replace("{maptypedata}", json.dumps(maptypedata)) - with open(os.path.join(self.destdir, "config.js"), 'w') as output: + with open(os.path.join(self.destdir, "overviewerConfig.js"), 'w') as output: output.write(config) bgcolor = (int(self.bg_color[1:3],16), int(self.bg_color[3:5],16), int(self.bg_color[5:7],16), 0) @@ -151,13 +151,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=[\n") + output.write("overviewer.collections.markerDatas.push([\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") + output.write("]);\n") # save persistent data self.world.persistentData['POI'] = self.world.POI @@ -166,11 +166,11 @@ class MapGen(object): # write out the default (empty, but documented) region table with open(os.path.join(self.destdir, "regions.js"), 'w') as output: - output.write('var regionData=[\n') + output.write('overviewer.collections.regionDatas.push([\n') output.write(' // {"color": "#FFAA00", "opacity": 0.5, "closed": true, "path": [\n') output.write(' // {"x": 0, "y": 0, "z": 0},\n') output.write(' // {"x": 0, "y": 10, "z": 0},\n') output.write(' // {"x": 0, "y": 0, "z": 10}\n') output.write(' // ]},\n') - output.write('];') + output.write(']);') diff --git a/overviewerConfig.js b/overviewerConfig.js new file mode 100644 index 0000000..1ceb336 --- /dev/null +++ b/overviewerConfig.js @@ -0,0 +1,138 @@ +var overviewerConfig = { + /** + * These are things that will probably not need to be changed by the user, + * but are there because otherwise changing them is a giant PITA. + */ + 'CONST': { + /** + * Height and width of the tiles in pixels (I think). + */ + 'tileSize': 384, + /** + * Various images used for markers and stuff. + */ + 'image': { + 'defaultMarker': 'signpost.png', + 'signMarker': 'signpost_icon.png', + 'compass': 'compass.png', + 'spawnMarker': 'http://google-maps-icons.googlecode.com/files/home.png', + 'queryMarker': 'http://google-maps-icons.googlecode.com/files/regroup.png' + }, + 'mapDivId': 'mcmap', + 'regionStrokeWeight': 2 + }, + /** + * General map settings. + */ + 'map': { + /** + * Control the visibility of various controls. + */ + 'controls': { + /** + * Navigation controls are the pan and zoom typically on the upper + * left. + */ + 'navigation': true + }, + /** + * The zoom level when the page is loaded without a specific zoom setting + */ + 'defaultZoom': 0, + /** + * This controls how far you can zoom out. + */ + 'minZoom': {minzoom}, + /** + * This controls how close you can zoom in. + */ + 'maxZoom': {maxzoom}, + /** + * Center on this point, in world coordinates. Should be an array, ex: + * [0,0,0] + */ + 'center': {spawn_coords}, + /** + * Set this to tell browsers how long they should cache tiles in minutes. + */ + 'cacheMinutes': 0, + /** + * Set to true to turn on debug mode, which adds a grid to the map along + * with co-ordinates and a bunch of console output. + */ + 'debug': false, + }, + /** + * Group definitions for objects that are partially selectable (signs and + * regions). + */ + 'objectGroups': { + /* signs -- A list of signpost groups. A signpost can fall into zero, + * one, or more than one group. See below for some examples. + * + * Required: + * label : string. Displayed in the drop down menu control. + * match : function. Applied to each marker (from markers.js). It + * is returns true if the marker should be part + * of the group. + * + * Optional: + * checked : boolean. Set to true to have the group visible by default + * icon : string. Used to specify an icon url. + */ + 'signs': [ + //{label: "'To'", checked: false, match: function(s) {return s.msg.match(/to/)}}, + //{label: "Storage", match: function(s) {return s.msg.match(/storage/i) || s.msg.match(/dirt/i) || s.msg.match(/sand/)}}, + //{label: "Below Sealevel", match: function(s) { return s.y<64;}}, + //{label: "Info", match: function(s) { return s.msg.match("\\[info\\]");}, icon:"http://google-maps-icons.googlecode.com/files/info.png"}, + {'label':'All', 'match':function(sign){return true;}} + ], + /* regions -- A list of region groups. A region can fall into zero, + * one, or more than one group. See below for some examples. + * Regions have been designed to work with the WorldGuard Overviewer + * Region importer at @link https://github.com/pironic/WG2OvR but your + * host must support php in order to run WG2OvR. You can also continue + * to use any other region format. + * + * Required: + * label : string. Displayed in the drop down menu control. + * clickable : boolean. Will determine if we should generate an + * experimental info window that shows details + * about the clicked region. + * NOTE: if a region (as defined in region.js) + * does not have a label, this will default to + * false. + * match : function. Applied to each region (from region.js). It + * returns true if the region should be part of + * the group. + * + * Optional: + * checked : boolean. Set to true to have the group visible by default + */ + 'regions': [ + //{'label':'All','clickable':true,'match':function(region){return true;}} + ] + }, + /* mapTypes -- a list of alternate map renderings available. At least one + * rendering must be listed. When more than one are provided, controls to + * switch between them are provided, with the first one being the default. + * + * Required: + * label : string. Displayed on the control. + * path : string. Location of the rendered tiles. + * Optional: + * base : string. Base of the url path for tile locations, useful + * for serving tiles from a different server than + * the js/html server. + * imgformat : string. File extension used for these tiles. Defaults to png. + * overlay : bool. If true, this tile set will be treated like an overlay + * Example: + * 'mapTypes': [ + * {'label': 'Day', 'path': 'lighting/tiles'}, + * {'label': 'Night', 'path': 'night/tiles', 'imgformat': 'jpg'}, + * {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'}, + * {'label': 'Overlay', 'path': 'overlay/tiles', 'overlay': true} + * ] + */ + 'mapTypes': {maptypedata} +}; \ No newline at end of file diff --git a/quadtree.py b/quadtree.py index 58fef1b..701aa27 100644 --- a/quadtree.py +++ b/quadtree.py @@ -115,10 +115,10 @@ class QuadtreeGen(object): returns -1 if it couldn't be detected, file not found, or nothing in config.js matched """ - indexfile = os.path.join(self.destdir, "config.js") + indexfile = os.path.join(self.destdir, "overviewerConfig.js") if not os.path.exists(indexfile): return -1 - matcher = re.compile(r"maxZoom:\s*(\d+)") + matcher = re.compile(r"maxZoom.*:\s*(\d+)") p = -1 for line in open(indexfile, "r"): res = matcher.search(line) diff --git a/web_assets/functions.js b/web_assets/functions.js deleted file mode 100644 index 847fba9..0000000 --- a/web_assets/functions.js +++ /dev/null @@ -1,702 +0,0 @@ -// var def -var map; // god of the overviewer... bow before the google api. -var markerCollection = {}; // holds groups of markers -var markersInit = false; // only have to load the markers once, this just makes sure we only do it once -var regionCollection = {}; // holds groups of regions -var regionsInit = false; // only have to load the regions once, this just makes sure we only do it once - -var prevInfoWindow = null; - -// add a popup info window to the marker and then the marker to the map. -// marker is the clickable image on the map with all data. -// item is just the same item in the markers.js -function prepareSignMarker(marker, item) { - var c = "

" + item.msg.replace(/\n/g,"
") + "

"; - var infowindow = new google.maps.InfoWindow({content: c - }); - google.maps.event.addListener(marker, 'click', function() { - if (prevInfoWindow) - prevInfoWindow.close() - infowindow.open(map,marker); - prevInfoWindow = infowindow - }); -} - -// reusable function for making drop down menus. -// title = string -// items = array -function createDropDown(title, items) { - var control = document.createElement("DIV"); - control.id = "customControl"; // let's let a style sheet do most of the styling here - - var controlText = document.createElement("DIV"); - controlText.innerHTML = title; - - var controlBorder = document.createElement("DIV"); - controlBorder.id="top"; - control.appendChild(controlBorder); - controlBorder.appendChild(controlText); - - var dropdownDiv = document.createElement("DIV"); - dropdownDiv.id="dropDown"; - control.appendChild(dropdownDiv); - dropdownDiv.innerHTML=""; - - // add the functionality to toggle visibility of the items - $(controlText).click(function() { - $(dropdownDiv).toggle(); - }); - - // add that control box we've made back to the map. - map.controls[google.maps.ControlPosition.TOP_RIGHT].push(control); - - for (idx in items) { - // create the visible elements of the item - var item = items[idx]; - //console.log(item); // debug - var d = document.createElement("div"); - var n = document.createElement("input"); - n.type="checkbox"; - - // give it a name - $(n).data("label",item.label); - jQuery(n).click((function(local_idx, local_item) { - return function(e) { - item.action(local_idx, local_item, e.target.checked); - }; - })(idx, item)); - - // if its checked, its gotta do something, do that here. - if (item.checked) { - n.checked = true; - item.action(idx, item.label, item.checked); - } - dropdownDiv.appendChild(d); - d.appendChild(n) - var textNode = document.createElement("text"); - if(item.icon) { - textNode.innerHTML = "" + item.label + "
"; - } else { - textNode.innerHTML = item.label + "
"; - } - - d.appendChild(textNode); - } -} - -function HomeControl(controlDiv, map) { - - controlDiv.style.padding = '5px'; - - // Set CSS for the control border - var control = document.createElement('DIV'); - control.id='top'; - control.title = 'Click to center the map on the Spawn'; - controlDiv.appendChild(control); - - // Set CSS for the control interior - var controlText = document.createElement('DIV'); - controlText.innerHTML = 'Spawn'; - controlText.id='button'; - control.appendChild(controlText); - - // Setup the click event listeners: simply set the map to map center as definned below - google.maps.event.addDomListener(control, 'click', function() { - map.panTo(fromWorldToLatLng(config.center[0], - config.center[1], - config.center[2])); - }); - -} - - -// need to define the controls including the compass and layer controls. top right! -// input variables are for chumps... and reusable functions. this is neither. -function drawMapControls() { - - // viewstate link (little link to where you're looking at the map, normally bottom left) - var viewStateDiv = document.createElement('DIV'); - viewStateDiv.id="link"; - // add it to the map, bottom left. - map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(viewStateDiv); - - // compass rose, in the top right corner - var compassDiv = document.createElement('DIV'); - compassDiv.style.padding = '5px'; - var compassImg = document.createElement('IMG'); - compassImg.src="compass.png"; - compassDiv.appendChild(compassImg); - compassDiv.index = 0; - // add it to the map, top right. - map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv); - - // Spawn button - var homeControlDiv = document.createElement('DIV'); - var homeControl = new HomeControl(homeControlDiv, map); - homeControlDiv.id = "customControl"; - homeControlDiv.index = 1; - map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv); - - - // only need to create the control if there are items in the list. as definned in config.js - if (signGroups.length > 0) { - // signpost display control - - var items = []; - for (idx in signGroups) { - var signGroup = signGroups[idx]; - var iconURL = signGroup.icon; - if (!iconURL) { iconURL = 'signpost.png'; } - items.push({ - "label": signGroup.label, - "checked": signGroup.checked, - "icon": iconURL, - "action": function(n, item, checked) { - jQuery.each(markerCollection[item.label], function(i,elem) { - elem.setVisible(checked); - }); - //alert(item.label); - } - }); - } - createDropDown("Signposts", items); - } - - // if there are any regions data, lets show the option to hide/show them. - if (regionGroups.length > 0) { - // region display control - - var items = []; - for (idx in regionGroups) { - var regionGroup = regionGroups[idx]; - items.push({ - "label": regionGroup.label, - "checked": regionGroup.checked, - "action": function(n, item, checked) { - jQuery.each(regionCollection[item.label], function(i,elem) { - elem.setMap(checked ? map : null); // Thanks to LeastWeasel for this line! - }); - } - }); - } - createDropDown("Regions", items); - } - - if (overlayMapTypes.length > 0) { - // overlay maps control - - var items = []; - for (idx in overlayMapTypes) { - var overlay = overlayMapTypes[idx]; - items.push({"label": overlay.name, "checked": false, "overlay": overlay, - "action": function(i, item, checked) { - if (checked) { - map.overlayMapTypes.push(item.overlay); - } else { - var idx_to_delete = -1; - map.overlayMapTypes.forEach(function(e, j) { - if (e == item.overlay) { idx_to_delete = j; } - }); - if (idx_to_delete >= 0) { - map.overlayMapTypes.removeAt(idx_to_delete); - } - } - }}); - } - createDropDown("Overlays", items); - } -} - -// will be recoded by pi, currently always displays all regions all the time. -// parse the data as definned in the regions.js -function initRegions() { - if (regionsInit) { return; } - if (typeof(regionData) == "undefined") { return; } // skip this if we have no region.js file - regionsInit = true; - - for (i in regionGroups) { - regionCollection[regionGroups[i].label] = []; - } - - for (i in regionData) { - var region = regionData[i]; - - // pull all the points out of the regions file. - var converted = new google.maps.MVCArray(); - var infoPoint = ""; - for (j in region.path) { - var point = region.path[j]; - converted.push(fromWorldToLatLng(point.x, point.y, point.z)); - - } - - for (idx in regionGroups) { - var regionGroup = regionGroups[idx]; - var testfunc = regionGroup.match; - var clickable = regionGroup.clickable - var label = regionGroup.label; - - if(region.label) { - var name = region.label - } else { - var name = 'rawr'; - clickable = false; // if it doesn't have a name, we dont have to show it. - } - - if (region.closed) { - var shape = new google.maps.Polygon({ - name: name, - clickable: clickable, - geodesic: false, - map: null, - strokeColor: region.color, - strokeOpacity: region.opacity, - strokeWeight: 2, - fillColor: region.color, - fillOpacity: region.opacity * 0.25, - zIndex: i, - paths: converted - }); - } else { - var shape = new google.maps.Polyline({ - name: name, - clickable: clickable, - geodesic: false, - map: null, - strokeColor: region.color, - strokeOpacity: region.opacity, - strokeWeight: 2, - zIndex: i, - path: converted - }); - } - regionCollection[label].push(shape); - - if (clickable) { - // add the region infowindow popup - infowindow = new google.maps.InfoWindow(); - google.maps.event.addListener(shape, 'click', function(e,i) { - - var contentString = "Region: "+this.name+"
"; - contentString += "Clicked Location:
" + e.latLng.lat() + "," + e.latLng.lng() + "
"; - - // Replace our Info Window's content and position - infowindow.setContent(contentString); - infowindow.setPosition(e.latLng); - - infowindow.open(map); - - }); - } - } - } -} - -// will initalize all the markers data as found in markers.js -// may need to be reviewed by agrif or someone else... little finicky right now. -function initMarkers() { - if (markersInit) { return; } // oh, we've already done this? nevermind, exit the function. - if (typeof(markerData) == "undefined") { return; } // no markers.js file, so skip this. - markersInit = true; // now that we've started, dont have to do it twice. - - // 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]; - - // a default: - var iconURL = ''; - if (item.type == 'spawn') { - // don't filter spawn, always display - - iconURL = 'http://google-maps-icons.googlecode.com/files/home.png'; - var converted = fromWorldToLatLng(item.x, item.y, item.z); - var marker = new google.maps.Marker({position: converted, - map: map, - title: jQuery.trim(item.msg), - icon: iconURL - }); - continue; - } - - if (item.type == 'querypos') { - // Set on page load if MC x/y/z coords are given in the query string - - iconURL = 'http://google-maps-icons.googlecode.com/files/regroup.png'; - var converted = fromWorldToLatLng(item.x, item.y, item.z); - var marker = new google.maps.Marker({position: converted, - map: map, - title: jQuery.trim(item.msg), - icon: iconURL - }); - - continue; - } - - var matched = false; - for (idx in signGroups) { - var signGroup = signGroups[idx]; - var testfunc = signGroup.match; - var label = signGroup.label; - - if (testfunc(item)) { - matched = true; - - // can add custom types of images for externally definned item types, like 'command' here. - if (item.type == 'sign') { iconURL = 'signpost_icon.png'; } - - //console.log(signGroup.icon); //debug - if (signGroup.icon) { iconURL = signGroup.icon; } - - var converted = fromWorldToLatLng(item.x, item.y, item.z); - var marker = new google.maps.Marker({position: converted, - map: map, - title: jQuery.trim(item.msg), - icon: iconURL, - visible: false - }); - - markerCollection[label].push(marker); - - if (item.type == 'sign') { - prepareSignMarker(marker, item); - } - } - } - - if (!matched) { - // is this signpost doesn't match any of the groups in config.js, add it automatically to the "__others__" group - if (item.type == 'sign') { iconURL = 'signpost_icon.png';} - - var converted = fromWorldToLatLng(item.x, item.y, item.z); - var marker = new google.maps.Marker({position: converted, - map: map, - title: jQuery.trim(item.msg), - icon: iconURL, - visible: false - }); - if (markerCollection["__others__"]) { - markerCollection["__others__"].push(marker); - } else { - markerCollection["__others__"] = [marker]; - } - - if (item.type == 'sign') { - prepareSignMarker(marker, item); - } - } - - - - } -} - -// update the link in the viewstate. -function makeLink() { - var displayZoom = map.getZoom(); - if (displayZoom == config.maxZoom) { - displayZoom = "max"; - } else { - displayZoom -= config.maxZoom; - } - var xyz; - var xyz = fromLatLngToWorld(map.getCenter().lat(), map.getCenter().lng()); - var a=location.href.substring(0,location.href.lastIndexOf(location.search)) - + "?x=" + Math.floor(xyz.x) - + "&y=" + Math.floor(xyz.y) - + "&z=" + Math.floor(xyz.z) - + "&zoom=" + displayZoom; - document.getElementById("link").innerHTML = a; -} - -// load the map up and add all the functions relevant stuff to the map. -function initialize() { - - var query = location.search.substring(1); - - var defaultCenter = fromWorldToLatLng(config.center[0], - config.center[1], - config.center[2]); - var lat = defaultCenter.lat(); - var lng = defaultCenter.lng(); - - var zoom = config.defaultZoom; - var hasquerypos = false; - var queryx = 0; - var queryy = 64; - var queryz = 0; - var mapcenter; - var pairs = query.split("&"); - for (var i=0; i 1) { - mapTyepControlToggle = true - } - var mapOptions = { - zoom: zoom, - center: mapcenter, - navigationControl: true, - scaleControl: false, - mapTypeControl: mapTyepControlToggle, - mapTypeControlOptions: { - mapTypeIds: mapTypeIds - }, - mapTypeId: mapTypeIdDefault, - streetViewControl: false, - }; - map = new google.maps.Map(document.getElementById('mcmap'), mapOptions); - - if(config.debug) { - map.overlayMapTypes.insertAt(0, new CoordMapType(new google.maps.Size(config.tileSize, config.tileSize))); - - google.maps.event.addListener(map, 'click', function(event) { - //console.log("latLng; " + event.latLng.lat() + ", " + event.latLng.lng()); - - var pnt = map.getProjection().fromLatLngToPoint(event.latLng); - //console.log("point: " + pnt); - - var pxx = pnt.x * config.tileSize * Math.pow(2, config.maxZoom); - var pxy = pnt.y * config.tileSize * Math.pow(2, config.maxZoom); - //console.log("pixel: " + pxx + ", " + pxy); - }); - } - - // Now attach the coordinate map type to the map's registry - for (idx in MCMapType) { - map.mapTypes.set('mcmap' + MCMapType[idx].name, MCMapType[idx]); - } - - // initialize the markers and regions - initMarkers(); - initRegions(); - drawMapControls(); - - //makeLink(); - - // Make the link again whenever the map changes - google.maps.event.addListener(map, 'zoom_changed', function() { - makeLink(); - }); - google.maps.event.addListener(map, 'center_changed', function() { - makeLink(); - }); - google.maps.event.addListener(map, 'maptypeid_changed', function() { - var newType = map.getMapTypeId(); - for(i in mapTypeData) { - if( 'mcmap' + mapTypeData[i].label == newType ) { - $('#mcmap').css('background-color', mapTypeData[i].bg_color); - break; - } - } - }); - // We can now set the map to use the 'coordinate' map type - map.setMapTypeId(mapTypeIdDefault); -} - - -// 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); -}; - -// helper to get map LatLng from world coordinates -// takes arguments in X, Y, Z order -// (arguments are *out of order*, because within the function we use -// the axes like the rest of Minecraft Overviewer -- with the Z and Y -// flipped from normal minecraft usage.) -function fromWorldToLatLng(x, z, y) -{ - // the width and height of all the highest-zoom tiles combined, inverted - var perPixel = 1.0 / (config.tileSize * Math.pow(2, config.maxZoom)); - - // This information about where the center column is may change with a different - // drawing implementation -- check it again after any drawing overhauls! - - // point (0, 0, 127) is at (0.5, 0.0) of tile (tiles/2 - 1, tiles/2) - // so the Y coordinate is at 0.5, and the X is at 0.5 - ((tileSize / 2) / (tileSize * 2^maxZoom)) - // or equivalently, 0.5 - (1 / 2^(maxZoom + 1)) - var lng = 0.5 - (1.0 / Math.pow(2, config.maxZoom + 1)); - var lat = 0.5; - - // the following metrics mimic those in ChunkRenderer.chunk_render in "chunk.py" - // or, equivalently, chunk_render in src/iterate.c - - // each block on X axis adds 12px to x and subtracts 6px from y - lng += 12 * x * perPixel; - lat -= 6 * x * perPixel; - - // each block on Y axis adds 12px to x and adds 6px to y - lng += 12 * y * perPixel; - lat += 6 * y * perPixel; - - // each block down along Z adds 12px to y - lat += 12 * (128 - z) * perPixel; - - // add on 12 px to the X coordinate to center our point - lng += 12 * perPixel; - - return new google.maps.LatLng(lat, lng); -} - -// NOTE: X, Y and Z in this function are Minecraft world definitions -// (that is, X is horizontal, Y is altitude and Z is vertical). -function fromLatLngToWorld(lat, lng) -{ - // Initialize world x/y/z object to be returned - var xyz = Array(); - xyz.x = 0; - xyz.y = 64; - xyz.z = 0; - - // the width and height of all the highest-zoom tiles combined, inverted - var perPixel = 1.0 / (config.tileSize * Math.pow(2, config.maxZoom)); - - // Revert base positioning - // See equivalent code in fromWorldToLatLng() - lng -= 0.5 - (1.0 / Math.pow(2, config.maxZoom + 1)); - lat -= 0.5; - - // I'll admit, I plugged this into Wolfram Alpha: - // a = (x * 12 * r) + (z * 12 * r), b = (z * 6 * r) - (x * 6 * r) - // And I don't know the math behind solving for for X and Z given - // A (lng) and B (lat). But Wolfram Alpha did. :) I'd welcome - // suggestions for splitting this up into long form and documenting - // it. -RF - xyz.x = (lng - 2 * lat) / (24 * perPixel) - xyz.z = (lng + 2 * lat) / (24 * perPixel) - - // Adjust for the fact that we we can't figure out what Y is given - // only latitude and longitude, so assume Y=64. - xyz.x += 64 + 1; - xyz.z -= 64 + 2; - - return xyz; -} - -function getTileUrlGenerator(path, path_base, path_ext) { - return function(tile, zoom) { - var url = path; - var url_base = ( path_base ? path_base : '' ); - 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 + '.' + path_ext; - if(config.cacheMinutes > 0) { - var d = new Date(); - url += '?c=' + Math.floor(d.getTime() / (1000 * 60 * config.cacheMinutes)); - } - return(url_base + url); - } -} - -var MCMapOptions = new Array; -var MCMapType = new Array; -var mapTypeIdDefault = null; -var mapTypeIds = []; -var overlayMapTypes = []; -for (idx in mapTypeData) { - var view = mapTypeData[idx]; - var imgformat = view.imgformat ? view.imgformat : 'png'; - - MCMapOptions[view.label] = { - getTileUrl: getTileUrlGenerator(view.path, view.base, imgformat), - tileSize: new google.maps.Size(config.tileSize, config.tileSize), - maxZoom: config.maxZoom, - minZoom: config.minZoom, - isPng: !(imgformat.match(/^png$/i) == null) - }; - - MCMapType[view.label] = new google.maps.ImageMapType(MCMapOptions[view.label]); - MCMapType[view.label].name = view.label; - MCMapType[view.label].alt = "Minecraft " + view.label + " Map"; - MCMapType[view.label].projection = new MCMapProjection(); - - if (view.overlay) { - overlayMapTypes.push(MCMapType[view.label]); - } else { - if (mapTypeIdDefault == null) { - mapTypeIdDefault = 'mcmap' + view.label; - } - mapTypeIds.push('mcmap' + view.label); - } -} - -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; -}; diff --git a/web_assets/overviewer.js b/web_assets/overviewer.js index 4b0d986..a298bf0 100644 --- a/web_assets/overviewer.js +++ b/web_assets/overviewer.js @@ -1,16 +1,56 @@ var overviewer = { + /** + * This holds the map, probably the most important var in this file + */ 'map': null, + /** + * These are collections of data used in various places + */ 'collections': { + /** + * A list of lists of raw marker data objects, this will allow for an + * arbitrary number of marker data sources. This replaces the old + * markerData var from markers.js. Now you can add markers by including + * a file with: + * overviewer.collections.markerDatas.push([]); + */ 'markerDatas': [], + /** + * The actual Marker objects are stored here. + */ 'markers': {}, + /** + * Same as markerDatas, list of lists of raw region objects. + */ 'regionDatas': [], + /** + * The actual Region objects. + */ 'regions': {}, + /** + * Overlay mapTypes (like Spawn) will go in here. + */ 'overlays': [], + /** + * MapTypes that aren't overlays will end up in here. + */ 'mapTypes': {}, + /** + * The mapType names are in here. + */ 'mapTypeIds': [], + /** + * This is the current infoWindow object, we keep track of it so that + * there is only one open at a time. + */ 'infoWindow': null }, 'util': { + /** + * General initialization function, called when the page is loaded. + * Probably shouldn't need changing unless some very different kind of new + * feature gets added. + */ 'initialize': function() { overviewer.util.initializeClassPrototypes(); overviewer.util.initializeMapTypes(); @@ -19,6 +59,11 @@ var overviewer = { overviewer.util.initializeRegions(); overviewer.util.createMapControls(); }, + /** + * This adds some methods to these classes because Javascript is stupid + * and this seems like the best way to avoid re-creating the same methods + * on each object at object creation time. + */ 'initializeClassPrototypes': function() { overviewer.classes.MapProjection.prototype.fromLatLngToPoint = function(latLng) { var x = latLng.lng() * overviewerConfig.CONST.tileSize; @@ -34,9 +79,14 @@ var overviewer = { overviewer.classes.CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) { var div = ownerDocument.createElement('DIV'); - div.innerHTML = '(' + coord.x + ', ' + coord.y + ', ' + zoom + ')'; - div.innerHTML += '
'; + div.innerHTML = '(' + coord.x + ', ' + coord.y + ', ' + zoom + + ')' + '
'; + //TODO: figure out how to get the current mapType, I think this + //will add the maptile url to the grid thing once it works + //div.innerHTML += overviewer.collections.mapTypes[0].getTileUrl(coord, zoom); + + //this should probably just have a css class div.style.width = this.tileSize.width + 'px'; div.style.height = this.tileSize.height + 'px'; div.style.fontSize = '10px'; @@ -47,8 +97,8 @@ var overviewer = { }; }, /** - * I think this was old code that was replaced by stuff that is now - * in initializeMap() + * Setup the varous mapTypes before we actually create the map. This used + * to be a bunch of crap down at the bottom of functions.js */ 'initializeMapTypes': function() { var mapOptions = {}; @@ -81,6 +131,12 @@ var overviewer = { } } }, + /** + * This is where the magic happens. We setup the map with all it's + * options. The query string is also parsed here so we can know if + * we should be looking at a particular point on the map or just use + * the default view. + */ 'initializeMap': function() { var defaultCenter = overviewer.util.fromWorldToLatLng( overviewerConfig.map.center[0], overviewerConfig.map.center[1], @@ -103,7 +159,7 @@ var overviewer = { zoom = overviewerConfig.map.minZoom; } else { zoom = parseInt(queryParams.zoom); - if (zoom < 0 && zoom + overvierConfig.map.maxZoom >= 0) { + if (zoom < 0 && zoom + overviewerConfig.map.maxZoom >= 0) { //if zoom is negative, try to treat as "zoom out from max zoom" zoom += overviewerConfig.map.maxZoom; } else { @@ -187,6 +243,12 @@ var overviewer = { // We can now set the map to use the 'coordinate' map type overviewer.map.setMapTypeId(overviewer.util.getDefaultMapTypeId()); }, + /** + * Read through overviewer.collections.markerDatas and create Marker + * objects and stick them in overviewer.collections.markers . This + * should probably be done differently at some point so that we can + * support markers that change position more easily. + */ 'initializeMarkers': function() { //first, give all collections an empty array to work with for (i in overviewerConfig.objectGroups.signs) { @@ -283,6 +345,9 @@ var overviewer = { } } }, + /** + * Same as initializeMarkers() for the most part. + */ 'initializeRegions': function() { for (i in overviewerConfig.objectGroups.regions) { overviewer.collections.regions[overviewerConfig.objectGroups.regions[i].label] = []; @@ -347,11 +412,22 @@ var overviewer = { } } }, + /** + * Gee, I wonder what this does. + * + * @param string msg + */ 'debug': function(msg) { if (overviewerConfig.map.debug) { console.log(msg); } }, + /** + * Simple helper function to split the query string into key/value + * pairs. Doesn't do any type conversion but both are lowercase'd. + * + * @return Object + */ 'parseQueryString': function() { var results = {}; var queryString = location.search.substring(1); @@ -365,6 +441,10 @@ var overviewer = { } return results; }, + /** + * Set the link (at the bottom of the screen) to the current view. + * TODO: make this preserve the mapTypeId as well + */ 'setViewUrl': function() { var displayZoom = overviewer.map.getZoom(); if (displayZoom == overviewerConfig.map.maxZoom) { @@ -387,6 +467,18 @@ var overviewer = { 'getDefaultMapTypeId': function() { return overviewer.collections.mapTypeIds[0]; }, + /** + * helper to get map LatLng from world coordinates takes arguments in + * X, Y, Z order (arguments are *out of order*, because within the + * function we use the axes like the rest of Minecraft Overviewer -- + * with the Z and Y flipped from normal minecraft usage.) + * + * @param int x + * @param int z + * @param int y + * + * @return google.maps.LatLng + */ 'fromWorldToLatLng': function(x, z, y) { // the width and height of all the highest-zoom tiles combined, // inverted @@ -423,6 +515,16 @@ var overviewer = { return new google.maps.LatLng(lat, lng); }, + /** + * The opposite of fromWorldToLatLng + * NOTE: X, Y and Z in this function are Minecraft world definitions + * (that is, X is horizontal, Y is altitude and Z is vertical). + * + * @param float lat + * @param float lng + * + * @return Array + */ 'fromLatLngToWorld': function(lat, lng) { // Initialize world x/y/z object to be returned var point = Array(); @@ -456,6 +558,10 @@ var overviewer = { return point; }, + /** + * Create and draw the various map controls and other related things + * like the compass, current view link, etc. + */ 'createMapControls': function() { // viewstate link (little link to where you're looking at the map, // normally bottom left) @@ -559,6 +665,12 @@ var overviewer = { overviewer.util.createDropDown('Overlays', items); } }, + /** + * Reusable method for creating drop-down menus + * + * @param string title + * @param array items + */ 'createDropDown': function(title, items) { var control = document.createElement('DIV'); // let's let a style sheet do most of the styling here @@ -619,6 +731,13 @@ var overviewer = { itemDiv.appendChild(textNode); } }, + /** + * Create the pop-up infobox for when you click on a region, this can't + * be done in-line because of stupid Javascript scoping problems with + * closures or something. + * + * @param google.maps.Polygon|google.maps.Polyline shape + */ 'createRegionInfoWindow': function(shape) { var infowindow = new google.maps.InfoWindow(); google.maps.event.addListener(shape, 'click', function(event, i) { @@ -635,6 +754,11 @@ var overviewer = { overviewer.collections.infoWindow = infowindow; }); }, + /** + * Same as createRegionInfoWindow() + * + * @param google.maps.Marker marker + */ 'createMarkerInfoWindow': function(marker) { var windowContent = '

' + marker.title.replace(/\n/g,'
') + '

'; @@ -650,7 +774,16 @@ var overviewer = { }); } }, + /** + * The various classes needed in this file. + */ 'classes': { + /** + * This is the button that centers the map on spawn. Not sure why we + * need a separate class for this and not some of the other controls. + * + * @param documentElement controlDiv + */ 'HomeControl': function(controlDiv) { controlDiv.style.padding = '5px'; // Set CSS for the control border @@ -674,15 +807,43 @@ var overviewer = { overviewerConfig.map.center[2])); }); }, + /** + * 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) + */ 'MapProjection' : function() { this.inverseTileSize = 1.0 / overviewerConfig.CONST.tileSize; }, - //Looks like this is only used for debugging + /** + * This is a mapType used only for debugging, to draw a grid on the screen + * showing the tile co-ordinates and tile path. Currently the tile path + * part does not work. + * + * @param google.maps.Size tileSize + */ 'CoordMapType': function(tileSize) { this.tileSize = tileSize; } }, + /** + * Stuff that we give to the google maps code instead of using ourselves + * goes in here. + * + * Also, why do I keep writing these comments as if I'm multiple people? I + * should probably stop that. + */ 'gmap': { + /** + * Generate a function to get the path to a tile at a particular location + * and zoom level. + * + * @param string path + * @param string pathBase + * @param string pathExt + */ 'getTileUrlGenerator': function(path, pathBase, pathExt) { return function(tile, zoom) { var url = path; diff --git a/web_assets/overviewerConfig.js b/web_assets/overviewerConfig.js deleted file mode 100644 index 841841e..0000000 --- a/web_assets/overviewerConfig.js +++ /dev/null @@ -1,46 +0,0 @@ -var overviewerConfig = { - //These will probably never change - 'CONST': { - 'tileSize': 384, - 'image': { - 'defaultMarker': 'signpost.png', - 'signMarker': 'signpost_icon.png', - 'compass': 'compass.png', - 'spawnMarker': 'http://google-maps-icons.googlecode.com/files/home.png', - 'queryMarker': 'http://google-maps-icons.googlecode.com/files/regroup.png' - }, - 'mapDivId': 'mcmap', - 'regionStrokeWeight': 2 - }, - 'map': { - 'controls': { - 'navigation': true - }, - 'defaultZoom': 0, - 'minZoom': {minzoom}, - 'maxZoom': {maxzoom}, - 'center': {spawn_coords}, - 'cacheMinutes': 0, - 'debug': false, - }, - 'objectGroups': { - 'signs': [ - { - 'label': 'All', - 'match': function(sign) { - return true; - } - } - ], - 'regions': [ - { - 'label': 'All', - 'clickable':true, - 'match': function(region) { - return true; - } - } - ] - }, - 'mapTypes': {maptypedata} -}; From 219fa3e7a975a1887be5327e1c417f479f794158 Mon Sep 17 00:00:00 2001 From: Alex Headley Date: Sat, 23 Apr 2011 23:36:29 -0400 Subject: [PATCH 210/213] fixed remaining known bugs made zoom/pan controls independently toggle-able re-added markers.js and regions.js to index.html fixed adding marker from query string fixed initial bg_color setting --- overviewerConfig.js | 11 +++++---- web_assets/index.html | 2 ++ web_assets/overviewer.js | 49 +++++++++++++++++++++++++--------------- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/overviewerConfig.js b/overviewerConfig.js index 1ceb336..8b34a0f 100644 --- a/overviewerConfig.js +++ b/overviewerConfig.js @@ -30,10 +30,13 @@ var overviewerConfig = { */ 'controls': { /** - * Navigation controls are the pan and zoom typically on the upper - * left. + * Pan control is the hand with the arrows around it in the upper left. */ - 'navigation': true + 'pan': true, + /** + * Zoom control is the zoom slider bar in the upper left. + */ + 'zoom': true }, /** * The zoom level when the page is loaded without a specific zoom setting @@ -135,4 +138,4 @@ var overviewerConfig = { * ] */ 'mapTypes': {maptypedata} -}; \ No newline at end of file +}; diff --git a/web_assets/index.html b/web_assets/index.html index 14c6666..b3cb596 100644 --- a/web_assets/index.html +++ b/web_assets/index.html @@ -7,6 +7,8 @@ + + diff --git a/web_assets/overviewer.js b/web_assets/overviewer.js index a298bf0..37b465b 100644 --- a/web_assets/overviewer.js +++ b/web_assets/overviewer.js @@ -172,27 +172,30 @@ var overviewer = { mapcenter = overviewer.util.fromWorldToLatLng(queryParams.x, queryParams.y, queryParams.z); // Add a market indicating the user-supplied position - overviewer.collections.markers.push({ + overviewer.collections.markerDatas.push([{ 'msg': 'Coordinates ' + queryParams.x + ', ' + queryParams.y + ', ' + queryParams.z, 'y': parseFloat(queryParams.y), 'x': parseFloat(queryParams.x), 'z': parseFloat(queryParams.z), - 'type': 'querypos'}); + 'type': 'querypos'}]); } else { mapcenter = new google.maps.LatLng(lat, lng); } var mapOptions = { - zoom: zoom, - center: mapcenter, - navigationControl: overviewerConfig.map.controls.navigation, - scaleControl: false, - mapTypeControl: overviewer.collections.mapTypeIds.length > 1, + zoom: zoom, + center: mapcenter, + panControl: overviewerConfig.map.controls.pan, + scaleControl: false, + mapTypeControl: overviewer.collections.mapTypeIds.length > 1, mapTypeControlOptions: { mapTypeIds: overviewer.collections.mapTypeIds }, - mapTypeId: overviewer.util.getDefaultMapTypeId(), - streetViewControl: false + mapTypeId: overviewer.util.getDefaultMapTypeId(), + streetViewControl: false, + zoomControl: overviewerConfig.map.controls.zoom, + backgroundColor: overviewer.util.getMapTypeBackgroundColor( + overviewer.util.getDefaultMapTypeId()) }; overviewer.map = new google.maps.Map(document.getElementById( overviewerConfig.CONST.mapDivId), mapOptions); @@ -230,15 +233,9 @@ var overviewer = { overviewer.util.setViewUrl(); }); google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function() { - var newType = overviewer.map.getMapTypeId(); - for(i in overviewerConfig.mapTypes) { - if( overviewerConfig.CONST.mapDivId + - overviewerConfig.mapTypes[i].label == newType ) { - $('#'+overviewerConfig.CONST.mapDivId).css( - 'background-color', overviewerConfig.mapTypes[i].bg_color); - break; - } - } + $('#'+overviewerConfig.CONST.mapDivId).css( + 'background-color', overviewer.util.getMapTypeBackgroundColor( + overviewer.map.getMapTypeId())); }); // We can now set the map to use the 'coordinate' map type overviewer.map.setMapTypeId(overviewer.util.getDefaultMapTypeId()); @@ -412,6 +409,22 @@ var overviewer = { } } }, + /** + * Change the map's div's background color according to the mapType's bg_color setting + * + * @param string mapTypeId + * @return string + */ + 'getMapTypeBackgroundColor': function(mapTypeId) { + for(i in overviewerConfig.mapTypes) { + if( overviewerConfig.CONST.mapDivId + + overviewerConfig.mapTypes[i].label == mapTypeId ) { + overviewer.util.debug('Found background color for: ' + + overviewerConfig.mapTypes[i].bg_color); + return overviewerConfig.mapTypes[i].bg_color; + } + } + }, /** * Gee, I wonder what this does. * From 4d76e106e0c39d875594df95641ac473b73f4daa Mon Sep 17 00:00:00 2001 From: Alex Headley Date: Sun, 24 Apr 2011 00:37:37 -0400 Subject: [PATCH 211/213] added some more toggle-able controls --- overviewerConfig.js | 18 +++++++++++++++++- web_assets/overviewer.js | 27 +++++++++++++++++---------- 2 files changed, 34 insertions(+), 11 deletions(-) diff --git a/overviewerConfig.js b/overviewerConfig.js index 8b34a0f..fd12c49 100644 --- a/overviewerConfig.js +++ b/overviewerConfig.js @@ -36,7 +36,23 @@ var overviewerConfig = { /** * Zoom control is the zoom slider bar in the upper left. */ - 'zoom': true + 'zoom': true, + /** + * Spawn control is the "Spawn" button that centers the map on spawn. + */ + 'spawn': true, + /** + * The compass in the upper right. + */ + 'compass': true, + /** + * The mapType control is the slider for selecting different map types. + */ + 'mapType': true, + /** + * The small box at the bottom that displays the link to the current map view. + */ + 'link': true }, /** * The zoom level when the page is loaded without a specific zoom setting diff --git a/web_assets/overviewer.js b/web_assets/overviewer.js index 37b465b..f927625 100644 --- a/web_assets/overviewer.js +++ b/web_assets/overviewer.js @@ -187,7 +187,8 @@ var overviewer = { center: mapcenter, panControl: overviewerConfig.map.controls.pan, scaleControl: false, - mapTypeControl: overviewer.collections.mapTypeIds.length > 1, + mapTypeControl: overviewerConfig.map.controls.mapType && + overviewer.collections.mapTypeIds.length > 1, mapTypeControlOptions: { mapTypeIds: overviewer.collections.mapTypeIds }, @@ -226,12 +227,6 @@ var overviewer = { } // Make the link again whenever the map changes - google.maps.event.addListener(overviewer.map, 'zoom_changed', function() { - overviewer.util.setViewUrl(); - }); - google.maps.event.addListener(overviewer.map, 'center_changed', function() { - overviewer.util.setViewUrl(); - }); google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function() { $('#'+overviewerConfig.CONST.mapDivId).css( 'background-color', overviewer.util.getMapTypeBackgroundColor( @@ -581,7 +576,15 @@ var overviewer = { var viewStateDiv = document.createElement('DIV'); viewStateDiv.id='link'; // add it to the map, bottom left. - overviewer.map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(viewStateDiv); + if (overviewerConfig.map.controls.link) { + google.maps.event.addListener(overviewer.map, 'zoom_changed', function() { + overviewer.util.setViewUrl(); + }); + google.maps.event.addListener(overviewer.map, 'center_changed', function() { + overviewer.util.setViewUrl(); + }); + overviewer.map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(viewStateDiv); + } // compass rose, in the top right corner var compassDiv = document.createElement('DIV'); @@ -591,14 +594,18 @@ var overviewer = { compassDiv.appendChild(compassImg); compassDiv.index = 0; // add it to the map, top right. - overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv); + if (overviewerConfig.map.controls.compass) { + overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv); + } // Spawn button var homeControlDiv = document.createElement('DIV'); var homeControl = new overviewer.classes.HomeControl(homeControlDiv); homeControlDiv.id = 'customControl'; homeControlDiv.index = 1; - overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv); + if (overviewerConfig.map.controls.spawn) { + overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv); + } // only need to create the control if there are items in the list. // as defined in config.js From b44df77405b4883bc0a9b5be9ca33b60b7121564 Mon Sep 17 00:00:00 2001 From: aheadley Date: Mon, 25 Apr 2011 17:02:04 -0400 Subject: [PATCH 212/213] renamed style.css to be consistent with overviewer.js --- web_assets/index.html | 2 +- web_assets/{style.css => overviewer.css} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename web_assets/{style.css => overviewer.css} (100%) diff --git a/web_assets/index.html b/web_assets/index.html index b3cb596..3798f17 100644 --- a/web_assets/index.html +++ b/web_assets/index.html @@ -2,7 +2,7 @@ - + diff --git a/web_assets/style.css b/web_assets/overviewer.css similarity index 100% rename from web_assets/style.css rename to web_assets/overviewer.css From 00a3285555d7f759cb512fa0f23a31d268aaa76c Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Thu, 28 Apr 2011 11:10:06 -0400 Subject: [PATCH 213/213] Fix for py2exe --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 5eb8c23..497b5ac 100644 --- a/setup.py +++ b/setup.py @@ -28,7 +28,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']), - ('', ['config.js', 'COPYING.txt', 'README.rst']), + ('', ['overviewerConfig.js', 'COPYING.txt', 'README.rst']), ('web_assets', glob.glob('web_assets/*'))] setup_kwargs['zipfile'] = None if platform.system() == 'Windows' and '64bit' in platform.architecture():