Merge branch 'master' into lighting
Conflicts: chunk.py gmap.py textures.py world.py
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.pyc
|
||||||
12
README.rst
12
README.rst
@@ -100,7 +100,7 @@ it.
|
|||||||
|
|
||||||
The Overviewer will put a cached image for every chunk *directly in your world
|
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
|
directory by default*. If you do not like this behavior, you can specify
|
||||||
another location with the --chunkdir option. See below for details.
|
another location with the --cachedir option. See below for details.
|
||||||
|
|
||||||
Options
|
Options
|
||||||
-------
|
-------
|
||||||
@@ -121,6 +121,12 @@ Options
|
|||||||
|
|
||||||
python gmap.py --cachedir=<chunk cache dir> <world> <output dir>
|
python gmap.py --cachedir=<chunk cache dir> <world> <output dir>
|
||||||
|
|
||||||
|
--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
|
-p PROCS, --processes=PROCS
|
||||||
Adding the "-p" option will utilize more cores during processing. This
|
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
|
can speed up rendering quite a bit. The default is set to the same
|
||||||
@@ -209,6 +215,10 @@ render for my world from 85M to 67M.
|
|||||||
|
|
||||||
find /path/to/destination -name "*.png" -exec pngcrush {} {}.crush \; -exec mv {}.crush {} \;
|
find /path/to/destination -name "*.png" -exec pngcrush {} {}.crush \; -exec mv {}.crush {} \;
|
||||||
|
|
||||||
|
Or if you prefer a more parallel solution, try something like this::
|
||||||
|
|
||||||
|
find /path/to/destination -print0 | xargs -0 -n 1 -P <nr_procs> sh -c 'pngcrush $0 temp.$$ && mv temp.$$ $0'
|
||||||
|
|
||||||
If you're on Windows, I've gotten word that this command line snippet works
|
If you're on Windows, I've gotten word that this command line snippet works
|
||||||
provided pngout is installed and on your path. Note that the % symbols will
|
provided pngout is installed and on your path. Note that the % symbols will
|
||||||
need to be doubled up if this is in a batch file.
|
need to be doubled up if this is in a batch file.
|
||||||
|
|||||||
48
chunk.py
48
chunk.py
@@ -15,7 +15,6 @@
|
|||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from PIL import Image, ImageDraw, ImageEnhance
|
from PIL import Image, ImageDraw, ImageEnhance
|
||||||
from itertools import izip, count
|
|
||||||
import os.path
|
import os.path
|
||||||
import hashlib
|
import hashlib
|
||||||
|
|
||||||
@@ -88,9 +87,14 @@ def get_blocklight_array(level):
|
|||||||
blocklight_expanded[:,:,1::2] = (blocklight & 0xF0) >> 4
|
blocklight_expanded[:,:,1::2] = (blocklight & 0xF0) >> 4
|
||||||
return blocklight_expanded
|
return blocklight_expanded
|
||||||
|
|
||||||
|
def get_blockdata_array(level):
|
||||||
|
"""Returns the ancillary data from the 'Data' byte array. Data is packed
|
||||||
|
in a similar manner to skylight data"""
|
||||||
|
return numpy.frombuffer(level['Data'], dtype=numpy.uint8).reshape((16,16,64))
|
||||||
|
|
||||||
# This set holds blocks ids that can be seen through, for occlusion calculations
|
# 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, 50, 51, 52, 53,
|
transparent_blocks = set([0, 6, 8, 9, 18, 20, 37, 38, 39, 40, 44, 50, 51, 52, 53,
|
||||||
59, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 79, 83, 85])
|
59, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 81, 83, 85])
|
||||||
|
|
||||||
def render_and_save(chunkfile, cachedir, worldobj, cave=False):
|
def render_and_save(chunkfile, cachedir, worldobj, cave=False):
|
||||||
"""Used as the entry point for the multiprocessing workers (since processes
|
"""Used as the entry point for the multiprocessing workers (since processes
|
||||||
@@ -309,6 +313,13 @@ class ChunkRenderer(object):
|
|||||||
blocks = blocks.copy()
|
blocks = blocks.copy()
|
||||||
blocks[skylight != 0] = 21
|
blocks[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
|
||||||
|
blockData_expanded[:,:,::2] = blockData & 0x0F
|
||||||
|
# Odd elements get the upper 4 bits
|
||||||
|
blockData_expanded[:,:,1::2] = blockData >> 4
|
||||||
|
|
||||||
|
|
||||||
# Each block is 24x24
|
# 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 X axis adds 12px to x and subtracts 6px from y in the image
|
||||||
@@ -327,7 +338,23 @@ class ChunkRenderer(object):
|
|||||||
for z in xrange(128):
|
for z in xrange(128):
|
||||||
try:
|
try:
|
||||||
blockid = blocks[x,y,z]
|
blockid = blocks[x,y,z]
|
||||||
t = textures.blockmap[blockid]
|
|
||||||
|
# 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:
|
||||||
|
t = textures.specialblockmap[(blockid, ancilData)]
|
||||||
|
except KeyError:
|
||||||
|
t = None
|
||||||
|
|
||||||
|
else:
|
||||||
|
t = textures.blockmap[blockid]
|
||||||
if not t:
|
if not t:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -420,12 +447,19 @@ class ChunkRenderer(object):
|
|||||||
img.paste((0,0,0), (imgx, imgy), ImageEnhance.Brightness(facemasks[2]).enhance(black_coeff))
|
img.paste((0,0,0), (imgx, imgy), ImageEnhance.Brightness(facemasks[2]).enhance(black_coeff))
|
||||||
|
|
||||||
# Draw edge lines
|
# Draw edge lines
|
||||||
if blockid not in transparent_blocks:
|
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)
|
draw = ImageDraw.Draw(img)
|
||||||
if x != 15 and blocks[x+1,y,z] == 0:
|
if x != 15 and blocks[x+1,y,z] == 0:
|
||||||
draw.line(((imgx+12,imgy), (imgx+22,imgy+5)), fill=(0,0,0), width=1)
|
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:
|
if y != 0 and blocks[x,y-1,z] == 0:
|
||||||
draw.line(((imgx,imgy+6), (imgx+12,imgy)), fill=(0,0,0), width=1)
|
draw.line(((imgx,imgy+6+increment), (imgx+12,imgy+increment)), fill=(0,0,0), width=1)
|
||||||
|
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
|||||||
32
gmap.py
32
gmap.py
@@ -26,14 +26,16 @@ from optparse import OptionParser
|
|||||||
import re
|
import re
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import time
|
import time
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s")
|
||||||
|
|
||||||
import world
|
import world
|
||||||
import quadtree
|
import quadtree
|
||||||
|
|
||||||
helptext = """
|
helptext = """
|
||||||
%prog [OPTIONS] <World # / Path to World> <tiles dest dir>
|
%prog [OPTIONS] <World # / Path to World> <tiles dest dir>
|
||||||
%prog -d <World # / Path to World / Path to cache dir> [tiles dest dir]
|
%prog -d <World # / Path to World / Path to cache dir> [tiles dest dir]"""
|
||||||
"""
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
try:
|
try:
|
||||||
@@ -48,6 +50,9 @@ def main():
|
|||||||
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("--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("--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.")
|
||||||
|
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.")
|
||||||
|
|
||||||
options, args = parser.parse_args()
|
options, args = parser.parse_args()
|
||||||
|
|
||||||
@@ -87,12 +92,28 @@ def main():
|
|||||||
else:
|
else:
|
||||||
chunklist = None
|
chunklist = None
|
||||||
|
|
||||||
|
if options.imgformat:
|
||||||
|
if options.imgformat not in ('jpg','png'):
|
||||||
|
parser.error("Unknown imgformat!")
|
||||||
|
else:
|
||||||
|
imgformat = options.imgformat
|
||||||
|
else:
|
||||||
|
imgformat = 'png'
|
||||||
|
|
||||||
|
logging.getLogger().setLevel(
|
||||||
|
logging.getLogger().level + 10*options.quiet)
|
||||||
|
logging.getLogger().setLevel(
|
||||||
|
logging.getLogger().level - 10*options.verbose)
|
||||||
|
|
||||||
|
logging.info("Welcome to Minecraft Overviewer!")
|
||||||
|
logging.debug("Current log level: {0}".format(logging.getLogger().level))
|
||||||
|
|
||||||
# First generate the world's chunk images
|
# First generate the world's chunk images
|
||||||
w = world.WorldRenderer(worlddir, cachedir, chunklist=chunklist, lighting=options.lighting, night=options.night)
|
w = world.WorldRenderer(worlddir, cachedir, chunklist=chunklist, lighting=options.lighting, night=options.night)
|
||||||
w.go(options.procs)
|
w.go(options.procs)
|
||||||
|
|
||||||
# Now generate the tiles
|
# Now generate the tiles
|
||||||
q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom)
|
q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat)
|
||||||
q.go(options.procs)
|
q.go(options.procs)
|
||||||
|
|
||||||
def delete_all(worlddir, tiledir):
|
def delete_all(worlddir, tiledir):
|
||||||
@@ -104,7 +125,7 @@ def delete_all(worlddir, tiledir):
|
|||||||
for f in filenames:
|
for f in filenames:
|
||||||
if matcher.match(f):
|
if matcher.match(f):
|
||||||
filepath = os.path.join(dirpath, f)
|
filepath = os.path.join(dirpath, f)
|
||||||
print "Deleting {0}".format(filepath)
|
logging.info("Deleting {0}".format(filepath))
|
||||||
os.unlink(filepath)
|
os.unlink(filepath)
|
||||||
|
|
||||||
# Now delete all /hash/ files in the tile dir.
|
# Now delete all /hash/ files in the tile dir.
|
||||||
@@ -113,7 +134,7 @@ def delete_all(worlddir, tiledir):
|
|||||||
for f in filenames:
|
for f in filenames:
|
||||||
if f.endswith(".hash"):
|
if f.endswith(".hash"):
|
||||||
filepath = os.path.join(dirpath, f)
|
filepath = os.path.join(dirpath, f)
|
||||||
print "Deleting {0}".format(filepath)
|
logging.info("Deleting {0}".format(filepath))
|
||||||
os.unlink(filepath)
|
os.unlink(filepath)
|
||||||
|
|
||||||
def list_worlds():
|
def list_worlds():
|
||||||
@@ -134,4 +155,5 @@ def list_worlds():
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
multiprocessing.freeze_support()
|
||||||
main()
|
main()
|
||||||
|
|||||||
100
quadtree.py
100
quadtree.py
@@ -23,9 +23,12 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import collections
|
import collections
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
|
import util
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This module has routines related to generating a quadtree of tiles
|
This module has routines related to generating a quadtree of tiles
|
||||||
|
|
||||||
@@ -43,7 +46,7 @@ def catch_keyboardinterrupt(func):
|
|||||||
try:
|
try:
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
print "Ctrl-C caught!"
|
logging.error("Ctrl-C caught!")
|
||||||
raise Exception("Exiting")
|
raise Exception("Exiting")
|
||||||
except:
|
except:
|
||||||
import traceback
|
import traceback
|
||||||
@@ -52,7 +55,7 @@ def catch_keyboardinterrupt(func):
|
|||||||
return newfunc
|
return newfunc
|
||||||
|
|
||||||
class QuadtreeGen(object):
|
class QuadtreeGen(object):
|
||||||
def __init__(self, worldobj, destdir, depth=None):
|
def __init__(self, worldobj, destdir, depth=None, imgformat=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
|
||||||
|
|
||||||
@@ -62,6 +65,9 @@ class QuadtreeGen(object):
|
|||||||
minimum depth that contains all chunks is calculated and used.
|
minimum depth that contains all chunks is calculated and used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
assert(imgformat)
|
||||||
|
self.imgformat = imgformat
|
||||||
|
|
||||||
if depth is None:
|
if depth is None:
|
||||||
# Determine quadtree depth (midpoint is always 0,0)
|
# Determine quadtree depth (midpoint is always 0,0)
|
||||||
for p in xrange(15):
|
for p in xrange(15):
|
||||||
@@ -106,16 +112,18 @@ class QuadtreeGen(object):
|
|||||||
else:
|
else:
|
||||||
if not complete % 1000 == 0:
|
if not complete % 1000 == 0:
|
||||||
return
|
return
|
||||||
print "{0}/{1} tiles complete on level {2}/{3}".format(
|
logging.info("{0}/{1} tiles complete on level {2}/{3}".format(
|
||||||
complete, total, level, self.p)
|
complete, total, level, self.p))
|
||||||
|
|
||||||
def write_html(self, zoomlevel):
|
def write_html(self, zoomlevel, imgformat):
|
||||||
"""Writes out index.html"""
|
"""Writes out index.html"""
|
||||||
templatepath = os.path.join(os.path.split(__file__)[0], "template.html")
|
templatepath = os.path.join(util.get_program_path(), "template.html")
|
||||||
|
|
||||||
html = open(templatepath, 'r').read()
|
html = open(templatepath, 'r').read()
|
||||||
html = html.replace(
|
html = html.replace(
|
||||||
"{maxzoom}", str(zoomlevel))
|
"{maxzoom}", str(zoomlevel))
|
||||||
|
html = html.replace(
|
||||||
|
"{imgformat}", str(imgformat))
|
||||||
|
|
||||||
with open(os.path.join(self.destdir, "index.html"), 'w') as output:
|
with open(os.path.join(self.destdir, "index.html"), 'w') as output:
|
||||||
output.write(html)
|
output.write(html)
|
||||||
@@ -125,6 +133,12 @@ class QuadtreeGen(object):
|
|||||||
with open(os.path.join(self.destdir, "markers.js"), 'w') as output:
|
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=%s" % json.dumps(self.world.POI))
|
||||||
|
|
||||||
|
# Write a blank image
|
||||||
|
blank = Image.new("RGBA", (1,1))
|
||||||
|
tileDir = os.path.join(self.destdir, "tiles")
|
||||||
|
if not os.path.exists(tileDir): os.mkdir(tileDir)
|
||||||
|
blank.save(os.path.join(tileDir, "blank."+self.imgformat))
|
||||||
|
|
||||||
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
|
||||||
index.html to see what maxZoom is set to.
|
index.html to see what maxZoom is set to.
|
||||||
@@ -159,8 +173,8 @@ class QuadtreeGen(object):
|
|||||||
newdir = "new" + str(dirnum)
|
newdir = "new" + str(dirnum)
|
||||||
newdirpath = getpath(newdir)
|
newdirpath = getpath(newdir)
|
||||||
|
|
||||||
files = [str(dirnum)+".png", str(dirnum)+".hash", str(dirnum)]
|
files = [str(dirnum)+"."+self.imgformat, str(dirnum)+".hash", str(dirnum)]
|
||||||
newfiles = [str(newnum)+".png", str(newnum)+".hash", str(newnum)]
|
newfiles = [str(newnum)+"."+self.imgformat, str(newnum)+".hash", str(newnum)]
|
||||||
|
|
||||||
os.mkdir(newdirpath)
|
os.mkdir(newdirpath)
|
||||||
for f, newf in zip(files, newfiles):
|
for f, newf in zip(files, newfiles):
|
||||||
@@ -221,7 +235,7 @@ class QuadtreeGen(object):
|
|||||||
# (even if tilechunks is empty, render_worldtile will delete
|
# (even if tilechunks is empty, render_worldtile will delete
|
||||||
# existing images if appropriate)
|
# existing images if appropriate)
|
||||||
yield pool.apply_async(func=render_worldtile, args= (tilechunks,
|
yield pool.apply_async(func=render_worldtile, args= (tilechunks,
|
||||||
colstart, colend, rowstart, rowend, dest))
|
colstart, colend, rowstart, rowend, dest, self.imgformat))
|
||||||
|
|
||||||
def _apply_render_inntertile(self, pool, zoom):
|
def _apply_render_inntertile(self, pool, zoom):
|
||||||
"""Same as _apply_render_worltiles but for the inntertile routine.
|
"""Same as _apply_render_worltiles but for the inntertile routine.
|
||||||
@@ -233,7 +247,7 @@ class QuadtreeGen(object):
|
|||||||
dest = os.path.join(self.destdir, "tiles", *(str(x) for x in path[:-1]))
|
dest = os.path.join(self.destdir, "tiles", *(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))
|
yield pool.apply_async(func=render_innertile, args= (dest, name, self.imgformat))
|
||||||
|
|
||||||
def go(self, procs):
|
def go(self, procs):
|
||||||
"""Renders all tiles"""
|
"""Renders all tiles"""
|
||||||
@@ -245,12 +259,12 @@ class QuadtreeGen(object):
|
|||||||
curdepth = self._get_cur_depth()
|
curdepth = self._get_cur_depth()
|
||||||
if curdepth != -1:
|
if curdepth != -1:
|
||||||
if self.p > curdepth:
|
if self.p > curdepth:
|
||||||
print "Your map seemes to have expanded beyond its previous bounds."
|
logging.warning("Your map seemes to have expanded beyond its previous bounds.")
|
||||||
print "Doing some tile re-arrangements... just a sec..."
|
logging.warning( "Doing some tile re-arrangements... just a sec...")
|
||||||
for _ in xrange(self.p-curdepth):
|
for _ in xrange(self.p-curdepth):
|
||||||
self._increase_depth()
|
self._increase_depth()
|
||||||
elif self.p < curdepth:
|
elif self.p < curdepth:
|
||||||
print "Your map seems to have shrunk. Re-arranging tiles, just a sec..."
|
logging.warning("Your map seems to have shrunk. Re-arranging tiles, just a sec...")
|
||||||
for _ in xrange(curdepth - self.p):
|
for _ in xrange(curdepth - self.p):
|
||||||
self._decrease_depth()
|
self._decrease_depth()
|
||||||
|
|
||||||
@@ -260,17 +274,17 @@ class QuadtreeGen(object):
|
|||||||
else:
|
else:
|
||||||
pool = multiprocessing.Pool(processes=procs)
|
pool = multiprocessing.Pool(processes=procs)
|
||||||
|
|
||||||
self.write_html(self.p)
|
self.write_html(self.p, self.imgformat)
|
||||||
|
|
||||||
# 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
|
||||||
total = 4**self.p
|
total = 4**self.p
|
||||||
print "Rendering highest zoom level of tiles now."
|
logging.info("Rendering highest zoom level of tiles now.")
|
||||||
print "There are {0} tiles to render".format(total)
|
logging.info("There are {0} tiles to render".format(total))
|
||||||
print "There are {0} total levels to render".format(self.p)
|
logging.info("There are {0} total levels to render".format(self.p))
|
||||||
print "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.")
|
||||||
print "The others will go faster"
|
logging.info("The others will go faster")
|
||||||
for result in self._apply_render_worldtiles(pool):
|
for result in self._apply_render_worldtiles(pool):
|
||||||
results.append(result)
|
results.append(result)
|
||||||
if len(results) > 10000:
|
if len(results) > 10000:
|
||||||
@@ -295,7 +309,7 @@ class QuadtreeGen(object):
|
|||||||
assert len(results) == 0
|
assert len(results) == 0
|
||||||
complete = 0
|
complete = 0
|
||||||
total = 4**zoom
|
total = 4**zoom
|
||||||
print "Starting level", 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):
|
||||||
results.append(result)
|
results.append(result)
|
||||||
if len(results) > 10000:
|
if len(results) > 10000:
|
||||||
@@ -311,13 +325,13 @@ class QuadtreeGen(object):
|
|||||||
|
|
||||||
self.print_statusline(complete, total, level, True)
|
self.print_statusline(complete, total, level, True)
|
||||||
|
|
||||||
print "Done"
|
logging.info("Done")
|
||||||
|
|
||||||
pool.close()
|
pool.close()
|
||||||
pool.join()
|
pool.join()
|
||||||
|
|
||||||
# Do the final one right here:
|
# Do the final one right here:
|
||||||
render_innertile(os.path.join(self.destdir, "tiles"), "base")
|
render_innertile(os.path.join(self.destdir, "tiles"), "base", self.imgformat)
|
||||||
|
|
||||||
def _get_range_by_path(self, path):
|
def _get_range_by_path(self, path):
|
||||||
"""Returns the x, y chunk coordinates of this tile"""
|
"""Returns the x, y chunk coordinates of this tile"""
|
||||||
@@ -348,28 +362,28 @@ class QuadtreeGen(object):
|
|||||||
return chunklist
|
return chunklist
|
||||||
|
|
||||||
@catch_keyboardinterrupt
|
@catch_keyboardinterrupt
|
||||||
def render_innertile(dest, name):
|
def render_innertile(dest, name, imgformat):
|
||||||
"""
|
"""
|
||||||
Renders a tile at os.path.join(dest, name)+".png" by taking tiles from
|
Renders a tile at os.path.join(dest, name)+".ext" by taking tiles from
|
||||||
os.path.join(dest, name, "{0,1,2,3}.png")
|
os.path.join(dest, name, "{0,1,2,3}.png")
|
||||||
"""
|
"""
|
||||||
imgpath = os.path.join(dest, name) + ".png"
|
imgpath = os.path.join(dest, name) + "." + imgformat
|
||||||
hashpath = os.path.join(dest, name) + ".hash"
|
hashpath = os.path.join(dest, name) + ".hash"
|
||||||
|
|
||||||
if name == "base":
|
if name == "base":
|
||||||
q0path = os.path.join(dest, "0.png")
|
q0path = os.path.join(dest, "0." + imgformat)
|
||||||
q1path = os.path.join(dest, "1.png")
|
q1path = os.path.join(dest, "1." + imgformat)
|
||||||
q2path = os.path.join(dest, "2.png")
|
q2path = os.path.join(dest, "2." + imgformat)
|
||||||
q3path = os.path.join(dest, "3.png")
|
q3path = os.path.join(dest, "3." + imgformat)
|
||||||
q0hash = os.path.join(dest, "0.hash")
|
q0hash = os.path.join(dest, "0.hash")
|
||||||
q1hash = os.path.join(dest, "1.hash")
|
q1hash = os.path.join(dest, "1.hash")
|
||||||
q2hash = os.path.join(dest, "2.hash")
|
q2hash = os.path.join(dest, "2.hash")
|
||||||
q3hash = os.path.join(dest, "3.hash")
|
q3hash = os.path.join(dest, "3.hash")
|
||||||
else:
|
else:
|
||||||
q0path = os.path.join(dest, name, "0.png")
|
q0path = os.path.join(dest, name, "0." + imgformat)
|
||||||
q1path = os.path.join(dest, name, "1.png")
|
q1path = os.path.join(dest, name, "1." + imgformat)
|
||||||
q2path = os.path.join(dest, name, "2.png")
|
q2path = os.path.join(dest, name, "2." + imgformat)
|
||||||
q3path = os.path.join(dest, name, "3.png")
|
q3path = os.path.join(dest, name, "3." + imgformat)
|
||||||
q0hash = os.path.join(dest, name, "0.hash")
|
q0hash = os.path.join(dest, name, "0.hash")
|
||||||
q1hash = os.path.join(dest, name, "1.hash")
|
q1hash = os.path.join(dest, name, "1.hash")
|
||||||
q2hash = os.path.join(dest, name, "2.hash")
|
q2hash = os.path.join(dest, name, "2.hash")
|
||||||
@@ -434,13 +448,16 @@ def render_innertile(dest, name):
|
|||||||
img.paste(quad3, (192, 192))
|
img.paste(quad3, (192, 192))
|
||||||
|
|
||||||
# Save it
|
# Save it
|
||||||
img.save(imgpath)
|
if imgformat == 'jpg':
|
||||||
|
img.save(imgpath, quality=95, subsampling=0)
|
||||||
|
else: # png
|
||||||
|
img.save(imgpath)
|
||||||
with open(hashpath, "wb") as hashout:
|
with open(hashpath, "wb") as hashout:
|
||||||
hashout.write(newhash)
|
hashout.write(newhash)
|
||||||
|
|
||||||
|
|
||||||
@catch_keyboardinterrupt
|
@catch_keyboardinterrupt
|
||||||
def render_worldtile(chunks, colstart, colend, rowstart, rowend, path):
|
def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat):
|
||||||
"""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
|
||||||
chunks around the edges are half-way cut off (so that neighboring tiles
|
chunks around the edges are half-way cut off (so that neighboring tiles
|
||||||
@@ -449,7 +466,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path):
|
|||||||
chunks is a list of (col, row, filename) of chunk images that are relevant
|
chunks is a list of (col, row, filename) of chunk images that are relevant
|
||||||
to this call
|
to this call
|
||||||
|
|
||||||
The image is saved to path+".png" and a hash is saved to path+".hash"
|
The image is saved to path+".ext" and a hash is saved to path+".hash"
|
||||||
|
|
||||||
If there are no chunks, this tile is not saved (if it already exists, it is
|
If there are no chunks, this tile is not saved (if it already exists, it is
|
||||||
deleted)
|
deleted)
|
||||||
@@ -490,7 +507,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path):
|
|||||||
# Before we render any tiles, check the hash of each image in this tile to
|
# Before we render any tiles, check the hash of each image in this tile to
|
||||||
# see if it's changed.
|
# see if it's changed.
|
||||||
hashpath = path + ".hash"
|
hashpath = path + ".hash"
|
||||||
imgpath = path + ".png"
|
imgpath = path + "." + imgformat
|
||||||
|
|
||||||
if not chunks:
|
if not chunks:
|
||||||
# No chunks were found in this tile
|
# No chunks were found in this tile
|
||||||
@@ -545,8 +562,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path):
|
|||||||
# corrupting it), then this could error.
|
# corrupting it), then this could error.
|
||||||
# Since we have no easy way of determining how this chunk was
|
# Since we have no easy way of determining how this chunk was
|
||||||
# generated, we need to just ignore it.
|
# generated, we need to just ignore it.
|
||||||
print "Error opening file", chunkfile
|
logging.warning("Could not open chunk '{0}' ({1})".format(chunkfile,e))
|
||||||
print "(Error was {0})".format(e)
|
|
||||||
try:
|
try:
|
||||||
# Remove the file so that the next run will re-generate it.
|
# Remove the file so that the next run will re-generate it.
|
||||||
os.unlink(chunkfile)
|
os.unlink(chunkfile)
|
||||||
@@ -555,12 +571,12 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path):
|
|||||||
# Ignore if file doesn't exist, another task could have already
|
# Ignore if file doesn't exist, another task could have already
|
||||||
# removed it.
|
# removed it.
|
||||||
if e.errno != errno.ENOENT:
|
if e.errno != errno.ENOENT:
|
||||||
print "Could not remove the corrupt chunk!"
|
logging.warning("Could not remove chunk '{0}'!".format(chunkfile))
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
print "Removed the corrupt file"
|
logging.warning("Removed the corrupt file")
|
||||||
|
|
||||||
print "You will need to re-run the Overviewer to fix this chunk"
|
logging.warning("You will need to re-run the Overviewer to fix this chunk")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
xpos = -192 + (col-colstart)*192
|
xpos = -192 + (col-colstart)*192
|
||||||
|
|||||||
13
setup.py
Normal file
13
setup.py
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
from distutils.core import setup
|
||||||
|
import py2exe
|
||||||
|
|
||||||
|
setup(console=['gmap.py'],
|
||||||
|
data_files=[('textures', ['textures/lava.png', 'textures/water.png']),
|
||||||
|
('', ['template.html'])],
|
||||||
|
zipfile = None,
|
||||||
|
options = {'py2exe': {
|
||||||
|
'bundle_files': 1,
|
||||||
|
}},
|
||||||
|
|
||||||
|
|
||||||
|
)
|
||||||
166
template.html
166
template.html
@@ -14,14 +14,73 @@
|
|||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
var config = {
|
var config = {
|
||||||
path: 'tiles',
|
path: 'tiles',
|
||||||
fileExt: 'png',
|
fileExt: '{imgformat}',
|
||||||
tileSize: 384,
|
tileSize: 384,
|
||||||
defaultZoom: 1,
|
defaultZoom: 1,
|
||||||
maxZoom: {maxzoom},
|
maxZoom: {maxzoom},
|
||||||
cacheMinutes: 0, // Change this to have browsers automatically requiest new images every x minutes
|
cacheMinutes: 0, // Change this to have browsers automatically request new images every x minutes
|
||||||
debug: false
|
debug: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 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"
|
||||||
|
|
||||||
|
// 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 and 18px to the Y to center our point
|
||||||
|
lng += 12 * perPixel;
|
||||||
|
lat += 18 * perPixel;
|
||||||
|
|
||||||
|
return new google.maps.LatLng(lat, lng);
|
||||||
|
}
|
||||||
|
|
||||||
var MCMapOptions = {
|
var MCMapOptions = {
|
||||||
getTileUrl: function(tile, zoom) {
|
getTileUrl: function(tile, zoom) {
|
||||||
var url = config.path;
|
var url = config.path;
|
||||||
@@ -52,6 +111,7 @@
|
|||||||
var MCMapType = new google.maps.ImageMapType(MCMapOptions);
|
var MCMapType = new google.maps.ImageMapType(MCMapOptions);
|
||||||
MCMapType.name = "MC Map";
|
MCMapType.name = "MC Map";
|
||||||
MCMapType.alt = "Minecraft Map";
|
MCMapType.alt = "Minecraft Map";
|
||||||
|
MCMapType.projection = new MCMapProjection();
|
||||||
|
|
||||||
function CoordMapType() {
|
function CoordMapType() {
|
||||||
}
|
}
|
||||||
@@ -75,61 +135,30 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
var map;
|
var map;
|
||||||
var prot;
|
|
||||||
|
|
||||||
var markersInit = false;
|
var markersInit = false;
|
||||||
|
|
||||||
function convertCoords (x,y,z) {
|
|
||||||
|
|
||||||
var imgx = 0;
|
|
||||||
var imgy = 0;
|
|
||||||
|
|
||||||
imgx = imgx + (12*x);
|
|
||||||
imgy = imgy - (6*x);
|
|
||||||
|
|
||||||
imgx = imgx + (12 * y);
|
|
||||||
imgy = imgy + (6* y);
|
|
||||||
|
|
||||||
imgy = imgy - (12*z);
|
|
||||||
|
|
||||||
// this math is mysterious. i don't fully understand it
|
|
||||||
// but the idea is to assume that block 0,0,0 in chunk 0,0
|
|
||||||
// is drawn in the very middle of the gmap at (192,192)
|
|
||||||
return [192*Math.pow(2,config.maxZoom)+imgx, 192*Math.pow(2,config.maxZoom)+imgy+768+768];
|
|
||||||
}
|
|
||||||
|
|
||||||
function initMarkers() {
|
function initMarkers() {
|
||||||
if (markersInit) { return; }
|
if (markersInit) { return; }
|
||||||
|
|
||||||
markersInit = true;
|
markersInit = true;
|
||||||
|
|
||||||
prot = map.getProjection();
|
|
||||||
|
|
||||||
for (i in markerData) {
|
|
||||||
var item = markerData[i];
|
|
||||||
|
|
||||||
var converted = convertCoords(item.x-16, item.z, item.y);
|
|
||||||
|
|
||||||
|
|
||||||
var x = converted[0] / Math.pow(2, config.maxZoom);
|
|
||||||
var y = converted[1] / Math.pow(2, config.maxZoom);
|
|
||||||
var p = new google.maps.Point(x,y);
|
|
||||||
var marker = new google.maps.Marker({
|
|
||||||
position: prot.fromPointToLatLng(p),
|
|
||||||
map: map,
|
|
||||||
title:item.msg
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
for (i in markerData) {
|
||||||
|
var item = markerData[i];
|
||||||
|
|
||||||
|
var converted = fromWorldToLatLng(item.x, item.y, item.z);
|
||||||
|
var marker = new google.maps.Marker({
|
||||||
|
position: converted,
|
||||||
|
map: map,
|
||||||
|
title: item.msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function initialize() {
|
function initialize() {
|
||||||
var mapOptions = {
|
var mapOptions = {
|
||||||
zoom: config.defaultZoom,
|
zoom: config.defaultZoom,
|
||||||
center: new google.maps.LatLng(-45, 90),
|
center: new google.maps.LatLng(0.5, 0.5),
|
||||||
navigationControl: true,
|
navigationControl: true,
|
||||||
scaleControl: false,
|
scaleControl: false,
|
||||||
mapTypeControl: false,
|
mapTypeControl: false,
|
||||||
@@ -139,34 +168,27 @@ title:item.msg
|
|||||||
|
|
||||||
if(config.debug) {
|
if(config.debug) {
|
||||||
map.overlayMapTypes.insertAt(0, new CoordMapType(new google.maps.Size(config.tileSize, config.tileSize)));
|
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
|
// Now attach the coordinate map type to the map's registry
|
||||||
map.mapTypes.set('mcmap', MCMapType);
|
map.mapTypes.set('mcmap', MCMapType);
|
||||||
|
|
||||||
// We can now set the map to use the 'coordinate' map type
|
// We can now set the map to use the 'coordinate' map type
|
||||||
map.setMapTypeId('mcmap');
|
map.setMapTypeId('mcmap');
|
||||||
|
|
||||||
prot = map.getProjection();
|
// initialize the markers
|
||||||
|
initMarkers();
|
||||||
if (config.debug)
|
|
||||||
google.maps.event.addListener(map, 'click', function(event) {
|
|
||||||
console.log("latLng: " + event.latLng.lat() + ", " + event.latLng.lng());
|
|
||||||
var pnt = prot.fromLatLngToPoint(event.latLng);
|
|
||||||
|
|
||||||
console.log("point: " + pnt);//
|
|
||||||
var pxx = pnt.x * Math.pow(2,config.maxZoom);
|
|
||||||
var pxy = pnt.y * Math.pow(2,config.maxZoom);
|
|
||||||
console.log("pixel: " + pxx + ", " + pxy);
|
|
||||||
});
|
|
||||||
|
|
||||||
google.maps.event.addListener(map, 'projection_changed', function(event) {
|
|
||||||
initMarkers();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
270
textures.py
270
textures.py
@@ -23,6 +23,8 @@ import math
|
|||||||
import numpy
|
import numpy
|
||||||
from PIL import Image, ImageEnhance
|
from PIL import Image, ImageEnhance
|
||||||
|
|
||||||
|
import util
|
||||||
|
|
||||||
def _find_file(filename, mode="rb"):
|
def _find_file(filename, mode="rb"):
|
||||||
"""Searches for the given file and returns an open handle to it.
|
"""Searches for the given file and returns an open handle to it.
|
||||||
This searches the following locations in this order:
|
This searches the following locations in this order:
|
||||||
@@ -39,7 +41,7 @@ def _find_file(filename, mode="rb"):
|
|||||||
* The program dir / textures
|
* The program dir / textures
|
||||||
|
|
||||||
"""
|
"""
|
||||||
programdir = os.path.dirname(__file__)
|
programdir = util.get_program_path()
|
||||||
path = os.path.join(programdir, filename)
|
path = os.path.join(programdir, filename)
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
return open(path, mode)
|
return open(path, mode)
|
||||||
@@ -92,13 +94,19 @@ def _get_terrain_image():
|
|||||||
def _split_terrain(terrain):
|
def _split_terrain(terrain):
|
||||||
"""Builds and returns a length 256 array of each 16x16 chunk of texture"""
|
"""Builds and returns a length 256 array of each 16x16 chunk of texture"""
|
||||||
textures = []
|
textures = []
|
||||||
|
(terrain_width, terrain_height) = terrain.size
|
||||||
|
texture_resolution = terrain_width / 16
|
||||||
for y in xrange(16):
|
for y in xrange(16):
|
||||||
for x in xrange(16):
|
for x in xrange(16):
|
||||||
left = x*16
|
left = x*texture_resolution
|
||||||
upper = y*16
|
upper = y*texture_resolution
|
||||||
right = left+16
|
right = left+texture_resolution
|
||||||
lower = upper+16
|
lower = upper+texture_resolution
|
||||||
region = terrain.crop((left,upper,right,lower))
|
region = terrain.transform(
|
||||||
|
(16, 16),
|
||||||
|
Image.EXTENT,
|
||||||
|
(left,upper,right,lower),
|
||||||
|
Image.BICUBIC)
|
||||||
textures.append(region)
|
textures.append(region)
|
||||||
|
|
||||||
return textures
|
return textures
|
||||||
@@ -106,15 +114,20 @@ def _split_terrain(terrain):
|
|||||||
# This maps terainids to 16x16 images
|
# This maps terainids to 16x16 images
|
||||||
terrain_images = _split_terrain(_get_terrain_image())
|
terrain_images = _split_terrain(_get_terrain_image())
|
||||||
|
|
||||||
def transform_image(img):
|
def transform_image(img, blockID=None):
|
||||||
"""Takes a PIL image and rotates it left 45 degrees and shrinks the y axis
|
"""Takes a PIL image and rotates it left 45 degrees and shrinks the y axis
|
||||||
by a factor of 2. Returns the resulting image, which will be 24x12 pixels
|
by a factor of 2. Returns the resulting image, which will be 24x12 pixels
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Resize to 17x17, since the diagonal is approximately 24 pixels, a nice
|
if blockID in (81,): # cacti
|
||||||
# even number that can be split in half twice
|
# Resize to 15x15, since the cactus texture is a little smaller than the other textures
|
||||||
img = img.resize((17, 17), Image.BILINEAR)
|
img = img.resize((15, 15), Image.BILINEAR)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Resize to 17x17, since the diagonal is approximately 24 pixels, a nice
|
||||||
|
# even number that can be split in half twice
|
||||||
|
img = img.resize((17, 17), Image.BILINEAR)
|
||||||
|
|
||||||
# Build the Affine transformation matrix for this perspective
|
# Build the Affine transformation matrix for this perspective
|
||||||
transform = numpy.matrix(numpy.identity(3))
|
transform = numpy.matrix(numpy.identity(3))
|
||||||
@@ -134,10 +147,25 @@ def transform_image(img):
|
|||||||
newimg = img.transform((24,12), Image.AFFINE, transform)
|
newimg = img.transform((24,12), Image.AFFINE, transform)
|
||||||
return newimg
|
return newimg
|
||||||
|
|
||||||
def transform_image_side(img):
|
def transform_image_side(img, blockID=None):
|
||||||
"""Takes an image and shears it for the left side of the cube (reflect for
|
"""Takes an image and shears it for the left side of the cube (reflect for
|
||||||
the right side)"""
|
the right side)"""
|
||||||
|
|
||||||
|
if blockID in (44,): # step block
|
||||||
|
# make the top half transparent
|
||||||
|
# (don't just crop img, since we want the size of
|
||||||
|
# img to be unchanged
|
||||||
|
mask = img.crop((0,8,16,16))
|
||||||
|
n = Image.new(img.mode, img.size, (38,92,255,0))
|
||||||
|
n.paste(mask,(0,0,16,8), mask)
|
||||||
|
img = n
|
||||||
|
if blockID in (78,): # snow
|
||||||
|
# make the top three quarters transparent
|
||||||
|
mask = img.crop((0,12,16,16))
|
||||||
|
n = Image.new(img.mode, img.size, (38,92,255,0))
|
||||||
|
n.paste(mask,(0,12,16,16), mask)
|
||||||
|
img = n
|
||||||
|
|
||||||
# Size of the cube side before shear
|
# Size of the cube side before shear
|
||||||
img = img.resize((12,12))
|
img = img.resize((12,12))
|
||||||
|
|
||||||
@@ -151,20 +179,20 @@ def transform_image_side(img):
|
|||||||
return newimg
|
return newimg
|
||||||
|
|
||||||
|
|
||||||
def _build_block(top, side, texID=None):
|
def _build_block(top, side, blockID=None):
|
||||||
"""From a top texture and a side texture, build a block image.
|
"""From a top texture and a side texture, build a block image.
|
||||||
top and side should be 16x16 image objects. Returns a 24x24 image
|
top and side should be 16x16 image objects. Returns a 24x24 image
|
||||||
|
|
||||||
"""
|
"""
|
||||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||||
|
|
||||||
top = transform_image(top)
|
top = transform_image(top, blockID)
|
||||||
|
|
||||||
if not side:
|
if not side:
|
||||||
img.paste(top, (0,0), top)
|
img.paste(top, (0,0), top)
|
||||||
return img
|
return img
|
||||||
|
|
||||||
side = transform_image_side(side)
|
side = transform_image_side(side, blockID)
|
||||||
|
|
||||||
otherside = side.transpose(Image.FLIP_LEFT_RIGHT)
|
otherside = side.transpose(Image.FLIP_LEFT_RIGHT)
|
||||||
|
|
||||||
@@ -179,16 +207,34 @@ def _build_block(top, side, texID=None):
|
|||||||
otherside.putalpha(othersidealpha)
|
otherside.putalpha(othersidealpha)
|
||||||
|
|
||||||
## special case for non-block things
|
## special case for non-block things
|
||||||
if texID in (12,13,15,28,29,80,73): ## flowers, sapling, mushrooms, regular torch, reeds
|
# 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
|
||||||
# instead of pasting these blocks at the cube edges, place them in the middle:
|
# instead of pasting these blocks at the cube edges, place them in the middle:
|
||||||
# and omit the top
|
# and omit the top
|
||||||
img.paste(side, (6,3), side)
|
img.paste(side, (6,3), side)
|
||||||
img.paste(otherside, (6,3), otherside)
|
img.paste(otherside, (6,3), otherside)
|
||||||
return img
|
return img
|
||||||
|
|
||||||
img.paste(side, (0,6), side)
|
|
||||||
img.paste(otherside, (12,6), otherside)
|
if blockID in (81,): # cacti!
|
||||||
img.paste(top, (0,0), top)
|
img.paste(side, (2,6), side)
|
||||||
|
img.paste(otherside, (10,6), otherside)
|
||||||
|
img.paste(top, (0,2), top)
|
||||||
|
elif blockID in (44,): # half step
|
||||||
|
# shift each texture down 6 pixels
|
||||||
|
img.paste(side, (0,12), side)
|
||||||
|
img.paste(otherside, (12,12), otherside)
|
||||||
|
img.paste(top, (0,6), top)
|
||||||
|
elif blockID in (78,): # snow
|
||||||
|
# shift each texture down 9 pixels
|
||||||
|
img.paste(side, (0,6), side)
|
||||||
|
img.paste(otherside, (12,6), otherside)
|
||||||
|
img.paste(top, (0,9), top)
|
||||||
|
else:
|
||||||
|
img.paste(side, (0,6), side)
|
||||||
|
img.paste(otherside, (12,6), otherside)
|
||||||
|
img.paste(top, (0,0), top)
|
||||||
|
|
||||||
# Manually touch up 6 pixels that leave a gap because of how the
|
# Manually touch up 6 pixels that leave a gap because of how the
|
||||||
# shearing works out. This makes the blocks perfectly tessellate-able
|
# shearing works out. This makes the blocks perfectly tessellate-able
|
||||||
@@ -218,7 +264,7 @@ def _build_blockimages():
|
|||||||
# 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
# 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
||||||
-1, -1, -1, 64, 64, 13, 12, 29, 28, 23, 22, 6, 6, 7, 8, 35, # Gold/iron blocks? Doublestep? TNT from above?
|
-1, -1, -1, 64, 64, 13, 12, 29, 28, 23, 22, 6, 6, 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
|
# 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, # Torch from above? leaving out fire. Redstone wire? Crops left out. sign post
|
36, 37, 80, -1, 65, 4, 25,101, 98, 24, 43, -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
|
# 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, -1, -1, 1, 66, 67, # door,ladder left out. Minecart rail orientation
|
||||||
# 80 81 82 83 84
|
# 80 81 82 83 84
|
||||||
@@ -233,9 +279,9 @@ def _build_blockimages():
|
|||||||
# 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
# 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
|
||||||
34, 20, 52, 48, 49, -1, -1, -1, -1, -1, -1, -1,- 1, -1, -1, -1,
|
34, 20, 52, 48, 49, -1, -1, -1, -1, -1, -1, -1,- 1, -1, -1, -1,
|
||||||
# 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
# 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
|
||||||
-1, -1, -1, 64, 64, 13, 12, 29, 28, 23, 22, 6, 6, 7, 8, 35,
|
-1, -1, -1, 64, 64, 13, 12, 29, 28, 23, 22, 5, 5, 7, 8, 35,
|
||||||
# 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
|
# 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, 43, -1, 86, 44, 61, -1,
|
||||||
# 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
|
# 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, -1, -1, 1, 66, 67,
|
||||||
# 80 81 82 83 84
|
# 80 81 82 83 84
|
||||||
@@ -243,8 +289,11 @@ def _build_blockimages():
|
|||||||
]
|
]
|
||||||
|
|
||||||
# This maps block id to the texture that goes on the side of the block
|
# This maps block id to the texture that goes on the side of the block
|
||||||
|
if len(topids) != len(sideids):
|
||||||
|
raise Exception("mismatched lengths")
|
||||||
|
|
||||||
allimages = []
|
allimages = []
|
||||||
for toptextureid, sidetextureid in zip(topids, sideids):
|
for toptextureid, sidetextureid,blockID in zip(topids, sideids,range(len(topids))):
|
||||||
if toptextureid == -1 or sidetextureid == -1:
|
if toptextureid == -1 or sidetextureid == -1:
|
||||||
allimages.append(None)
|
allimages.append(None)
|
||||||
continue
|
continue
|
||||||
@@ -252,7 +301,9 @@ def _build_blockimages():
|
|||||||
toptexture = terrain_images[toptextureid]
|
toptexture = terrain_images[toptextureid]
|
||||||
sidetexture = terrain_images[sidetextureid]
|
sidetexture = terrain_images[sidetextureid]
|
||||||
|
|
||||||
img = _build_block(toptexture, sidetexture, toptextureid)
|
## _build_block needs to know about the block ID, not just the textures
|
||||||
|
## of the block or the texture ID
|
||||||
|
img = _build_block(toptexture, sidetexture, blockID)
|
||||||
|
|
||||||
allimages.append((img.convert("RGB"), img.split()[3]))
|
allimages.append((img.convert("RGB"), img.split()[3]))
|
||||||
|
|
||||||
@@ -283,3 +334,176 @@ def load_water():
|
|||||||
blockmap[10] = lavablock.convert("RGB"), lavablock
|
blockmap[10] = lavablock.convert("RGB"), lavablock
|
||||||
blockmap[11] = blockmap[10]
|
blockmap[11] = blockmap[10]
|
||||||
load_water()
|
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)
|
||||||
|
else:
|
||||||
|
# TODO render carts that slop up or down
|
||||||
|
track = _transform_image(raw_straight, blockID)
|
||||||
|
|
||||||
|
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||||
|
img.paste(track, (0,12), track)
|
||||||
|
|
||||||
|
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))
|
||||||
|
img.paste(crop1, (0,12), crop1)
|
||||||
|
img.paste(crop2, (6,3), crop2)
|
||||||
|
img.paste(crop3, (6,3), crop3)
|
||||||
|
return (img.convert("RGB"), img.split()[3])
|
||||||
|
|
||||||
|
if blockID == 61: #furnace
|
||||||
|
top = _transform_image(terrain_images[1])
|
||||||
|
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))
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
if blockID == 62: # lit furnace
|
||||||
|
top = _transform_image(terrain_images[1])
|
||||||
|
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))
|
||||||
|
|
||||||
|
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])
|
||||||
|
|
||||||
|
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))
|
||||||
|
img.paste(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))
|
||||||
|
img.paste(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))
|
||||||
|
img.paste(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))
|
||||||
|
img.paste(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)
|
||||||
|
img.paste(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)
|
||||||
|
img.paste(tex, (0,0), tex)
|
||||||
|
|
||||||
|
if (data & 0x03) == 1:
|
||||||
|
if not swung:
|
||||||
|
tex = _transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT)
|
||||||
|
img.paste(tex, (0,0), tex)
|
||||||
|
else:
|
||||||
|
tex = _transform_image_side(raw_door)
|
||||||
|
img.paste(tex, (12,0), tex)
|
||||||
|
|
||||||
|
if (data & 0x03) == 2:
|
||||||
|
if not swung:
|
||||||
|
tex = _transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT))
|
||||||
|
img.paste(tex, (12,0), tex)
|
||||||
|
else:
|
||||||
|
tex = _transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT)
|
||||||
|
img.paste(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)
|
||||||
|
img.paste(tex, (12,6), tex)
|
||||||
|
else:
|
||||||
|
tex = _transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT))
|
||||||
|
img.paste(tex, (0,6), tex)
|
||||||
|
|
||||||
|
return (img.convert("RGB"), img.split()[3])
|
||||||
|
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# 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])
|
||||||
|
|
||||||
|
# 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] = (0,) # furnace
|
||||||
|
special_map[62] = (0,) # burning furnace
|
||||||
|
special_map[65] = (2,3,4,5) # ladder
|
||||||
|
special_map[64] = range(16) # wooden door
|
||||||
|
special_map[71] = range(16) # iron door
|
||||||
|
|
||||||
|
specialblockmap = {}
|
||||||
|
|
||||||
|
for blockID in special_blocks:
|
||||||
|
for data in special_map[blockID]:
|
||||||
|
specialblockmap[(blockID, data)] = generate_special_texture(blockID, data)
|
||||||
|
|||||||
29
util.py
Normal file
29
util.py
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Misc utility routines used by multiple files that don't belong anywhere else
|
||||||
|
"""
|
||||||
|
|
||||||
|
import imp
|
||||||
|
import os
|
||||||
|
import os.path
|
||||||
|
import sys
|
||||||
|
|
||||||
|
def get_program_path():
|
||||||
|
if hasattr(sys, "frozen") or imp.is_frozen("__main__"):
|
||||||
|
return os.path.dirname(sys.executable)
|
||||||
|
else:
|
||||||
|
return os.path.dirname(sys.argv[0])
|
||||||
19
world.py
19
world.py
@@ -18,9 +18,9 @@ import os
|
|||||||
import os.path
|
import os.path
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import sys
|
import sys
|
||||||
|
import logging
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from PIL import Image
|
|
||||||
|
|
||||||
import chunk
|
import chunk
|
||||||
import nbt
|
import nbt
|
||||||
@@ -180,8 +180,9 @@ class WorldRenderer(object):
|
|||||||
def go(self, procs):
|
def go(self, procs):
|
||||||
"""Starts the render. This returns when it is finished"""
|
"""Starts the render. This returns when it is finished"""
|
||||||
|
|
||||||
print "Scanning chunks"
|
logging.info("Scanning chunks")
|
||||||
raw_chunks = self._find_chunkfiles()
|
raw_chunks = self._find_chunkfiles()
|
||||||
|
logging.debug("Done scanning chunks")
|
||||||
|
|
||||||
# Translate chunks to our diagonal coordinate system
|
# Translate chunks to our diagonal coordinate system
|
||||||
mincol, maxcol, minrow, maxrow, chunks = _convert_coords(raw_chunks)
|
mincol, maxcol, minrow, maxrow, chunks = _convert_coords(raw_chunks)
|
||||||
@@ -215,9 +216,11 @@ class WorldRenderer(object):
|
|||||||
p = f.split(".")
|
p = f.split(".")
|
||||||
all_chunks.append((base36decode(p[1]), base36decode(p[2]),
|
all_chunks.append((base36decode(p[1]), base36decode(p[2]),
|
||||||
os.path.join(dirpath, f)))
|
os.path.join(dirpath, f)))
|
||||||
|
logging.debug((base36decode(p[1]), base36decode(p[2]),
|
||||||
|
os.path.join(dirpath, f)))
|
||||||
|
|
||||||
if not all_chunks:
|
if not all_chunks:
|
||||||
print "Error: No chunks found!"
|
logging.error("Error: No chunks found!")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return all_chunks
|
return all_chunks
|
||||||
|
|
||||||
@@ -238,7 +241,7 @@ class WorldRenderer(object):
|
|||||||
results = {}
|
results = {}
|
||||||
if processes == 1:
|
if processes == 1:
|
||||||
# Skip the multiprocessing stuff
|
# Skip the multiprocessing stuff
|
||||||
print "Rendering chunks synchronously since you requested 1 process"
|
logging.debug("Rendering chunks synchronously since you requested 1 process")
|
||||||
for i, (col, row, chunkfile) in enumerate(chunks):
|
for i, (col, row, chunkfile) in enumerate(chunks):
|
||||||
if inclusion_set and (col, row) not in inclusion_set:
|
if inclusion_set and (col, row) not in inclusion_set:
|
||||||
# Skip rendering, just find where the existing image is
|
# Skip rendering, just find where the existing image is
|
||||||
@@ -252,9 +255,9 @@ class WorldRenderer(object):
|
|||||||
results[(col, row)] = result
|
results[(col, row)] = result
|
||||||
if i > 0:
|
if i > 0:
|
||||||
if 1000 % i == 0 or i % 1000 == 0:
|
if 1000 % i == 0 or i % 1000 == 0:
|
||||||
print "{0}/{1} chunks rendered".format(i, len(chunks))
|
logging.info("{0}/{1} chunks rendered".format(i, len(chunks)))
|
||||||
else:
|
else:
|
||||||
print "Rendering chunks in {0} processes".format(processes)
|
logging.debug("Rendering chunks in {0} processes".format(processes))
|
||||||
pool = multiprocessing.Pool(processes=processes)
|
pool = multiprocessing.Pool(processes=processes)
|
||||||
asyncresults = []
|
asyncresults = []
|
||||||
for col, row, chunkfile in chunks:
|
for col, row, chunkfile in chunks:
|
||||||
@@ -277,10 +280,10 @@ class WorldRenderer(object):
|
|||||||
results[(col, row)] = result.get()
|
results[(col, row)] = result.get()
|
||||||
if i > 0:
|
if i > 0:
|
||||||
if 1000 % i == 0 or i % 1000 == 0:
|
if 1000 % i == 0 or i % 1000 == 0:
|
||||||
print "{0}/{1} chunks rendered".format(i, len(asyncresults))
|
logging.info("{0}/{1} chunks rendered".format(i, len(asyncresults)))
|
||||||
|
|
||||||
pool.join()
|
pool.join()
|
||||||
print "Done!"
|
logging.info("Done!")
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user