big commits to a bunch of stuff. See expanded message
Added an option to enter your own zoom level. Use -z to set the map at a particular zoom level. Zoom levels define the width and height in tiles of the highest zoom level, each new zoom level is twice as wide and tall. (z=6 -> 2^6 tiles wide and tall) Implemented tile re-arrangement on map expansion. Now most tiles will get re-used if your map needs another zoom level! No longer does it need to re-generate everything. No longer creates empty directories for tiles, only creates directories if needed. Fixed some minor off-by-one logic (and the code that canceled it out to make it work)
This commit is contained in:
3
gmap.py
3
gmap.py
@@ -21,6 +21,7 @@ def main():
|
||||
cpus = 1
|
||||
parser = OptionParser(usage=helptext)
|
||||
parser.add_option("-p", "--processes", dest="procs", help="How many chunks to render in parallel. A good number for this is the number of cores in your computer. Default %s" % cpus, default=cpus, action="store", type="int")
|
||||
parser.add_option("-z", "--zoom", dest="zoom", help="Sets the zoom level manually instead of calculating it. This can be useful if you have outlier chunks that make your world too big. This value will make the highest zoom level contain (2**ZOOM)^2 tiles", action="store", type="int")
|
||||
parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true")
|
||||
|
||||
options, args = parser.parse_args()
|
||||
@@ -43,7 +44,7 @@ def main():
|
||||
w.go(options.procs)
|
||||
|
||||
# Now generate the tiles
|
||||
q = quadtree.QuadtreeGen(w, destdir)
|
||||
q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom)
|
||||
q.go(options.procs)
|
||||
|
||||
def delete_all(worlddir, tiledir):
|
||||
|
||||
142
quadtree.py
142
quadtree.py
@@ -4,6 +4,8 @@ import os
|
||||
import os.path
|
||||
import hashlib
|
||||
import functools
|
||||
import re
|
||||
import shutil
|
||||
|
||||
from PIL import Image
|
||||
|
||||
@@ -33,30 +35,44 @@ def catch_keyboardinterrupt(func):
|
||||
return newfunc
|
||||
|
||||
class QuadtreeGen(object):
|
||||
def __init__(self, worldobj, destdir):
|
||||
def __init__(self, worldobj, destdir, depth=None):
|
||||
"""Generates a quadtree from the world given into the
|
||||
given dest directory
|
||||
|
||||
worldobj is a world.WorldRenderer object that has already been processed
|
||||
|
||||
"""
|
||||
# Determine quadtree depth (midpoint is always 0,0)
|
||||
for p in xrange(15):
|
||||
xdiameter = 2*2**p
|
||||
ydiameter = 4*2**p
|
||||
if xdiameter >= worldobj.maxcol and -xdiameter <= worldobj.mincol and \
|
||||
ydiameter >= worldobj.maxrow and -ydiameter <= worldobj.minrow:
|
||||
break
|
||||
else:
|
||||
raise ValueError("Your map is waaaay to big!")
|
||||
If depth is given, it overrides the calculated value. Otherwise, the
|
||||
minimum depth that contains all chunks is calculated and used.
|
||||
|
||||
self.p = p
|
||||
"""
|
||||
if depth is None:
|
||||
# Determine quadtree depth (midpoint is always 0,0)
|
||||
for p in xrange(15):
|
||||
# Will 2^p tiles wide and high suffice?
|
||||
|
||||
# X has twice as many chunks as tiles, then halved since this is a
|
||||
# radius
|
||||
xradius = 2**p
|
||||
# Y has 4 times as many chunks as tiles, then halved since this is
|
||||
# a radius
|
||||
yradius = 2*2**p
|
||||
if xradius >= worldobj.maxcol and -xradius <= worldobj.mincol and \
|
||||
yradius >= worldobj.maxrow and -yradius <= worldobj.minrow:
|
||||
break
|
||||
else:
|
||||
raise ValueError("Your map is waaaay too big!")
|
||||
|
||||
self.p = p
|
||||
else:
|
||||
self.p = depth
|
||||
xradius = 2**depth
|
||||
yradius = 2*2**depth
|
||||
|
||||
# Make new row and column ranges
|
||||
self.mincol = -xdiameter
|
||||
self.maxcol = xdiameter
|
||||
self.minrow = -ydiameter
|
||||
self.maxrow = ydiameter
|
||||
self.mincol = -xradius
|
||||
self.maxcol = xradius
|
||||
self.minrow = -yradius
|
||||
self.maxrow = yradius
|
||||
|
||||
self.world = worldobj
|
||||
self.destdir = destdir
|
||||
@@ -72,14 +88,22 @@ class QuadtreeGen(object):
|
||||
output.write(html)
|
||||
|
||||
def _get_cur_depth(self):
|
||||
"""How deep is the quadtree currently in the destdir? This assumes all
|
||||
the directories are created, even if they don't have any images
|
||||
"""How deep is the quadtree currently in the destdir? This glances in
|
||||
index.html to see what maxZoom is set to.
|
||||
returns -1 if it couldn't be detected, file not found, or nothing in
|
||||
index.html matched
|
||||
"""
|
||||
p = 0
|
||||
curdir = os.path.join(self.destdir, "tiles")
|
||||
while "0" in os.listdir(curdir):
|
||||
curdir = os.path.join(curdir, "0")
|
||||
p += 1
|
||||
indexfile = os.path.join(self.destdir, "index.html")
|
||||
if not os.path.exists(indexfile):
|
||||
return -1
|
||||
matcher = re.compile(r"maxZoom:\s*(\d+)")
|
||||
p = -1
|
||||
for line in open(indexfile, "r"):
|
||||
res = matcher.search(line)
|
||||
if res:
|
||||
p = int(res.group(1))
|
||||
break
|
||||
print "detected previous zoom level:", p
|
||||
return p
|
||||
|
||||
def _increase_depth(self):
|
||||
@@ -108,16 +132,46 @@ class QuadtreeGen(object):
|
||||
os.rename(p, getpath(newdir, newf))
|
||||
os.rename(newdirpath, getpath(str(dirnum)))
|
||||
|
||||
def _decrease_depth(self):
|
||||
"""If the map size decreases, or perhaps the user has a depth override
|
||||
in effect, re-arrange existing tiles for a smaller tree"""
|
||||
getpath = functools.partial(os.path.join, self.destdir, "tiles")
|
||||
|
||||
# quadrant 0/3 goes to 0
|
||||
# 1/2 goes to 1
|
||||
# 2/1 goes to 2
|
||||
# 3/0 goes to 3
|
||||
# Just worry about the directories here, the files at the top two
|
||||
# levels are cheap enough to replace
|
||||
os.rename(getpath("0", "3"), getpath("new0"))
|
||||
os.rename(getpath("1", "2"), getpath("new1"))
|
||||
os.rename(getpath("2", "1"), getpath("new2"))
|
||||
os.rename(getpath("3", "0"), getpath("new3"))
|
||||
|
||||
shutil.rmtree(getpath("0"))
|
||||
shutil.rmtree(getpath("1"))
|
||||
shutil.rmtree(getpath("2"))
|
||||
shutil.rmtree(getpath("3"))
|
||||
|
||||
os.rename(getpath("new0"), getpath("0"))
|
||||
os.rename(getpath("new1"), getpath("1"))
|
||||
os.rename(getpath("new2"), getpath("2"))
|
||||
os.rename(getpath("new3"), getpath("3"))
|
||||
|
||||
def go(self, procs):
|
||||
"""Renders all tiles"""
|
||||
|
||||
curdepth = self._get_cur_depth()
|
||||
if self.p > curdepth:
|
||||
print "Your map seemes to have expanded beyond its previous bounds."
|
||||
print "Doing some tile re-arrangements... just a sec..."
|
||||
for _ in xrange(self.p-curdepth):
|
||||
print "Increasing depth..."
|
||||
self._increase_depth()
|
||||
if curdepth != -1:
|
||||
if self.p > curdepth:
|
||||
print "Your map seemes to have expanded beyond its previous bounds."
|
||||
print "Doing some tile re-arrangements... just a sec..."
|
||||
for _ in xrange(self.p-curdepth):
|
||||
self._increase_depth()
|
||||
elif self.p < curdepth:
|
||||
print "Your map seems to have shrunk. Re-arranging tiles, just a sec..."
|
||||
for _ in xrange(curdepth - self.p):
|
||||
self._decrease_depth()
|
||||
|
||||
# Create a pool
|
||||
pool = multiprocessing.Pool(processes=procs)
|
||||
@@ -126,7 +180,7 @@ class QuadtreeGen(object):
|
||||
print "Computing the tile ranges and starting tile processers for inner-most tiles..."
|
||||
print "This takes the longest. The other levels will go quicker"
|
||||
results = []
|
||||
for path in iterate_base4(self.p+1):
|
||||
for path in iterate_base4(self.p):
|
||||
# Get the range for this tile
|
||||
colstart, rowstart = self._get_range_by_path(path)
|
||||
colend = colstart + 2
|
||||
@@ -135,11 +189,6 @@ class QuadtreeGen(object):
|
||||
# This image is rendered at:
|
||||
dest = os.path.join(self.destdir, "tiles", *(str(x) for x in path))
|
||||
|
||||
# The directory, create it if not exists
|
||||
dirdest = os.path.dirname(dest)
|
||||
if not os.path.exists(dirdest):
|
||||
os.makedirs(dirdest)
|
||||
|
||||
# And uses these chunks
|
||||
tilechunks = self._get_chunks_in_range(colstart, colend, rowstart,
|
||||
rowend)
|
||||
@@ -153,7 +202,7 @@ class QuadtreeGen(object):
|
||||
)
|
||||
)
|
||||
|
||||
self.write_html(self.p+1)
|
||||
self.write_html(self.p)
|
||||
|
||||
# Wait for all results to finish
|
||||
print "Rendering inner most zoom level tiles now!"
|
||||
@@ -161,15 +210,14 @@ class QuadtreeGen(object):
|
||||
# get() instead of wait() so we can see errors
|
||||
result.get()
|
||||
if i > 0 and (i % 100 == 0 or 100 % i == 0):
|
||||
print "{0}/{1} tiles complete on level {2}/{3}".format(
|
||||
i, len(results), 1, self.p+1)
|
||||
print "{0}/{1} tiles complete on level 1/{2}".format(
|
||||
i, len(results), self.p)
|
||||
print "Done"
|
||||
|
||||
# Now do the other layers
|
||||
for zoom in xrange(self.p, 0, -1):
|
||||
level = self.p+2-zoom
|
||||
print "Preparing level", level
|
||||
|
||||
for zoom in xrange(self.p-1, 0, -1):
|
||||
level = self.p - zoom + 1
|
||||
print "Starting level", level
|
||||
results = []
|
||||
for path in iterate_base4(zoom):
|
||||
# This image is rendered at:
|
||||
@@ -182,13 +230,12 @@ class QuadtreeGen(object):
|
||||
)
|
||||
)
|
||||
|
||||
print "Rendering level {0}/{1} now!".format(level, self.p+1)
|
||||
for i, result in enumerate(results):
|
||||
# get() instead of wait() so we can see errors
|
||||
result.get()
|
||||
if i > 0 and (i % 100 == 0 or 100 % i == 0):
|
||||
print "{0}/{1} tiles complete on level {2}/{3}".format(
|
||||
i, len(results), level, self.p+1)
|
||||
print "{0}/{1} tiles complete for level {2}/{3}".format(
|
||||
i, len(results), level, self.p)
|
||||
print "Done"
|
||||
|
||||
# Do the final one right here:
|
||||
@@ -378,6 +425,11 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path):
|
||||
os.unlink(hashpath)
|
||||
return None
|
||||
|
||||
# Create the directory if not exists
|
||||
dirdest = os.path.dirname(path)
|
||||
if not os.path.exists(dirdest):
|
||||
os.makedirs(dirdest)
|
||||
|
||||
imghash = hashlib.md5()
|
||||
for col, row, chunkfile in chunks:
|
||||
# Get the hash of this image and add it to our hash for this tile
|
||||
|
||||
Reference in New Issue
Block a user