Merged in dtt-c-render
Conflicts: src/overviewer.h
This commit is contained in:
@@ -246,6 +246,15 @@ Options
|
|||||||
--night
|
--night
|
||||||
This option enables --lighting, and renders the world at 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.
|
||||||
|
|
||||||
Viewing the Results
|
Viewing the Results
|
||||||
-------------------
|
-------------------
|
||||||
Within the output directory you will find two things: an index.html file, and a
|
Within the output directory you will find two things: an index.html file, and a
|
||||||
|
|||||||
13
chunk.py
13
chunk.py
@@ -46,15 +46,20 @@ image
|
|||||||
# alpha_over extension, BUT this extension may fall back to PIL's
|
# alpha_over extension, BUT this extension may fall back to PIL's
|
||||||
# paste(), which DOES need the workaround.)
|
# paste(), which DOES need the workaround.)
|
||||||
|
|
||||||
def get_lvldata(world,filename, x, y):
|
def get_lvldata(world, filename, x, y, retries=2):
|
||||||
"""Takes a filename and chunkcoords and returns the Level struct, which contains all the
|
"""Takes a filename and chunkcoords and returns the Level struct, which contains all the
|
||||||
level info"""
|
level info"""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
d = world.load_from_region(filename, x, y)
|
d = world.load_from_region(filename, x, y)
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
logging.warning("Error opening chunk (%i, %i) in %s. It may be corrupt. %s", x, y, filename, e)
|
if retries > 0:
|
||||||
raise ChunkCorrupt(str(e))
|
# wait a little bit, and try again (up to `retries` times)
|
||||||
|
time.sleep(1)
|
||||||
|
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)
|
if not d: raise NoSuchChunk(x,y)
|
||||||
return d[1]['Level']
|
return d[1]['Level']
|
||||||
@@ -569,7 +574,7 @@ class ChunkRenderer(object):
|
|||||||
|
|
||||||
# check to see if there are any signs in the persistentData list that are from this chunk.
|
# 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
|
# if so, remove them from the persistentData list (since they're have been added to the world.POI
|
||||||
# list above
|
# list above.
|
||||||
if self.queue:
|
if self.queue:
|
||||||
self.queue.put(['removePOI', (self.chunkX, self.chunkY)])
|
self.queue.put(['removePOI', (self.chunkX, self.chunkY)])
|
||||||
|
|
||||||
|
|||||||
17
composite.py
17
composite.py
@@ -23,12 +23,7 @@ Overviewer. It defaults to the PIL paste function when the custom
|
|||||||
alpha-over extension cannot be found.
|
alpha-over extension cannot be found.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
extension_alpha_over = None
|
from c_overviewer import alpha_over as extension_alpha_over
|
||||||
try:
|
|
||||||
from c_overviewer import alpha_over as _extension_alpha_over
|
|
||||||
extension_alpha_over = _extension_alpha_over
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def alpha_over(dest, src, pos_or_rect=(0, 0), mask=None):
|
def alpha_over(dest, src, pos_or_rect=(0, 0), mask=None):
|
||||||
"""Composite src over dest, using mask as the alpha channel (if
|
"""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
|
mask = src
|
||||||
|
|
||||||
global extension_alpha_over
|
global extension_alpha_over
|
||||||
if extension_alpha_over is not None:
|
return extension_alpha_over(dest, src, pos_or_rect, mask)
|
||||||
# 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)
|
|
||||||
|
|
||||||
|
|||||||
151
configParser.py
Normal file
151
configParser.py
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
from optparse import OptionParser
|
||||||
|
import sys
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
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):
|
||||||
|
|
||||||
|
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):
|
||||||
|
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
|
||||||
|
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:
|
||||||
|
if os.path.exists(self.configFile):
|
||||||
|
execfile(self.configFile, g, l)
|
||||||
|
except NameError, ex:
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
print "\nError 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
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
#print l.keys()
|
||||||
|
|
||||||
|
configResults = OptionsResults()
|
||||||
|
# third, 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])
|
||||||
|
elif a['type'] == "function":
|
||||||
|
if not callable(configResults.__dict__[n]):
|
||||||
|
raise ValueError("Not callable")
|
||||||
|
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
|
||||||
|
|
||||||
51
contrib/benchmark.py
Normal file
51
contrib/benchmark.py
Normal file
@@ -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)
|
||||||
105
contrib/validateRegionFile.py
Normal file
105
contrib/validateRegionFile.py
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
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 check_region(region_filename):
|
||||||
|
chunk_errors = []
|
||||||
|
if not os.path.exists(region_filename):
|
||||||
|
raise Exception('Region file not found: %s' % region_filename)
|
||||||
|
try:
|
||||||
|
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] <path/to/regions|path/to/regions/*.mcr|regionfile1.mcr regionfile2.mcr ...>',
|
||||||
|
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)
|
||||||
|
|
||||||
42
gmap.py
42
gmap.py
@@ -22,16 +22,25 @@ if not (sys.version_info[0] == 2 and sys.version_info[1] >= 6):
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import os.path
|
import os.path
|
||||||
from optparse import OptionParser
|
from configParser import ConfigOptionParser
|
||||||
import re
|
import re
|
||||||
|
import subprocess
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
import optimizeimages
|
|
||||||
import composite
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s")
|
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 world
|
||||||
import quadtree
|
import quadtree
|
||||||
|
|
||||||
@@ -44,19 +53,21 @@ def main():
|
|||||||
cpus = multiprocessing.cpu_count()
|
cpus = multiprocessing.cpu_count()
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
cpus = 1
|
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("-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")
|
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("--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("--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("--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("--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("--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("--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("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg.")
|
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("--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("-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("-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("--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. 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()
|
options, args = parser.parse_args()
|
||||||
|
|
||||||
@@ -101,6 +112,11 @@ def main():
|
|||||||
parser.error("Where do you want to save the tiles?")
|
parser.error("Where do you want to save the tiles?")
|
||||||
|
|
||||||
destdir = args[1]
|
destdir = args[1]
|
||||||
|
if options.display_config:
|
||||||
|
# just display the config file and exit
|
||||||
|
parser.display_config()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
if options.delete:
|
if options.delete:
|
||||||
return delete_all(worlddir, destdir)
|
return delete_all(worlddir, destdir)
|
||||||
@@ -123,7 +139,7 @@ def main():
|
|||||||
optimizeimages.check_programs(optimizeimg)
|
optimizeimages.check_programs(optimizeimg)
|
||||||
else:
|
else:
|
||||||
optimizeimg = None
|
optimizeimg = None
|
||||||
|
|
||||||
logging.getLogger().setLevel(
|
logging.getLogger().setLevel(
|
||||||
logging.getLogger().level + 10*options.quiet)
|
logging.getLogger().level + 10*options.quiet)
|
||||||
logging.getLogger().setLevel(
|
logging.getLogger().setLevel(
|
||||||
@@ -145,7 +161,7 @@ def main():
|
|||||||
|
|
||||||
# Now generate the tiles
|
# Now generate the tiles
|
||||||
# TODO chunklist
|
# TODO chunklist
|
||||||
q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg, lighting=options.lighting, night=options.night, spawn=options.spawn)
|
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.write_html(options.skipjs)
|
||||||
q.go(options.procs)
|
q.go(options.procs)
|
||||||
|
|
||||||
|
|||||||
140
quadtree.py
140
quadtree.py
@@ -82,8 +82,15 @@ def catch_keyboardinterrupt(func):
|
|||||||
raise
|
raise
|
||||||
return newfunc
|
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):
|
class QuadtreeGen(object):
|
||||||
def __init__(self, worldobj, destdir, depth=None, tiledir="tiles", imgformat=None, optimizeimg=None, lighting=False, night=False, spawn=False):
|
def __init__(self, worldobj, destdir, depth=None, tiledir="tiles", imgformat=None, optimizeimg=None, rendermode="normal", web_assets_hook=None):
|
||||||
"""Generates a quadtree from the world given into the
|
"""Generates a quadtree from the world given into the
|
||||||
given dest directory
|
given dest directory
|
||||||
|
|
||||||
@@ -96,10 +103,11 @@ class QuadtreeGen(object):
|
|||||||
assert(imgformat)
|
assert(imgformat)
|
||||||
self.imgformat = imgformat
|
self.imgformat = imgformat
|
||||||
self.optimizeimg = optimizeimg
|
self.optimizeimg = optimizeimg
|
||||||
|
self.web_assets_hook = web_assets_hook
|
||||||
|
|
||||||
self.lighting = lighting
|
self.lighting = rendermode in ("lighting", "night", "spawn")
|
||||||
self.night = night
|
self.night = rendermode in ("night", "spawn")
|
||||||
self.spawn = spawn
|
self.spawn = rendermode in ("spawn",)
|
||||||
|
|
||||||
# Make the destination dir
|
# Make the destination dir
|
||||||
if not os.path.exists(destdir):
|
if not os.path.exists(destdir):
|
||||||
@@ -189,6 +197,8 @@ class QuadtreeGen(object):
|
|||||||
output.write(index)
|
output.write(index)
|
||||||
|
|
||||||
if skipjs:
|
if skipjs:
|
||||||
|
if self.web_assets_hook:
|
||||||
|
self.web_assets_hook(self)
|
||||||
return
|
return
|
||||||
|
|
||||||
# since we will only discover PointsOfInterest in chunks that need to be
|
# since we will only discover PointsOfInterest in chunks that need to be
|
||||||
@@ -217,6 +227,9 @@ class QuadtreeGen(object):
|
|||||||
output.write(' // ]},\n')
|
output.write(' // ]},\n')
|
||||||
output.write('];')
|
output.write('];')
|
||||||
|
|
||||||
|
if self.web_assets_hook:
|
||||||
|
self.web_assets_hook(self)
|
||||||
|
|
||||||
def _get_cur_depth(self):
|
def _get_cur_depth(self):
|
||||||
"""How deep is the quadtree currently in the destdir? This glances in
|
"""How deep is the quadtree currently in the destdir? This glances in
|
||||||
config.js to see what maxZoom is set to.
|
config.js to see what maxZoom is set to.
|
||||||
@@ -292,10 +305,13 @@ class QuadtreeGen(object):
|
|||||||
shutil.rmtree(getpath("3"))
|
shutil.rmtree(getpath("3"))
|
||||||
os.rename(getpath("new3"), 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
|
"""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.
|
requested, a new task is added to the pool and a result returned.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
batch = []
|
||||||
|
tiles = 0
|
||||||
for path in iterate_base4(self.p):
|
for path in iterate_base4(self.p):
|
||||||
# Get the range for this tile
|
# Get the range for this tile
|
||||||
colstart, rowstart = self._get_range_by_path(path)
|
colstart, rowstart = self._get_range_by_path(path)
|
||||||
@@ -305,30 +321,41 @@ class QuadtreeGen(object):
|
|||||||
# This image is rendered at:
|
# This image is rendered at:
|
||||||
dest = os.path.join(self.destdir, self.tiledir, *(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)
|
#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))
|
||||||
|
tiles += 1
|
||||||
|
if tiles >= batch_size:
|
||||||
|
tiles = 0
|
||||||
|
yield pool.apply_async(func=render_worldtile_batch, args= [batch])
|
||||||
|
batch = []
|
||||||
|
|
||||||
# And uses these chunks
|
if tiles > 0:
|
||||||
tilechunks = self._get_chunks_in_range(colstart, colend, rowstart,
|
yield pool.apply_async(func=render_worldtile_batch, args= (batch,))
|
||||||
rowend)
|
|
||||||
#logging.debug(" tilechunks: %r", tilechunks)
|
|
||||||
|
|
||||||
# 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= (self,
|
|
||||||
tilechunks, colstart, colend, rowstart, rowend, dest))
|
|
||||||
|
|
||||||
def _apply_render_inntertile(self, pool, zoom):
|
def _apply_render_inntertile(self, pool, zoom,batch_size):
|
||||||
"""Same as _apply_render_worltiles but for the inntertile routine.
|
"""Same as _apply_render_worltiles but for the inntertile routine.
|
||||||
Returns an iterator that yields result objects from tasks that have
|
Returns an iterator that yields result objects from tasks that have
|
||||||
been applied to the pool.
|
been applied to the pool.
|
||||||
"""
|
"""
|
||||||
|
batch = []
|
||||||
|
tiles = 0
|
||||||
for path in iterate_base4(zoom):
|
for path in iterate_base4(zoom):
|
||||||
# This image is rendered at:
|
# This image is rendered at:
|
||||||
dest = os.path.join(self.destdir, self.tiledir, *(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])
|
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):
|
def go(self, procs):
|
||||||
"""Renders all tiles"""
|
"""Renders all tiles"""
|
||||||
|
|
||||||
@@ -344,12 +371,17 @@ class QuadtreeGen(object):
|
|||||||
for _ in xrange(curdepth - self.p):
|
for _ in xrange(curdepth - self.p):
|
||||||
self._decrease_depth()
|
self._decrease_depth()
|
||||||
|
|
||||||
|
logging.debug("Parent process {0}".format(os.getpid()))
|
||||||
# Create a pool
|
# Create a pool
|
||||||
if procs == 1:
|
if procs == 1:
|
||||||
pool = FakePool()
|
pool = FakePool()
|
||||||
|
global child_quadtree
|
||||||
|
child_quadtree = self
|
||||||
else:
|
else:
|
||||||
pool = multiprocessing.Pool(processes=procs)
|
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
|
# Render the highest level of tiles from the chunks
|
||||||
results = collections.deque()
|
results = collections.deque()
|
||||||
complete = 0
|
complete = 0
|
||||||
@@ -359,20 +391,20 @@ class QuadtreeGen(object):
|
|||||||
logging.info("There are {0} total levels to render".format(self.p))
|
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("Don't worry, each level has only 25% as many tiles as the last.")
|
||||||
logging.info("The others will go faster")
|
logging.info("The others will go faster")
|
||||||
for result in self._apply_render_worldtiles(pool):
|
count = 0
|
||||||
|
batch_size = 10
|
||||||
|
for result in self._apply_render_worldtiles(pool,batch_size):
|
||||||
results.append(result)
|
results.append(result)
|
||||||
if len(results) > 10000:
|
if len(results) > (10000/batch_size):
|
||||||
# Empty the queue before adding any more, so that memory
|
# Empty the queue before adding any more, so that memory
|
||||||
# required has an upper bound
|
# required has an upper bound
|
||||||
while len(results) > 500:
|
while len(results) > (500/batch_size):
|
||||||
results.popleft().get()
|
complete += results.popleft().get()
|
||||||
complete += 1
|
|
||||||
self.print_statusline(complete, total, 1)
|
self.print_statusline(complete, total, 1)
|
||||||
|
|
||||||
# Wait for the rest of the results
|
# Wait for the rest of the results
|
||||||
while len(results) > 0:
|
while len(results) > 0:
|
||||||
results.popleft().get()
|
complete += results.popleft().get()
|
||||||
complete += 1
|
|
||||||
self.print_statusline(complete, total, 1)
|
self.print_statusline(complete, total, 1)
|
||||||
|
|
||||||
self.print_statusline(complete, total, 1, True)
|
self.print_statusline(complete, total, 1, True)
|
||||||
@@ -384,17 +416,15 @@ class QuadtreeGen(object):
|
|||||||
complete = 0
|
complete = 0
|
||||||
total = 4**zoom
|
total = 4**zoom
|
||||||
logging.info("Starting level {0}".format(level))
|
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)
|
results.append(result)
|
||||||
if len(results) > 10000:
|
if len(results) > (10000/batch_size):
|
||||||
while len(results) > 500:
|
while len(results) > (500/batch_size):
|
||||||
results.popleft().get()
|
complete += results.popleft().get()
|
||||||
complete += 1
|
|
||||||
self.print_statusline(complete, total, level)
|
self.print_statusline(complete, total, level)
|
||||||
# Empty the queue
|
# Empty the queue
|
||||||
while len(results) > 0:
|
while len(results) > 0:
|
||||||
results.popleft().get()
|
complete += results.popleft().get()
|
||||||
complete += 1
|
|
||||||
self.print_statusline(complete, total, level)
|
self.print_statusline(complete, total, level)
|
||||||
|
|
||||||
self.print_statusline(complete, total, level, True)
|
self.print_statusline(complete, total, level, True)
|
||||||
@@ -448,6 +478,14 @@ class QuadtreeGen(object):
|
|||||||
return chunklist
|
return chunklist
|
||||||
|
|
||||||
@catch_keyboardinterrupt
|
@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_innertile(job[0],job[1],job[2],job[3])
|
||||||
|
return count
|
||||||
|
|
||||||
def render_innertile(dest, name, imgformat, optimizeimg):
|
def render_innertile(dest, name, imgformat, optimizeimg):
|
||||||
"""
|
"""
|
||||||
Renders a tile at os.path.join(dest, name)+".ext" by taking tiles from
|
Renders a tile at os.path.join(dest, name)+".ext" by taking tiles from
|
||||||
@@ -512,6 +550,30 @@ def render_innertile(dest, name, imgformat, optimizeimg):
|
|||||||
optimize_image(imgpath, imgformat, optimizeimg)
|
optimize_image(imgpath, imgformat, optimizeimg)
|
||||||
|
|
||||||
@catch_keyboardinterrupt
|
@catch_keyboardinterrupt
|
||||||
|
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
|
||||||
|
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):
|
def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path):
|
||||||
"""Renders just the specified chunks into a tile and save it. Unlike usual
|
"""Renders just the specified chunks into a tile and save it. Unlike usual
|
||||||
python conventions, rowend and colend are inclusive. Additionally, the
|
python conventions, rowend and colend are inclusive. Additionally, the
|
||||||
@@ -529,7 +591,7 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path)
|
|||||||
Standard tile size has colend-colstart=2 and rowend-rowstart=4
|
Standard tile size has colend-colstart=2 and rowend-rowstart=4
|
||||||
|
|
||||||
There is no return value
|
There is no return value
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# width of one chunk is 384. Each column is half a chunk wide. The total
|
# 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 is (384 + 192*(numcols-1)) since the first column contributes full
|
||||||
@@ -597,16 +659,10 @@ def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path)
|
|||||||
|
|
||||||
# check chunk mtimes to see if they are newer
|
# check chunk mtimes to see if they are newer
|
||||||
try:
|
try:
|
||||||
#tile_mtime = os.path.getmtime(imgpath)
|
|
||||||
regionMtimes = {}
|
|
||||||
needs_rerender = False
|
needs_rerender = False
|
||||||
for col, row, chunkx, chunky, regionfile in chunks:
|
for col, row, chunkx, chunky, regionfile in chunks:
|
||||||
# check region file mtime first.
|
# 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 = world.get_region_mtime(regionfile)
|
||||||
regionMtime = regionMtimes.get(regionfile,None)
|
|
||||||
if regionMtime is None:
|
|
||||||
regionMtime = os.path.getmtime(regionfile)
|
|
||||||
regionMtimes[regionfile] = regionMtime
|
|
||||||
if regionMtime <= tile_mtime:
|
if regionMtime <= tile_mtime:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
@@ -253,6 +253,7 @@ alpha_over_wrap(PyObject *self, PyObject *args)
|
|||||||
/* destination position read */
|
/* destination position read */
|
||||||
if (!PyArg_ParseTuple(pos, "iiii", &dx, &dy, &xsize, &ysize)) {
|
if (!PyArg_ParseTuple(pos, "iiii", &dx, &dy, &xsize, &ysize)) {
|
||||||
/* try again, but this time try to read a point */
|
/* try again, but this time try to read a point */
|
||||||
|
PyErr_Clear();
|
||||||
xsize = 0;
|
xsize = 0;
|
||||||
ysize = 0;
|
ysize = 0;
|
||||||
if (!PyArg_ParseTuple(pos, "ii", &dx, &dy)) {
|
if (!PyArg_ParseTuple(pos, "ii", &dx, &dy)) {
|
||||||
|
|||||||
@@ -22,6 +22,40 @@
|
|||||||
/* macro for getting blockID from a chunk of memory */
|
/* macro for getting blockID from a chunk of memory */
|
||||||
#define getBlock(blockThing, x,y,z) (*(unsigned char *)(PyArray_GETPTR3(blockThing, (x), (y), (z))))
|
#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) {
|
static inline int isTransparent(PyObject* tup, unsigned char b) {
|
||||||
PyObject *block = PyInt_FromLong(b);
|
PyObject *block = PyInt_FromLong(b);
|
||||||
int ret = PySequence_Contains(tup, block);
|
int ret = PySequence_Contains(tup, block);
|
||||||
@@ -302,8 +336,6 @@ chunk_render(PyObject *self, PyObject *args) {
|
|||||||
|
|
||||||
PyObject *blocks_py;
|
PyObject *blocks_py;
|
||||||
|
|
||||||
PyObject *textures, *blockmap, *special_blocks, *specialblockmap, *chunk_mod, *transparent_blocks;
|
|
||||||
|
|
||||||
int imgx, imgy;
|
int imgx, imgy;
|
||||||
int x, y, z;
|
int x, y, z;
|
||||||
|
|
||||||
@@ -331,23 +363,6 @@ chunk_render(PyObject *self, PyObject *args) {
|
|||||||
PyObject *right_blocks = PyObject_GetAttrString(chunk, "right_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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Py_DECREF(textures);
|
|
||||||
Py_DECREF(chunk_mod);
|
|
||||||
|
|
||||||
for (x = 15; x > -1; x--) {
|
for (x = 15; x > -1; x--) {
|
||||||
for (y = 0; y < 16; y++) {
|
for (y = 0; y < 16; y++) {
|
||||||
@@ -427,9 +442,6 @@ chunk_render(PyObject *self, PyObject *args) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Py_DECREF(blocks_py);
|
Py_DECREF(blocks_py);
|
||||||
Py_DECREF(blockmap);
|
|
||||||
Py_DECREF(special_blocks);
|
|
||||||
Py_DECREF(specialblockmap);
|
|
||||||
|
|
||||||
return Py_BuildValue("i",2);
|
return Py_BuildValue("i",2);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,4 +35,10 @@ initc_overviewer(void)
|
|||||||
(void)Py_InitModule("c_overviewer", COverviewerMethods);
|
(void)Py_InitModule("c_overviewer", COverviewerMethods);
|
||||||
/* for numpy */
|
/* for numpy */
|
||||||
import_array();
|
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?
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,5 +38,6 @@ PyObject *brightness(PyObject *img, double factor);
|
|||||||
/* in iterate.c */
|
/* in iterate.c */
|
||||||
PyObject *chunk_render(PyObject *self, PyObject *args);
|
PyObject *chunk_render(PyObject *self, PyObject *args);
|
||||||
PyObject *chunk_render_lighting(PyObject *self, PyObject *args);
|
PyObject *chunk_render_lighting(PyObject *self, PyObject *args);
|
||||||
|
int init_chunk_render(void);
|
||||||
|
|
||||||
#endif /* __OVERVIEWER_H_INCLUDED__ */
|
#endif /* __OVERVIEWER_H_INCLUDED__ */
|
||||||
|
|||||||
18
world.py
18
world.py
@@ -64,7 +64,7 @@ class World(object):
|
|||||||
"""Does world-level preprocessing to prepare for QuadtreeGen
|
"""Does world-level preprocessing to prepare for QuadtreeGen
|
||||||
worlddir is the path to the minecraft world
|
worlddir is the path to the minecraft world
|
||||||
"""
|
"""
|
||||||
|
|
||||||
mincol = maxcol = minrow = maxrow = 0
|
mincol = maxcol = minrow = maxrow = 0
|
||||||
|
|
||||||
def __init__(self, worlddir, useBiomeData=False,regionlist=None, lighting=False):
|
def __init__(self, worlddir, useBiomeData=False,regionlist=None, lighting=False):
|
||||||
@@ -79,7 +79,7 @@ class World(object):
|
|||||||
for x, y, regionfile in self._iterate_regionfiles():
|
for x, y, regionfile in self._iterate_regionfiles():
|
||||||
mcr = nbt.MCRFileReader(regionfile)
|
mcr = nbt.MCRFileReader(regionfile)
|
||||||
mcr.get_chunk_info()
|
mcr.get_chunk_info()
|
||||||
regions[regionfile] = mcr
|
regions[regionfile] = (mcr,os.path.getmtime(regionfile))
|
||||||
regionfiles[(x,y)] = (x,y,regionfile)
|
regionfiles[(x,y)] = (x,y,regionfile)
|
||||||
self.regionfiles = regionfiles
|
self.regionfiles = regionfiles
|
||||||
self.regions = regions
|
self.regions = regions
|
||||||
@@ -118,8 +118,6 @@ class World(object):
|
|||||||
_, _, regionfile = self.regionfiles.get((chunkX//32, chunkY//32),(None,None,None));
|
_, _, regionfile = self.regionfiles.get((chunkX//32, chunkY//32),(None,None,None));
|
||||||
return regionfile
|
return regionfile
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def load_from_region(self,filename, x, y):
|
def load_from_region(self,filename, x, y):
|
||||||
nbt = self.load_region(filename).load_chunk(x, y)
|
nbt = self.load_region(filename).load_chunk(x, y)
|
||||||
if nbt is None:
|
if nbt is None:
|
||||||
@@ -128,11 +126,15 @@ class World(object):
|
|||||||
return nbt.read_all()
|
return nbt.read_all()
|
||||||
|
|
||||||
|
|
||||||
#filo region cache
|
#used to reload a changed region
|
||||||
def load_region(self,filename):
|
def reload_region(self,filename):
|
||||||
#return nbt.MCRFileReader(filename)
|
self.regions[filename] = (nbt.MCRFileReader(filename),os.path.getmtime(regionfile))
|
||||||
return self.regions[filename]
|
|
||||||
|
|
||||||
|
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):
|
def convert_coords(self, chunkx, chunky):
|
||||||
"""Takes a coordinate (chunkx, chunky) where chunkx and chunky are
|
"""Takes a coordinate (chunkx, chunky) where chunkx and chunky are
|
||||||
|
|||||||
Reference in New Issue
Block a user