Merge branch 'master' into lighting
Conflicts: chunk.py
This commit is contained in:
27
chunk.py
27
chunk.py
@@ -80,6 +80,21 @@ def get_blockdata_array(level):
|
|||||||
in a similar manner to skylight data"""
|
in a similar manner to skylight data"""
|
||||||
return numpy.frombuffer(level['Data'], dtype=numpy.uint8).reshape((16,16,64))
|
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, 78, 79, 81, 83, 85])
|
59, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 81, 83, 85])
|
||||||
@@ -456,12 +471,7 @@ 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):
|
|
||||||
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]
|
blockid = blocks[x,y,z]
|
||||||
|
|
||||||
# the following blocks don't have textures that can be pre-computed from the blockid
|
# the following blocks don't have textures that can be pre-computed from the blockid
|
||||||
@@ -578,11 +588,6 @@ class ChunkRenderer(object):
|
|||||||
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+increment), (imgx+12,imgy+increment)), fill=(0,0,0), width=1)
|
draw.line(((imgx,imgy+6+increment), (imgx+12,imgy+increment)), fill=(0,0,0), width=1)
|
||||||
|
|
||||||
|
|
||||||
finally:
|
|
||||||
# Do this no mater how the above block exits
|
|
||||||
imgy -= 12
|
|
||||||
|
|
||||||
return img
|
return img
|
||||||
|
|
||||||
# Render 3 blending masks for lighting
|
# Render 3 blending masks for lighting
|
||||||
|
|||||||
8
gmap.py
8
gmap.py
@@ -51,6 +51,7 @@ def main():
|
|||||||
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("--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("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.")
|
||||||
parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, help="Print more output. You can specify this option multiple times.")
|
parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, help="Print more output. You can specify this option multiple times.")
|
||||||
|
|
||||||
@@ -100,6 +101,11 @@ def main():
|
|||||||
else:
|
else:
|
||||||
imgformat = 'png'
|
imgformat = 'png'
|
||||||
|
|
||||||
|
if options.optimizeimg:
|
||||||
|
optimizeimg = options.optimizeimg
|
||||||
|
else:
|
||||||
|
optimizeimg = None
|
||||||
|
|
||||||
logging.getLogger().setLevel(
|
logging.getLogger().setLevel(
|
||||||
logging.getLogger().level + 10*options.quiet)
|
logging.getLogger().level + 10*options.quiet)
|
||||||
logging.getLogger().setLevel(
|
logging.getLogger().setLevel(
|
||||||
@@ -113,7 +119,7 @@ def main():
|
|||||||
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.go(options.procs)
|
q.go(options.procs)
|
||||||
|
|
||||||
def delete_all(worlddir, tiledir):
|
def delete_all(worlddir, tiledir):
|
||||||
|
|||||||
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]
|
||||||
|
|
||||||
38
quadtree.py
38
quadtree.py
@@ -24,10 +24,12 @@ import shutil
|
|||||||
import collections
|
import collections
|
||||||
import json
|
import json
|
||||||
import logging
|
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
|
||||||
@@ -55,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
|
||||||
|
|
||||||
@@ -67,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)
|
||||||
@@ -128,11 +131,20 @@ 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)
|
||||||
|
|
||||||
|
# write out the default marker table
|
||||||
|
|
||||||
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 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('];')
|
||||||
|
|
||||||
# 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")
|
||||||
@@ -235,7 +247,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.
|
||||||
@@ -247,7 +260,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"""
|
||||||
@@ -331,7 +344,7 @@ class QuadtreeGen(object):
|
|||||||
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"""
|
||||||
@@ -362,7 +375,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")
|
||||||
@@ -452,12 +465,15 @@ def render_innertile(dest, name, imgformat):
|
|||||||
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
|
||||||
@@ -586,6 +602,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)
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -155,6 +156,48 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var regionsInit = false;
|
||||||
|
function initRegions() {
|
||||||
|
if (regionsInit) { return; }
|
||||||
|
|
||||||
|
regionsInit = true;
|
||||||
|
|
||||||
|
for (i in regionData) {
|
||||||
|
var region = regionData[i];
|
||||||
|
var converted = new google.maps.MVCArray();
|
||||||
|
for (j in region.path) {
|
||||||
|
var point = region.path[j];
|
||||||
|
converted.push(fromWorldToLatLng(point.x, point.y, point.z));
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
@@ -162,6 +205,7 @@
|
|||||||
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);
|
||||||
@@ -187,8 +231,9 @@
|
|||||||
// 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');
|
||||||
|
|
||||||
// initialize the markers
|
// initialize the markers and regions
|
||||||
initMarkers();
|
initMarkers();
|
||||||
|
initRegions();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
7
world.py
7
world.py
@@ -123,6 +123,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)
|
||||||
|
|
||||||
@@ -216,8 +221,6 @@ 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:
|
||||||
logging.error("Error: No chunks found!")
|
logging.error("Error: No chunks found!")
|
||||||
|
|||||||
Reference in New Issue
Block a user