Merge remote branch 'upstream/master'
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*.pyc
|
||||||
201
chunk.py
201
chunk.py
@@ -15,9 +15,9 @@
|
|||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
from PIL import Image, ImageDraw
|
from PIL import Image, ImageDraw
|
||||||
from itertools import izip, count
|
|
||||||
import os.path
|
import os.path
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import logging
|
||||||
|
|
||||||
import nbt
|
import nbt
|
||||||
import textures
|
import textures
|
||||||
@@ -60,9 +60,29 @@ def get_skylight_array(level):
|
|||||||
"""
|
"""
|
||||||
return numpy.frombuffer(level['SkyLight'], dtype=numpy.uint8).reshape((16,16,64))
|
return numpy.frombuffer(level['SkyLight'], dtype=numpy.uint8).reshape((16,16,64))
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
def iterate_chunkblocks(xoff,yoff):
|
||||||
|
"""Iterates over the 16x16x128 blocks of a chunk in rendering order.
|
||||||
|
Yields (x,y,z,imgx,imgy)
|
||||||
|
x,y,z is the block coordinate in the chunk
|
||||||
|
imgx,imgy is the image offset in the chunk image where that block should go
|
||||||
|
"""
|
||||||
|
for x in xrange(15,-1,-1):
|
||||||
|
for y in xrange(16):
|
||||||
|
imgx = xoff + x*12 + y*12
|
||||||
|
imgy = yoff - x*6 + y*6 + 128*12 + 16*12//2
|
||||||
|
for z in xrange(128):
|
||||||
|
yield x,y,z,imgx,imgy
|
||||||
|
imgy -= 12
|
||||||
|
|
||||||
|
|
||||||
# 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, 44, 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, 81, 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, cave=False):
|
def render_and_save(chunkfile, cachedir, cave=False):
|
||||||
"""Used as the entry point for the multiprocessing workers (since processes
|
"""Used as the entry point for the multiprocessing workers (since processes
|
||||||
@@ -72,6 +92,9 @@ def render_and_save(chunkfile, cachedir, cave=False):
|
|||||||
a = ChunkRenderer(chunkfile, cachedir)
|
a = ChunkRenderer(chunkfile, cachedir)
|
||||||
try:
|
try:
|
||||||
return a.render_and_save(cave)
|
return a.render_and_save(cave)
|
||||||
|
except ChunkCorrupt:
|
||||||
|
# This should be non-fatal, but should print a warning
|
||||||
|
pass
|
||||||
except Exception, e:
|
except Exception, e:
|
||||||
import traceback
|
import traceback
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@@ -85,6 +108,9 @@ def render_and_save(chunkfile, cachedir, cave=False):
|
|||||||
# forever for it to finish.
|
# forever for it to finish.
|
||||||
raise Exception()
|
raise Exception()
|
||||||
|
|
||||||
|
class ChunkCorrupt(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
class ChunkRenderer(object):
|
class ChunkRenderer(object):
|
||||||
def __init__(self, chunkfile, cachedir):
|
def __init__(self, chunkfile, cachedir):
|
||||||
"""Make a new chunk renderer for the given chunkfile.
|
"""Make a new chunk renderer for the given chunkfile.
|
||||||
@@ -115,7 +141,11 @@ class ChunkRenderer(object):
|
|||||||
def _load_level(self):
|
def _load_level(self):
|
||||||
"""Loads and returns the level structure"""
|
"""Loads and returns the level structure"""
|
||||||
if not hasattr(self, "_level"):
|
if not hasattr(self, "_level"):
|
||||||
self._level = get_lvldata(self.chunkfile)
|
try:
|
||||||
|
self._level = get_lvldata(self.chunkfile)
|
||||||
|
except Exception, e:
|
||||||
|
logging.warning("Error opening chunk file %s. It may be corrupt. %s", self.chunkfile, e)
|
||||||
|
raise ChunkCorrupt(str(e))
|
||||||
return self._level
|
return self._level
|
||||||
level = property(_load_level)
|
level = property(_load_level)
|
||||||
|
|
||||||
@@ -168,7 +198,7 @@ class ChunkRenderer(object):
|
|||||||
# An image exists? Instead of checking the hash which is kinda
|
# An image exists? Instead of checking the hash which is kinda
|
||||||
# expensive (for tens of thousands of chunks, yes it is) check if
|
# expensive (for tens of thousands of chunks, yes it is) check if
|
||||||
# the mtime of the chunk file is newer than the mtime of oldimg
|
# the mtime of the chunk file is newer than the mtime of oldimg
|
||||||
if os.path.getmtime(self.chunkfile) < os.path.getmtime(oldimg_path):
|
if os.path.getmtime(self.chunkfile) <= os.path.getmtime(oldimg_path):
|
||||||
# chunkfile is older than the image, don't even bother checking
|
# chunkfile is older than the image, don't even bother checking
|
||||||
# the hash
|
# the hash
|
||||||
return oldimg_path
|
return oldimg_path
|
||||||
@@ -193,6 +223,9 @@ class ChunkRenderer(object):
|
|||||||
if dest_filename == oldimg:
|
if dest_filename == oldimg:
|
||||||
# There is an existing file, the chunk has a newer mtime, but the
|
# There is an existing file, the chunk has a newer mtime, but the
|
||||||
# hashes match.
|
# hashes match.
|
||||||
|
# Before we return it, update its mtime so the next round
|
||||||
|
# doesn't have to check the hash
|
||||||
|
os.utime(dest_path, None)
|
||||||
return dest_path
|
return dest_path
|
||||||
else:
|
else:
|
||||||
# Remove old image for this chunk. Anything already existing is
|
# Remove old image for this chunk. Anything already existing is
|
||||||
@@ -238,6 +271,13 @@ class ChunkRenderer(object):
|
|||||||
blocks = blocks.copy()
|
blocks = blocks.copy()
|
||||||
blocks[skylight_expanded != 0] = 21
|
blocks[skylight_expanded != 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
|
||||||
@@ -249,81 +289,94 @@ class ChunkRenderer(object):
|
|||||||
if not img:
|
if not img:
|
||||||
img = Image.new("RGBA", (384, 1728), (38,92,255,0))
|
img = Image.new("RGBA", (384, 1728), (38,92,255,0))
|
||||||
|
|
||||||
for x in xrange(15,-1,-1):
|
for x,y,z,imgx,imgy in iterate_chunkblocks(xoff,yoff):
|
||||||
for y in xrange(16):
|
blockid = blocks[x,y,z]
|
||||||
imgx = xoff + x*12 + y*12
|
|
||||||
imgy = yoff - x*6 + y*6 + 128*12 + 16*12//2
|
|
||||||
for z in xrange(128):
|
|
||||||
try:
|
|
||||||
blockid = blocks[x,y,z]
|
|
||||||
t = textures.blockmap[blockid]
|
|
||||||
if not t:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Check if this block is occluded
|
# the following blocks don't have textures that can be pre-computed from the blockid
|
||||||
if cave and (
|
# alone. additional data is required.
|
||||||
x == 0 and y != 15 and z != 127
|
# TODO torches, redstone torches, crops, ladders, stairs,
|
||||||
):
|
# levers, doors, buttons, and signs all need to be handled here (and in textures.py)
|
||||||
# If it's on the x face, only render if there's a
|
|
||||||
# transparent block in the y+1 direction OR the z-1
|
|
||||||
# direction
|
|
||||||
if (
|
|
||||||
blocks[x,y+1,z] not in transparent_blocks and
|
|
||||||
blocks[x,y,z+1] not in transparent_blocks
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
elif cave and (
|
|
||||||
y == 15 and x != 0 and z != 127
|
|
||||||
):
|
|
||||||
# If it's on the facing y face, only render if there's
|
|
||||||
# a transparent block in the x-1 direction OR the z-1
|
|
||||||
# direction
|
|
||||||
if (
|
|
||||||
blocks[x-1,y,z] not in transparent_blocks and
|
|
||||||
blocks[x,y,z+1] not in transparent_blocks
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
elif cave and (
|
|
||||||
y == 15 and x == 0
|
|
||||||
):
|
|
||||||
# If it's on the facing edge, only render if what's
|
|
||||||
# above it is transparent
|
|
||||||
if (
|
|
||||||
blocks[x,y,z+1] not in transparent_blocks
|
|
||||||
):
|
|
||||||
continue
|
|
||||||
elif (
|
|
||||||
# Normal block or not cave mode, check sides for
|
|
||||||
# transparentcy or render unconditionally if it's
|
|
||||||
# on a shown face
|
|
||||||
x != 0 and y != 15 and z != 127 and
|
|
||||||
blocks[x-1,y,z] not in transparent_blocks and
|
|
||||||
blocks[x,y+1,z] not in transparent_blocks and
|
|
||||||
blocks[x,y,z+1] not in transparent_blocks
|
|
||||||
):
|
|
||||||
# Don't render if all sides aren't transparent and
|
|
||||||
# we're not on the edge
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Draw the actual block on the image. For cave images,
|
## minecart track, crops, ladder, doors, etc.
|
||||||
# tint the block with a color proportional to its depth
|
if blockid in textures.special_blocks:
|
||||||
if cave:
|
# also handle furnaces here, since one side has a different texture than the other
|
||||||
img.paste(Image.blend(t[0],depth_colors[z],0.3), (imgx, imgy), t[1])
|
ancilData = blockData_expanded[x,y,z]
|
||||||
else:
|
try:
|
||||||
img.paste(t[0], (imgx, imgy), t[1])
|
t = textures.specialblockmap[(blockid, ancilData)]
|
||||||
|
except KeyError:
|
||||||
|
t = None
|
||||||
|
|
||||||
# Draw edge lines
|
else:
|
||||||
if blockid not in transparent_blocks:
|
t = textures.blockmap[blockid]
|
||||||
draw = ImageDraw.Draw(img)
|
if not t:
|
||||||
if x != 15 and blocks[x+1,y,z] == 0:
|
continue
|
||||||
draw.line(((imgx+12,imgy), (imgx+22,imgy+5)), fill=(0,0,0), width=1)
|
|
||||||
if y != 0 and blocks[x,y-1,z] == 0:
|
|
||||||
draw.line(((imgx,imgy+6), (imgx+12,imgy)), fill=(0,0,0), width=1)
|
|
||||||
|
|
||||||
|
# Check if this block is occluded
|
||||||
|
if cave and (
|
||||||
|
x == 0 and y != 15 and z != 127
|
||||||
|
):
|
||||||
|
# If it's on the x face, only render if there's a
|
||||||
|
# transparent block in the y+1 direction OR the z-1
|
||||||
|
# direction
|
||||||
|
if (
|
||||||
|
blocks[x,y+1,z] not in transparent_blocks and
|
||||||
|
blocks[x,y,z+1] not in transparent_blocks
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
elif cave and (
|
||||||
|
y == 15 and x != 0 and z != 127
|
||||||
|
):
|
||||||
|
# If it's on the facing y face, only render if there's
|
||||||
|
# a transparent block in the x-1 direction OR the z-1
|
||||||
|
# direction
|
||||||
|
if (
|
||||||
|
blocks[x-1,y,z] not in transparent_blocks and
|
||||||
|
blocks[x,y,z+1] not in transparent_blocks
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
elif cave and (
|
||||||
|
y == 15 and x == 0
|
||||||
|
):
|
||||||
|
# If it's on the facing edge, only render if what's
|
||||||
|
# above it is transparent
|
||||||
|
if (
|
||||||
|
blocks[x,y,z+1] not in transparent_blocks
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
elif (
|
||||||
|
# Normal block or not cave mode, check sides for
|
||||||
|
# transparentcy or render unconditionally if it's
|
||||||
|
# on a shown face
|
||||||
|
x != 0 and y != 15 and z != 127 and
|
||||||
|
blocks[x-1,y,z] not in transparent_blocks and
|
||||||
|
blocks[x,y+1,z] not in transparent_blocks and
|
||||||
|
blocks[x,y,z+1] not in transparent_blocks
|
||||||
|
):
|
||||||
|
# Don't render if all sides aren't transparent and
|
||||||
|
# we're not on the edge
|
||||||
|
continue
|
||||||
|
|
||||||
finally:
|
# Draw the actual block on the image. For cave images,
|
||||||
# Do this no mater how the above block exits
|
# tint the block with a color proportional to its depth
|
||||||
imgy -= 12
|
if cave:
|
||||||
|
img.paste(Image.blend(t[0],depth_colors[z],0.3), (imgx, imgy), t[1])
|
||||||
|
else:
|
||||||
|
img.paste(t[0], (imgx, imgy), t[1])
|
||||||
|
|
||||||
|
# Draw edge lines
|
||||||
|
if blockid in (44,): # step block
|
||||||
|
increment = 6
|
||||||
|
elif blockid in (78,): # snow
|
||||||
|
increment = 9
|
||||||
|
else:
|
||||||
|
increment = 0
|
||||||
|
|
||||||
|
if blockid not in transparent_blocks or blockid in (78,): #special case snow so the outline is still drawn
|
||||||
|
draw = ImageDraw.Draw(img)
|
||||||
|
if x != 15 and blocks[x+1,y,z] == 0:
|
||||||
|
draw.line(((imgx+12,imgy+increment), (imgx+22,imgy+5+increment)), fill=(0,0,0), width=1)
|
||||||
|
if y != 0 and blocks[x,y-1,z] == 0:
|
||||||
|
draw.line(((imgx,imgy+6+increment), (imgx+12,imgy+increment)), fill=(0,0,0), width=1)
|
||||||
|
|
||||||
return img
|
return img
|
||||||
|
|
||||||
|
|||||||
27
gmap.py
27
gmap.py
@@ -26,6 +26,9 @@ 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
|
||||||
@@ -46,6 +49,10 @@ def main():
|
|||||||
parser.add_option("--cachedir", dest="cachedir", help="Sets the directory where the Overviewer will save chunk images, which is an intermediate step before the tiles are generated. You must use the same directory each time to gain any benefit from the cache. If not set, this defaults to your world directory.")
|
parser.add_option("--cachedir", dest="cachedir", help="Sets the directory where the Overviewer will save chunk images, which is an intermediate step before the tiles are generated. You must use the same directory each time to gain any benefit from the cache. If not set, this defaults to your world directory.")
|
||||||
parser.add_option("--chunklist", dest="chunklist", help="A file containing, on each line, a path to a chunkfile to update. Instead of scanning the world directory for chunks, it will just use this list. Normal caching rules still apply.")
|
parser.add_option("--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("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.")
|
parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.")
|
||||||
|
parser.add_option("--optimize-img", dest="optimizeimg", help="If using png, perform image file size optimizations on the output. Specify 1 for pngcrush, 2 for pngcrush+optipng+advdef. This may double (or more) render times, but will produce up to 30% smaller images. NOTE: requires corresponding programs in $PATH or %PATH%")
|
||||||
|
parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.")
|
||||||
|
parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, help="Print more output. You can specify this option multiple times.")
|
||||||
|
parser.add_option("--skip-js", dest="skipjs", action="store_true", help="Don't output marker.js or regions.js")
|
||||||
|
|
||||||
options, args = parser.parse_args()
|
options, args = parser.parse_args()
|
||||||
|
|
||||||
@@ -93,12 +100,26 @@ def main():
|
|||||||
else:
|
else:
|
||||||
imgformat = 'png'
|
imgformat = 'png'
|
||||||
|
|
||||||
|
if options.optimizeimg:
|
||||||
|
optimizeimg = options.optimizeimg
|
||||||
|
else:
|
||||||
|
optimizeimg = None
|
||||||
|
|
||||||
|
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)
|
w = world.WorldRenderer(worlddir, cachedir, chunklist=chunklist)
|
||||||
w.go(options.procs)
|
w.go(options.procs)
|
||||||
|
|
||||||
# Now generate the tiles
|
# Now generate the tiles
|
||||||
q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat)
|
q = quadtree.QuadtreeGen(w, destdir, depth=options.zoom, imgformat=imgformat, optimizeimg=optimizeimg)
|
||||||
|
q.write_html(options.skipjs)
|
||||||
q.go(options.procs)
|
q.go(options.procs)
|
||||||
|
|
||||||
def delete_all(worlddir, tiledir):
|
def delete_all(worlddir, tiledir):
|
||||||
@@ -110,7 +131,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.
|
||||||
@@ -119,7 +140,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():
|
||||||
|
|||||||
36
optimizeimages.py
Normal file
36
optimizeimages.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# 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/>.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import shlex
|
||||||
|
|
||||||
|
def optimize_image(imgpath, imgformat, optimizeimg):
|
||||||
|
if imgformat == 'png':
|
||||||
|
if optimizeimg == "1" or optimizeimg == "2":
|
||||||
|
# we can't do an atomic replace here because windows is terrible
|
||||||
|
# so instead, we make temp files, delete the old ones, and rename
|
||||||
|
# the temp files. go windows!
|
||||||
|
subprocess.Popen(shlex.split("pngcrush " + imgpath + " " + imgpath + ".tmp"),
|
||||||
|
stderr=subprocess.STDOUT, stdout=subprocess.PIPE).communicate()[0]
|
||||||
|
os.remove(imgpath)
|
||||||
|
os.rename(imgpath+".tmp", imgpath)
|
||||||
|
|
||||||
|
if optimizeimg == "2":
|
||||||
|
subprocess.Popen(shlex.split("optipng " + imgpath), stderr=subprocess.STDOUT,
|
||||||
|
stdout=subprocess.PIPE).communicate()[0]
|
||||||
|
subprocess.Popen(shlex.split("advdef -z4 " + imgpath), stderr=subprocess.STDOUT,
|
||||||
|
stdout=subprocess.PIPE).communicate()[0]
|
||||||
|
|
||||||
123
quadtree.py
123
quadtree.py
@@ -23,10 +23,13 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
import collections
|
import collections
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
import util
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
import util
|
from optimizeimages import optimize_image
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
This module has routines related to generating a quadtree of tiles
|
This module has routines related to generating a quadtree of tiles
|
||||||
@@ -45,7 +48,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
|
||||||
@@ -54,7 +57,7 @@ def catch_keyboardinterrupt(func):
|
|||||||
return newfunc
|
return newfunc
|
||||||
|
|
||||||
class QuadtreeGen(object):
|
class QuadtreeGen(object):
|
||||||
def __init__(self, worldobj, destdir, depth=None, imgformat=None):
|
def __init__(self, worldobj, destdir, depth=None, imgformat=None, optimizeimg=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
|
||||||
|
|
||||||
@@ -66,6 +69,7 @@ class QuadtreeGen(object):
|
|||||||
"""
|
"""
|
||||||
assert(imgformat)
|
assert(imgformat)
|
||||||
self.imgformat = imgformat
|
self.imgformat = imgformat
|
||||||
|
self.optimizeimg = optimizeimg
|
||||||
|
|
||||||
if depth is None:
|
if depth is None:
|
||||||
# Determine quadtree depth (midpoint is always 0,0)
|
# Determine quadtree depth (midpoint is always 0,0)
|
||||||
@@ -111,11 +115,13 @@ 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, imgformat):
|
def write_html(self, skipjs=False):
|
||||||
"""Writes out index.html"""
|
"""Writes out index.html, marker.js, and region.js"""
|
||||||
|
zoomlevel = self.p
|
||||||
|
imgformat = self.imgformat
|
||||||
templatepath = os.path.join(util.get_program_path(), "template.html")
|
templatepath = os.path.join(util.get_program_path(), "template.html")
|
||||||
|
|
||||||
html = open(templatepath, 'r').read()
|
html = open(templatepath, 'r').read()
|
||||||
@@ -127,17 +133,29 @@ class QuadtreeGen(object):
|
|||||||
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)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
with open(os.path.join(self.destdir, "markers.js"), 'w') as output:
|
|
||||||
output.write("var markerData=%s" % json.dumps(self.world.POI))
|
|
||||||
|
|
||||||
# Write a blank image
|
# Write a blank image
|
||||||
blank = Image.new("RGBA", (1,1))
|
blank = Image.new("RGBA", (1,1))
|
||||||
tileDir = os.path.join(self.destdir, "tiles")
|
tileDir = os.path.join(self.destdir, "tiles")
|
||||||
if not os.path.exists(tileDir): os.mkdir(tileDir)
|
if not os.path.exists(tileDir): os.mkdir(tileDir)
|
||||||
blank.save(os.path.join(tileDir, "blank."+self.imgformat))
|
blank.save(os.path.join(tileDir, "blank."+self.imgformat))
|
||||||
|
|
||||||
|
if skipjs:
|
||||||
|
return
|
||||||
|
|
||||||
|
# write out the default marker table
|
||||||
|
with open(os.path.join(self.destdir, "markers.js"), 'w') as output:
|
||||||
|
output.write("var markerData=%s" % json.dumps(self.world.POI))
|
||||||
|
|
||||||
|
# write out the default (empty, but documented) region table
|
||||||
|
with open(os.path.join(self.destdir, "regions.js"), 'w') as output:
|
||||||
|
output.write('var regionData=[\n')
|
||||||
|
output.write(' // {"color": "#FFAA00", "opacity": 0.5, "closed": true, "path": [\n')
|
||||||
|
output.write(' // {"x": 0, "y": 0, "z": 0},\n')
|
||||||
|
output.write(' // {"x": 0, "y": 10, "z": 0},\n')
|
||||||
|
output.write(' // {"x": 0, "y": 0, "z": 10}\n')
|
||||||
|
output.write(' // ]},\n')
|
||||||
|
output.write('];')
|
||||||
|
|
||||||
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.
|
||||||
@@ -172,8 +190,8 @@ class QuadtreeGen(object):
|
|||||||
newdir = "new" + str(dirnum)
|
newdir = "new" + str(dirnum)
|
||||||
newdirpath = getpath(newdir)
|
newdirpath = getpath(newdir)
|
||||||
|
|
||||||
files = [str(dirnum)+"."+imgformat, str(dirnum)+".hash", str(dirnum)]
|
files = [str(dirnum)+"."+self.imgformat, str(dirnum)+".hash", str(dirnum)]
|
||||||
newfiles = [str(newnum)+"."+imgformat, 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):
|
||||||
@@ -234,7 +252,8 @@ 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, self.imgformat))
|
colstart, colend, rowstart, rowend, dest, self.imgformat,
|
||||||
|
self.optimizeimg))
|
||||||
|
|
||||||
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.
|
||||||
@@ -246,7 +265,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, self.imgformat))
|
yield pool.apply_async(func=render_innertile, args= (dest, name, self.imgformat, self.optimizeimg))
|
||||||
|
|
||||||
def go(self, procs):
|
def go(self, procs):
|
||||||
"""Renders all tiles"""
|
"""Renders all tiles"""
|
||||||
@@ -258,12 +277,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()
|
||||||
|
|
||||||
@@ -273,17 +292,15 @@ class QuadtreeGen(object):
|
|||||||
else:
|
else:
|
||||||
pool = multiprocessing.Pool(processes=procs)
|
pool = multiprocessing.Pool(processes=procs)
|
||||||
|
|
||||||
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:
|
||||||
@@ -308,7 +325,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:
|
||||||
@@ -324,13 +341,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", self.imgformat)
|
render_innertile(os.path.join(self.destdir, "tiles"), "base", self.imgformat, self.optimizeimg)
|
||||||
|
|
||||||
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"""
|
||||||
@@ -361,7 +378,7 @@ class QuadtreeGen(object):
|
|||||||
return chunklist
|
return chunklist
|
||||||
|
|
||||||
@catch_keyboardinterrupt
|
@catch_keyboardinterrupt
|
||||||
def render_innertile(dest, name, imgformat):
|
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
|
||||||
os.path.join(dest, name, "{0,1,2,3}.png")
|
os.path.join(dest, name, "{0,1,2,3}.png")
|
||||||
@@ -434,29 +451,44 @@ def render_innertile(dest, name, imgformat):
|
|||||||
img = Image.new("RGBA", (384, 384), (38,92,255,0))
|
img = Image.new("RGBA", (384, 384), (38,92,255,0))
|
||||||
|
|
||||||
if q0path:
|
if q0path:
|
||||||
quad0 = Image.open(q0path).resize((192,192), Image.ANTIALIAS)
|
try:
|
||||||
img.paste(quad0, (0,0))
|
quad0 = Image.open(q0path).resize((192,192), Image.ANTIALIAS)
|
||||||
|
img.paste(quad0, (0,0))
|
||||||
|
except Exception, e:
|
||||||
|
logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", q0path, e)
|
||||||
if q1path:
|
if q1path:
|
||||||
quad1 = Image.open(q1path).resize((192,192), Image.ANTIALIAS)
|
try:
|
||||||
img.paste(quad1, (192,0))
|
quad1 = Image.open(q1path).resize((192,192), Image.ANTIALIAS)
|
||||||
|
img.paste(quad1, (192,0))
|
||||||
|
except Exception, e:
|
||||||
|
logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", q1path, e)
|
||||||
if q2path:
|
if q2path:
|
||||||
quad2 = Image.open(q2path).resize((192,192), Image.ANTIALIAS)
|
try:
|
||||||
img.paste(quad2, (0, 192))
|
quad2 = Image.open(q2path).resize((192,192), Image.ANTIALIAS)
|
||||||
|
img.paste(quad2, (0, 192))
|
||||||
|
except Exception, e:
|
||||||
|
logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", q2path, e)
|
||||||
if q3path:
|
if q3path:
|
||||||
quad3 = Image.open(q3path).resize((192,192), Image.ANTIALIAS)
|
try:
|
||||||
img.paste(quad3, (192, 192))
|
quad3 = Image.open(q3path).resize((192,192), Image.ANTIALIAS)
|
||||||
|
img.paste(quad3, (192, 192))
|
||||||
|
except Exception, e:
|
||||||
|
logging.warning("Couldn't open %s. It may be corrupt, you may need to delete it. %s", q3path, e)
|
||||||
|
|
||||||
# Save it
|
# Save it
|
||||||
if imgformat == 'jpg':
|
if imgformat == 'jpg':
|
||||||
img.save(imgpath, quality=95, subsampling=0)
|
img.save(imgpath, quality=95, subsampling=0)
|
||||||
else: # png
|
else: # png
|
||||||
img.save(imgpath)
|
img.save(imgpath)
|
||||||
|
if optimizeimg:
|
||||||
|
optimize_image(imgpath, imgformat, optimizeimg)
|
||||||
|
|
||||||
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, imgformat):
|
def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat, optimizeimg):
|
||||||
"""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
|
||||||
@@ -561,8 +593,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat
|
|||||||
# 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)
|
||||||
@@ -571,12 +602,12 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat
|
|||||||
# 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
|
||||||
@@ -586,6 +617,10 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat
|
|||||||
|
|
||||||
# Save them
|
# Save them
|
||||||
tileimg.save(imgpath)
|
tileimg.save(imgpath)
|
||||||
|
|
||||||
|
if optimizeimg:
|
||||||
|
optimize_image(imgpath, imgformat, optimizeimg)
|
||||||
|
|
||||||
with open(hashpath, "wb") as hashout:
|
with open(hashpath, "wb") as hashout:
|
||||||
hashout.write(digest)
|
hashout.write(digest)
|
||||||
|
|
||||||
|
|||||||
187
template.html
187
template.html
@@ -8,6 +8,7 @@
|
|||||||
#mcmap { height: 100% }
|
#mcmap { height: 100% }
|
||||||
</style>
|
</style>
|
||||||
<script type="text/javascript" src="markers.js"></script>
|
<script type="text/javascript" src="markers.js"></script>
|
||||||
|
<script type="text/javascript" src="regions.js"></script>
|
||||||
<script type="text/javascript"
|
<script type="text/javascript"
|
||||||
src="http://maps.google.com/maps/api/js?sensor=false">
|
src="http://maps.google.com/maps/api/js?sensor=false">
|
||||||
</script>
|
</script>
|
||||||
@@ -22,6 +23,65 @@
|
|||||||
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 +112,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,98 +136,104 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
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];
|
||||||
|
|
||||||
for (i in markerData) {
|
var converted = fromWorldToLatLng(item.x, item.y, item.z);
|
||||||
var item = markerData[i];
|
var marker = new google.maps.Marker({
|
||||||
|
position: converted,
|
||||||
|
map: map,
|
||||||
|
title: item.msg
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var converted = convertCoords(item.x-16, item.z, item.y);
|
var regionsInit = false;
|
||||||
|
function initRegions() {
|
||||||
|
if (regionsInit) { return; }
|
||||||
|
|
||||||
|
regionsInit = true;
|
||||||
|
|
||||||
var x = converted[0] / Math.pow(2, config.maxZoom);
|
for (i in regionData) {
|
||||||
var y = converted[1] / Math.pow(2, config.maxZoom);
|
var region = regionData[i];
|
||||||
var p = new google.maps.Point(x,y);
|
var converted = new google.maps.MVCArray();
|
||||||
var marker = new google.maps.Marker({
|
for (j in region.path) {
|
||||||
position: prot.fromPointToLatLng(p),
|
var point = region.path[j];
|
||||||
map: map,
|
converted.push(fromWorldToLatLng(point.x, point.y, point.z));
|
||||||
title:item.msg
|
}
|
||||||
});
|
|
||||||
|
|
||||||
}
|
if (region.closed) {
|
||||||
|
new google.maps.Polygon({
|
||||||
|
clickable: false,
|
||||||
|
geodesic: false,
|
||||||
}
|
map: map,
|
||||||
|
strokeColor: region.color,
|
||||||
|
strokeOpacity: region.opacity,
|
||||||
|
strokeWeight: 2,
|
||||||
|
fillColor: region.color,
|
||||||
|
fillOpacity: region.opacity * 0.25,
|
||||||
|
zIndex: i,
|
||||||
|
paths: converted
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
new google.maps.Polyline({
|
||||||
|
clickable: false,
|
||||||
|
geodesic: false,
|
||||||
|
map: map,
|
||||||
|
strokeColor: region.color,
|
||||||
|
strokeOpacity: region.opacity,
|
||||||
|
strokeWeight: 2,
|
||||||
|
zIndex: i,
|
||||||
|
path: converted
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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,
|
||||||
|
streetViewControl: false,
|
||||||
mapTypeId: 'mcmap'
|
mapTypeId: 'mcmap'
|
||||||
};
|
};
|
||||||
map = new google.maps.Map(document.getElementById("mcmap"), mapOptions);
|
map = new google.maps.Map(document.getElementById("mcmap"), mapOptions);
|
||||||
|
|
||||||
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 and regions
|
||||||
|
initMarkers();
|
||||||
if (config.debug)
|
initRegions();
|
||||||
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>
|
||||||
|
|||||||
196
textures.py
196
textures.py
@@ -114,7 +114,7 @@ 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, blockID):
|
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
|
||||||
|
|
||||||
@@ -147,18 +147,24 @@ def _transform_image(img, blockID):
|
|||||||
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, blockID):
|
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
|
if blockID in (44,): # step block
|
||||||
# make the top half transpartent
|
# make the top half transparent
|
||||||
# (don't just crop img, since we want the size of
|
# (don't just crop img, since we want the size of
|
||||||
# img to be unchanged
|
# img to be unchanged
|
||||||
mask = img.crop((0,8,16,16))
|
mask = img.crop((0,8,16,16))
|
||||||
n = Image.new(img.mode, img.size, (38,92,255,0))
|
n = Image.new(img.mode, img.size, (38,92,255,0))
|
||||||
n.paste(mask,(0,0,16,8), mask)
|
n.paste(mask,(0,0,16,8), mask)
|
||||||
img = n
|
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))
|
||||||
@@ -201,6 +207,8 @@ def _build_block(top, side, blockID=None):
|
|||||||
otherside.putalpha(othersidealpha)
|
otherside.putalpha(othersidealpha)
|
||||||
|
|
||||||
## special case for non-block things
|
## special case for non-block things
|
||||||
|
# TODO once torches are handled by generate_special_texture, remove
|
||||||
|
# them from this list
|
||||||
if blockID in (37,38,6,39,40,50,83): ## flowers, sapling, mushrooms, regular torch, reeds
|
if blockID in (37,38,6,39,40,50,83): ## 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
|
||||||
@@ -218,6 +226,11 @@ def _build_block(top, side, blockID=None):
|
|||||||
img.paste(side, (0,12), side)
|
img.paste(side, (0,12), side)
|
||||||
img.paste(otherside, (12,12), otherside)
|
img.paste(otherside, (12,12), otherside)
|
||||||
img.paste(top, (0,6), top)
|
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:
|
else:
|
||||||
img.paste(side, (0,6), side)
|
img.paste(side, (0,6), side)
|
||||||
img.paste(otherside, (12,6), otherside)
|
img.paste(otherside, (12,6), otherside)
|
||||||
@@ -251,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
|
||||||
@@ -268,7 +281,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, 5, 5, 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
|
||||||
@@ -321,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)
|
||||||
|
|||||||
28
world.py
28
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
|
||||||
@@ -121,6 +121,11 @@ class WorldRenderer(object):
|
|||||||
chunklist.append((base36decode(p[1]), base36decode(p[2]),
|
chunklist.append((base36decode(p[1]), base36decode(p[2]),
|
||||||
path))
|
path))
|
||||||
|
|
||||||
|
if not chunklist:
|
||||||
|
logging.error("No valid chunks specified in your chunklist!")
|
||||||
|
logging.error("HINT: chunks are in your world directory and have names of the form 'c.*.*.dat'")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Translate to col, row coordinates
|
# Translate to col, row coordinates
|
||||||
_, _, _, _, chunklist = _convert_coords(chunklist)
|
_, _, _, _, chunklist = _convert_coords(chunklist)
|
||||||
|
|
||||||
@@ -147,10 +152,8 @@ class WorldRenderer(object):
|
|||||||
chunkY = spawnZ/16
|
chunkY = spawnZ/16
|
||||||
|
|
||||||
## The filename of this chunk
|
## The filename of this chunk
|
||||||
chunkFile = "%s/%s/c.%s.%s.dat" % (base36encode(chunkX % 64),
|
chunkFile = os.path.join(base36encode(chunkX % 64), base36encode(chunkY % 64),
|
||||||
base36encode(chunkY % 64),
|
"c.%s.%s.dat" % (base36encode(chunkX), base36encode(chunkY)))
|
||||||
base36encode(chunkX),
|
|
||||||
base36encode(chunkY))
|
|
||||||
|
|
||||||
|
|
||||||
data=nbt.load(os.path.join(self.worlddir, chunkFile))[1]
|
data=nbt.load(os.path.join(self.worlddir, chunkFile))[1]
|
||||||
@@ -171,8 +174,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)
|
||||||
@@ -208,7 +212,7 @@ class WorldRenderer(object):
|
|||||||
os.path.join(dirpath, f)))
|
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
|
||||||
|
|
||||||
@@ -229,7 +233,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
|
||||||
@@ -243,9 +247,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:
|
||||||
@@ -268,10 +272,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