0

Merge branch 'master' into mineral-overlay

Conflicts:
	src/overviewer.h
This commit is contained in:
Aaron Griffith
2011-06-06 22:18:23 -04:00
27 changed files with 449 additions and 313 deletions

View File

@@ -191,11 +191,18 @@ Options
--list-rendermodes --list-rendermodes
List the available render modes, and a short description of each. List the available render modes, and a short description of each.
--settings=PATH
Use this option to load settings from a file. The format of this file is
given below.
Settings Settings
-------- --------
You can optionally store settings in a file named settings.py. It is a regular
python script, so you can use any python functions or modules you want. You can optionally store settings in a file named settings.py (or really,
anything you want). It is a regular python script, so you can use any python
functions or modules you want. To use a settings file, use the --settings
command line option.
For a sample settings file, look at 'sample.settings.py'. Note that this file For a sample settings file, look at 'sample.settings.py'. Note that this file
is not meant to be used directly, but instead it should be used as a is not meant to be used directly, but instead it should be used as a
@@ -241,6 +248,18 @@ web_assets_hook
This function should accept one argument: a QuadtreeGen object. This function should accept one argument: a QuadtreeGen object.
web_assets_path
This option lets you provide alternative web assets to use when
rendering. The contents of this folder will be copied into the output folder
during render, and will overwrite any default files already copied by
Overviewer. See the web_assets folder included with Overviewer for the
default assets.
textures_path
This is like web_assets_path, but instead it provides an alternative texture
source. Overviewer looks in here for terrain.png and other textures before
it looks anywhere else.
Viewing the Results Viewing the Results
------------------- -------------------
Within the output directory you will find two things: an index.html file, and a Within the output directory you will find two things: an index.html file, and a

View File

@@ -114,10 +114,10 @@ def get_tileentity_data(level):
return data return data
# 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, 26, 27, 28, 30, 37, 38, 39, 40, transparent_blocks = set([ 0, 6, 8, 9, 18, 20, 26, 27, 28, 30, 31, 32, 37, 38,
44, 50, 51, 52, 53, 55, 59, 63, 64, 65, 66, 67, 68, 69, 39, 40, 44, 50, 51, 52, 53, 55, 59, 63, 64, 65, 66, 67,
70, 71, 72, 74, 75, 76, 77, 78, 79, 81, 83, 85, 90, 92, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 81, 83, 85,
93, 94]) 90, 92, 93, 94, 96])
# This set holds block ids that are solid blocks # This set holds block ids that are solid blocks
solid_blocks = set([1, 2, 3, 4, 5, 7, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, solid_blocks = set([1, 2, 3, 4, 5, 7, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,

View File

@@ -22,6 +22,9 @@ class ConfigOptionParser(object):
self.customArgs = ["required", "commandLineOnly", "default", "listify", "listdelim", "choices"] self.customArgs = ["required", "commandLineOnly", "default", "listify", "listdelim", "choices"]
self.requiredArgs = [] self.requiredArgs = []
# add the *very* special config-file path option
self.add_option("--settings", dest="config_file", help="Specifies a settings file to load, by name. This file's format is discussed in the README.", metavar="PATH", type="string", commandLineOnly=True)
def display_config(self): def display_config(self):
logging.info("Using the following settings:") logging.info("Using the following settings:")
@@ -68,8 +71,14 @@ class ConfigOptionParser(object):
# if this has a default, use that to seed the globals dict # if this has a default, use that to seed the globals dict
if a.get("default", None): g[n] = a['default'] if a.get("default", None): g[n] = a['default']
g['args'] = args g['args'] = args
try: try:
if options.config_file:
self.configFile = options.config_file
elif os.path.exists(self.configFile):
# warn about automatic loading
logging.warning("Automatic settings.py loading is DEPRECATED, and may be removed in the future. Please use --settings instead.")
if os.path.exists(self.configFile): if os.path.exists(self.configFile):
execfile(self.configFile, g, l) execfile(self.configFile, g, l)
except NameError, ex: except NameError, ex:

View File

@@ -20,7 +20,7 @@ import sys
sys.path.insert(0,".") sys.path.insert(0,".")
import nbt import nbt
from chunk import get_blockarray_fromfile from chunk import get_blockarray_fromfile, get_blockarray
import os import os
import re import re

View File

@@ -68,6 +68,7 @@ class MapGen(object):
self.skipjs = configInfo.get('skipjs', None) self.skipjs = configInfo.get('skipjs', None)
self.web_assets_hook = configInfo.get('web_assets_hook', None) self.web_assets_hook = configInfo.get('web_assets_hook', None)
self.web_assets_path = configInfo.get('web_assets_path', None)
self.bg_color = configInfo.get('bg_color') self.bg_color = configInfo.get('bg_color')
if not len(quadtrees) > 0: if not len(quadtrees) > 0:
@@ -81,14 +82,28 @@ class MapGen(object):
raise ValueError("all the given quadtrees must have the same destdir and world") raise ValueError("all the given quadtrees must have the same destdir and world")
self.quadtrees = quadtrees self.quadtrees = quadtrees
def go(self, procs): def go(self, procs):
"""Writes out config.js, marker.js, and region.js """Writes out config.js, marker.js, and region.js
Copies web assets into the destdir""" Copies web assets into the destdir"""
zoomlevel = self.p zoomlevel = self.p
configpath = os.path.join(util.get_program_path(), "overviewerConfig.js")
config = open(configpath, 'r').read() bgcolor = (int(self.bg_color[1:3],16), int(self.bg_color[3:5],16), int(self.bg_color[5:7],16), 0)
blank = Image.new("RGBA", (1,1), bgcolor)
# Write a blank image
for quadtree in self.quadtrees:
tileDir = os.path.join(self.destdir, quadtree.tiledir)
if not os.path.exists(tileDir): os.mkdir(tileDir)
blank.save(os.path.join(tileDir, "blank."+quadtree.imgformat))
# copy web assets into destdir:
mirror_dir(os.path.join(util.get_program_path(), "web_assets"), self.destdir)
# do the same with the local copy, if we have it
if self.web_assets_path:
mirror_dir(self.web_assets_path, self.destdir)
# replace the config js stuff
config = open(os.path.join(self.destdir, 'overviewerConfig.js'), 'r').read()
config = config.replace( config = config.replace(
"{minzoom}", str(0)) "{minzoom}", str(0))
config = config.replace( config = config.replace(
@@ -111,18 +126,7 @@ class MapGen(object):
with open(os.path.join(self.destdir, "overviewerConfig.js"), 'w') as output: with open(os.path.join(self.destdir, "overviewerConfig.js"), 'w') as output:
output.write(config) output.write(config)
bgcolor = (int(self.bg_color[1:3],16), int(self.bg_color[3:5],16), int(self.bg_color[5:7],16), 0) # Add time and version in index.html
blank = Image.new("RGBA", (1,1), bgcolor)
# Write a blank image
for quadtree in self.quadtrees:
tileDir = os.path.join(self.destdir, quadtree.tiledir)
if not os.path.exists(tileDir): os.mkdir(tileDir)
blank.save(os.path.join(tileDir, "blank."+quadtree.imgformat))
# copy web assets into destdir:
mirror_dir(os.path.join(util.get_program_path(), "web_assets"), self.destdir)
# Add time in index.html
indexpath = os.path.join(self.destdir, "index.html") indexpath = os.path.join(self.destdir, "index.html")
index = open(indexpath, 'r').read() index = open(indexpath, 'r').read()

View File

@@ -45,7 +45,8 @@ def optimize_image(imgpath, imgformat, optimizeimg):
os.rename(imgpath+".tmp", imgpath) os.rename(imgpath+".tmp", imgpath)
if optimizeimg >= 2: if optimizeimg >= 2:
subprocess.Popen([optipng, imgpath], stderr=subprocess.STDOUT, # the "-nc" it's needed to no broke the transparency of tiles
subprocess.Popen([optipng, "-nc", imgpath], stderr=subprocess.STDOUT,
stdout=subprocess.PIPE).communicate()[0] stdout=subprocess.PIPE).communicate()[0]
subprocess.Popen([advdef, "-z4",imgpath], stderr=subprocess.STDOUT, subprocess.Popen([advdef, "-z4",imgpath], stderr=subprocess.STDOUT,
stdout=subprocess.PIPE).communicate()[0] stdout=subprocess.PIPE).communicate()[0]

View File

@@ -93,7 +93,8 @@ def main():
parser.add_option("-p", "--processes", dest="procs", help="How many worker processes to start. Default %s" % cpus, default=cpus, action="store", type="int") parser.add_option("-p", "--processes", dest="procs", help="How many worker processes to start. Default %s" % cpus, default=cpus, action="store", type="int")
parser.add_option("-z", "--zoom", dest="zoom", help="Sets the zoom level manually instead of calculating it. This can be useful if you have outlier chunks that make your world too big. This value will make the highest zoom level contain (2**ZOOM)^2 tiles", action="store", type="int", configFileOnly=True) parser.add_option("-z", "--zoom", dest="zoom", help="Sets the zoom level manually instead of calculating it. This can be useful if you have outlier chunks that make your world too big. This value will make the highest zoom level contain (2**ZOOM)^2 tiles", action="store", type="int", configFileOnly=True)
parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true", commandLineOnly=True) parser.add_option("-d", "--delete", dest="delete", help="Clear all caches. Next time you render your world, it will have to start completely over again. This is probably not a good idea for large worlds. Use this if you change texture packs and want to re-render everything.", action="store_true", commandLineOnly=True)
parser.add_option("--chunklist", dest="chunklist", help="A file containing, on each line, a path to a chunkfile to update. Instead of scanning the world directory for chunks, it will just use this list. Normal caching rules still apply.") parser.add_option("--regionlist", dest="regionlist", help="A file containing, on each line, a path to a regionlist to update. Instead of scanning the world directory for regions, it will just use this list. Normal caching rules still apply.")
parser.add_option("--forcerender", dest="forcerender", help="Force re-rendering the entire map (or the given regionlist). Useful for re-rendering without deleting the entire map with --delete.", action="store_true")
parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render types, separated by commas. Use --list-rendermodes to list them all.", type="choice", choices=avail_rendermodes, required=True, default=avail_rendermodes[0], listify=True) parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render types, separated by commas. Use --list-rendermodes to list them all.", type="choice", choices=avail_rendermodes, required=True, default=avail_rendermodes[0], listify=True)
parser.add_option("--list-rendermodes", dest="list_rendermodes", action="store_true", help="List available render modes and exit.", commandLineOnly=True) parser.add_option("--list-rendermodes", dest="list_rendermodes", action="store_true", help="List available render modes and exit.", commandLineOnly=True)
parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg.", configFileOnly=True ) parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg.", configFileOnly=True )
@@ -101,6 +102,8 @@ def main():
parser.add_option("--bg_color", dest="bg_color", help="Configures the background color for the GoogleMap output. Specify in #RRGGBB format", configFileOnly=True, type="string", default="#1A1A1A") parser.add_option("--bg_color", dest="bg_color", help="Configures the background color for the GoogleMap output. Specify in #RRGGBB format", configFileOnly=True, type="string", default="#1A1A1A")
parser.add_option("--optimize-img", dest="optimizeimg", help="If using png, perform image file size optimizations on the output. Specify 1 for pngcrush, 2 for pngcrush+optipng+advdef. This may double (or more) render times, but will produce up to 30% smaller images. NOTE: requires corresponding programs in $PATH or %PATH%", configFileOnly=True) parser.add_option("--optimize-img", dest="optimizeimg", help="If using png, perform image file size optimizations on the output. Specify 1 for pngcrush, 2 for pngcrush+optipng+advdef. This may double (or more) render times, but will produce up to 30% smaller images. NOTE: requires corresponding programs in $PATH or %PATH%", configFileOnly=True)
parser.add_option("--web-assets-hook", dest="web_assets_hook", help="If provided, run this function after the web assets have been copied, but before actual tile rendering begins. It should accept a QuadtreeGen object as its only argument.", action="store", metavar="SCRIPT", type="function", configFileOnly=True) parser.add_option("--web-assets-hook", dest="web_assets_hook", help="If provided, run this function after the web assets have been copied, but before actual tile rendering begins. It should accept a QuadtreeGen object as its only argument.", action="store", metavar="SCRIPT", type="function", configFileOnly=True)
parser.add_option("--web-assets-path", dest="web_assets_path", help="Specifies a non-standard web_assets directory to use. Files here will overwrite the default web assets.", metavar="PATH", type="string", configFileOnly=True)
parser.add_option("--textures-path", dest="textures_path", help="Specifies a non-standard textures path, from which terrain.png and other textures are loaded.", metavar="PATH", type="string", configFileOnly=True)
parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.") parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.")
parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, help="Print more output. You can specify this option multiple times.") parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, help="Print more output. You can specify this option multiple times.")
parser.add_option("--skip-js", dest="skipjs", action="store_true", help="Don't output marker.js or regions.js") parser.add_option("--skip-js", dest="skipjs", action="store_true", help="Don't output marker.js or regions.js")
@@ -187,10 +190,10 @@ def main():
if options.delete: if options.delete:
return delete_all(worlddir, destdir) return delete_all(worlddir, destdir)
if options.chunklist: if options.regionlist:
chunklist = open(options.chunklist, 'r') regionlist = map(str.strip, open(options.regionlist, 'r'))
else: else:
chunklist = None regionlist = None
if options.imgformat: if options.imgformat:
if options.imgformat not in ('jpg','png'): if options.imgformat not in ('jpg','png'):
@@ -219,7 +222,7 @@ def main():
logging.info("Notice: Not using biome data for tinting") logging.info("Notice: Not using biome data for tinting")
# First do world-level preprocessing # First do world-level preprocessing
w = world.World(worlddir, useBiomeData=useBiomeData) w = world.World(worlddir, useBiomeData=useBiomeData, regionlist=regionlist)
w.go(options.procs) w.go(options.procs)
logging.info("Rending the following tilesets: %s", ",".join(options.rendermode)) logging.info("Rending the following tilesets: %s", ",".join(options.rendermode))
@@ -229,7 +232,7 @@ def main():
# create the quadtrees # create the quadtrees
# TODO chunklist # TODO chunklist
q = [] q = []
qtree_args = {'depth' : options.zoom, 'imgformat' : imgformat, 'imgquality' : options.imgquality, 'optimizeimg' : optimizeimg, 'bgcolor' : bgcolor} qtree_args = {'depth' : options.zoom, 'imgformat' : imgformat, 'imgquality' : options.imgquality, 'optimizeimg' : optimizeimg, 'bgcolor' : bgcolor, 'forcerender' : options.forcerender}
for rendermode in options.rendermode: for rendermode in options.rendermode:
if rendermode == 'normal': if rendermode == 'normal':
qtree = quadtree.QuadtreeGen(w, destdir, rendermode=rendermode, tiledir='tiles', **qtree_args) qtree = quadtree.QuadtreeGen(w, destdir, rendermode=rendermode, tiledir='tiles', **qtree_args)
@@ -242,7 +245,7 @@ def main():
qtree.go(options.procs) qtree.go(options.procs)
# create the distributed render # create the distributed render
r = rendernode.RenderNode(q) r = rendernode.RenderNode(q, options)
# write out the map and web assets # write out the map and web assets
m = googlemap.MapGen(q, configInfo=options) m = googlemap.MapGen(q, configInfo=options)

View File

@@ -49,7 +49,7 @@ def iterate_base4(d):
return itertools.product(xrange(4), repeat=d) return itertools.product(xrange(4), repeat=d)
class QuadtreeGen(object): class QuadtreeGen(object):
def __init__(self, worldobj, destdir, bgcolor, depth=None, tiledir=None, imgformat=None, imgquality=95, optimizeimg=None, rendermode="normal"): def __init__(self, worldobj, destdir, bgcolor, depth=None, tiledir=None, forcerender=False, imgformat=None, imgquality=95, optimizeimg=None, rendermode="normal"):
"""Generates a quadtree from the world given into the """Generates a quadtree from the world given into the
given dest directory given dest directory
@@ -60,6 +60,7 @@ class QuadtreeGen(object):
""" """
assert(imgformat) assert(imgformat)
self.forcerender = forcerender
self.imgformat = imgformat self.imgformat = imgformat
self.imgquality = imgquality self.imgquality = imgquality
self.optimizeimg = optimizeimg self.optimizeimg = optimizeimg
@@ -246,7 +247,7 @@ class QuadtreeGen(object):
regiony = regiony_ regiony = regiony_
_, _, c, mcr = get_region((regionx, regiony),(None,None,None,None)) _, _, c, mcr = get_region((regionx, regiony),(None,None,None,None))
if c is not None and mcr.chunkExists(chunkx,chunky): if c is not None and mcr.chunkExists(chunkx,chunky):
chunklist.append((col, row, chunkx, chunky, c)) chunklist.append((col, row, chunkx, chunky, c))
return chunklist return chunklist
@@ -425,8 +426,17 @@ class QuadtreeGen(object):
needs_rerender = False needs_rerender = False
get_region_mtime = world.get_region_mtime get_region_mtime = world.get_region_mtime
for col, row, chunkx, chunky, regionfile in chunks: for col, row, chunkx, chunky, regionfile in chunks:
# don't even check if it's not in the regionlist
if self.world.regionlist and region._filename not in self.world.regionlist:
continue
# bail early if forcerender is set
if self.forcerender:
needs_rerender = True
break
# check region file mtime first. # check region file mtime first.
region,regionMtime = get_region_mtime(regionfile) region,regionMtime = get_region_mtime(regionfile)
if regionMtime <= tile_mtime: if regionMtime <= tile_mtime:
continue continue

View File

@@ -26,6 +26,8 @@ import collections
import json import json
import logging import logging
import util import util
import textures
import c_overviewer
import cPickle import cPickle
import stat import stat
import errno import errno
@@ -59,14 +61,22 @@ def pool_initializer(rendernode):
logging.debug("Child process {0}".format(os.getpid())) logging.debug("Child process {0}".format(os.getpid()))
#stash the quadtree objects in a global variable after fork() for windows compat. #stash the quadtree objects in a global variable after fork() for windows compat.
global child_rendernode global child_rendernode
child_rendernode = rendernode child_rendernode = rendernode
# make sure textures are generated for this process
# and initialize c_overviewer
textures.generate(path=rendernode.options.get('textures_path', None))
c_overviewer.init_chunk_render()
# load biome data in each process, if needed
for quadtree in rendernode.quadtrees: for quadtree in rendernode.quadtrees:
if quadtree.world.useBiomeData: if quadtree.world.useBiomeData:
import textures
# make sure we've at least *tried* to load the color arrays in this process... # make sure we've at least *tried* to load the color arrays in this process...
textures.prepareBiomeData(quadtree.world.worlddir) textures.prepareBiomeData(quadtree.world.worlddir)
if not textures.grasscolor or not textures.foliagecolor: if not textures.grasscolor or not textures.foliagecolor:
raise Exception("Can't find grasscolor.png or foliagecolor.png") raise Exception("Can't find grasscolor.png or foliagecolor.png")
# only load biome data once
break
#http://docs.python.org/library/itertools.html #http://docs.python.org/library/itertools.html
def roundrobin(iterables): def roundrobin(iterables):
@@ -84,12 +94,13 @@ def roundrobin(iterables):
class RenderNode(object): class RenderNode(object):
def __init__(self, quadtrees): def __init__(self, quadtrees, options):
"""Distributes the rendering of a list of quadtrees.""" """Distributes the rendering of a list of quadtrees."""
if not len(quadtrees) > 0: if not len(quadtrees) > 0:
raise ValueError("there must be at least one quadtree to work on") raise ValueError("there must be at least one quadtree to work on")
self.options = options
self.quadtrees = quadtrees self.quadtrees = quadtrees
#bind an index value to the quadtree so we can find it again #bind an index value to the quadtree so we can find it again
#and figure out which worlds are where #and figure out which worlds are where

View File

@@ -50,10 +50,13 @@ zoom = 9
## Example: Dynamically create regionlist of only regions older than 2 days ## Example: Dynamically create regionlist of only regions older than 2 days
import os, time import os, time
# the following two lines are needed to the lambda to work
globals()['os'] = os
globals()['time'] = time
regionDir = os.path.join(args[0], "region") regionDir = os.path.join(args[0], "region")
regionFiles = filter(lambda x: x.endswith(".mcr"), os.listdir(regionDir)) regionFiles = filter(lambda x: x.endswith(".mcr"), os.listdir(regionDir))
def olderThanTwoDays(f): def olderThanTwoDays(f):
return time.time() - os.stat(f).st_mtime > (60*60*24*2) return time.time() - os.stat(os.path.join(args[0], 'region',f)).st_mtime > (60*60*24*2)
oldRegionFiles = filter(olderThanTwoDays, regionFiles) oldRegionFiles = filter(olderThanTwoDays, regionFiles)
with open("regionlist.txt", "w") as f: with open("regionlist.txt", "w") as f:
f.write("\n".join(oldRegionFiles)) f.write("\n".join(oldRegionFiles))
@@ -148,3 +151,9 @@ if "web_assets_hook" in locals():
skipjs = True skipjs = True
### As a reminder, don't use this file verbatim, it should only be used as
### a guide.
import sys
sys.exit("This sample-settings file shouldn't be used directly!")

View File

@@ -28,7 +28,7 @@ setup_kwargs['cmdclass'] = {}
if py2exe is not None: if py2exe is not None:
setup_kwargs['console'] = ['overviewer.py'] setup_kwargs['console'] = ['overviewer.py']
setup_kwargs['data_files'] = [('textures', ['textures/lava.png', 'textures/water.png', 'textures/fire.png', 'textures/portal.png']), setup_kwargs['data_files'] = [('textures', ['textures/lava.png', 'textures/water.png', 'textures/fire.png', 'textures/portal.png']),
('', ['overviewerConfig.js', 'COPYING.txt', 'README.rst']), ('', ['COPYING.txt', 'README.rst']),
('web_assets', glob.glob('web_assets/*'))] ('web_assets', glob.glob('web_assets/*'))]
setup_kwargs['zipfile'] = None setup_kwargs['zipfile'] = None
if platform.system() == 'Windows' and '64bit' in platform.architecture(): if platform.system() == 'Windows' and '64bit' in platform.architecture():

View File

@@ -24,43 +24,41 @@ static PyObject *special_blocks = NULL;
static PyObject *specialblockmap = NULL; static PyObject *specialblockmap = NULL;
static PyObject *transparent_blocks = NULL; static PyObject *transparent_blocks = NULL;
int init_chunk_render(void) { PyObject *init_chunk_render(PyObject *self, PyObject *args) {
/* if blockmap (or any of these) is not NULL, then that means that we've /* this function only needs to be called once, anything more is an
* somehow called this function twice. error out so we can notice this * error... */
* */ if (blockmap) {
if (blockmap) return 1; PyErr_SetString(PyExc_RuntimeError, "init_chunk_render should only be called once per process.");
return NULL;
}
textures = PyImport_ImportModule("textures"); textures = PyImport_ImportModule("textures");
/* ensure none of these pointers are NULL */ /* ensure none of these pointers are NULL */
if ((!textures)) { if ((!textures)) {
fprintf(stderr, "\ninit_chunk_render failed to load; textures\n"); return NULL;
PyErr_Print();
return 1;
} }
chunk_mod = PyImport_ImportModule("chunk"); chunk_mod = PyImport_ImportModule("chunk");
/* ensure none of these pointers are NULL */ /* ensure none of these pointers are NULL */
if ((!chunk_mod)) { if ((!chunk_mod)) {
fprintf(stderr, "\ninit_chunk_render failed to load; chunk\n"); return NULL;
PyErr_Print();
return 1;
} }
blockmap = PyObject_GetAttrString(textures, "blockmap"); blockmap = PyObject_GetAttrString(textures, "blockmap");
if (!blockmap)
return NULL;
special_blocks = PyObject_GetAttrString(textures, "special_blocks"); special_blocks = PyObject_GetAttrString(textures, "special_blocks");
if (!special_blocks)
return NULL;
specialblockmap = PyObject_GetAttrString(textures, "specialblockmap"); specialblockmap = PyObject_GetAttrString(textures, "specialblockmap");
if (!specialblockmap)
return NULL;
transparent_blocks = PyObject_GetAttrString(chunk_mod, "transparent_blocks"); transparent_blocks = PyObject_GetAttrString(chunk_mod, "transparent_blocks");
if (!transparent_blocks)
return NULL;
/* ensure none of these pointers are NULL */ Py_RETURN_NONE;
if ((!transparent_blocks) || (!blockmap) || (!special_blocks) || (!specialblockmap)) {
fprintf(stderr, "\ninit_chunk_render failed\n");
PyErr_Print();
return 1;
}
return 0;
} }
int int
@@ -162,13 +160,17 @@ generate_pseudo_data(RenderState *state, unsigned char ancilData) {
return ancilData; return ancilData;
} else if (state->block == 9) { /* water */ } else if (state->block == 9) { /* water */
/* an aditional bit for top is added to the 4 bits of check_adjacent_blocks */ /* an aditional bit for top is added to the 4 bits of check_adjacent_blocks */
if ((ancilData == 0) || (ancilData >= 10)) { /* static water, only top, and unkown ancildata values */ if (ancilData == 0) { /* static water */
data = 16; if ((z != 127) && (getArrayByte3D(state->blocks, x, y, z+1) == 9)) {
data = 0;
} else {
data = 16;
}
return data; /* = 0b10000 */ return data; /* = 0b10000 */
} else if ((ancilData > 0) && (ancilData < 8)) { /* flowing water */ } else if ((ancilData > 0) && (ancilData < 8)) { /* flowing water */
data = (check_adjacent_blocks(state, x, y, z, state->block) ^ 0x0f) | 0x10; data = (check_adjacent_blocks(state, x, y, z, state->block) ^ 0x0f) | 0x10;
return data; return data;
} else if ((ancilData == 8) || (ancilData == 9)) { /* falling water */ } else if (ancilData >= 8) { /* falling water */
data = (check_adjacent_blocks(state, x, y, z, state->block) ^ 0x0f); data = (check_adjacent_blocks(state, x, y, z, state->block) ^ 0x0f);
return data; return data;
} }
@@ -291,7 +293,6 @@ PyObject*
chunk_render(PyObject *self, PyObject *args) { chunk_render(PyObject *self, PyObject *args) {
RenderState state; RenderState state;
PyObject *blockdata_expanded;
int xoff, yoff; int xoff, yoff;
PyObject *imgsize, *imgsize0_py, *imgsize1_py; PyObject *imgsize, *imgsize0_py, *imgsize1_py;
@@ -309,8 +310,8 @@ chunk_render(PyObject *self, PyObject *args) {
PyObject *t = NULL; PyObject *t = NULL;
if (!PyArg_ParseTuple(args, "OOiiO", &state.self, &state.img, &xoff, &yoff, &blockdata_expanded)) if (!PyArg_ParseTuple(args, "OOiiO", &state.self, &state.img, &xoff, &yoff, &state.blockdata_expanded))
return Py_BuildValue("i", "-1"); return NULL;
/* fill in important modules */ /* fill in important modules */
state.textures = textures; state.textures = textures;
@@ -400,7 +401,7 @@ chunk_render(PyObject *self, PyObject *args) {
} else { } else {
PyObject *tmp; PyObject *tmp;
unsigned char ancilData = getArrayByte3D(blockdata_expanded, state.x, state.y, state.z); unsigned char ancilData = getArrayByte3D(state.blockdata_expanded, state.x, state.y, state.z);
if ((state.block == 85) || (state.block == 9) || (state.block == 55) || (state.block == 54) || (state.block == 2) || (state.block == 90)) { if ((state.block == 85) || (state.block == 9) || (state.block == 55) || (state.block == 54) || (state.block == 2) || (state.block == 90)) {
ancilData = generate_pseudo_data(&state, ancilData); ancilData = generate_pseudo_data(&state, ancilData);
} }
@@ -419,14 +420,15 @@ chunk_render(PyObject *self, PyObject *args) {
/* if we found a proper texture, render it! */ /* if we found a proper texture, render it! */
if (t != NULL && t != Py_None) if (t != NULL && t != Py_None)
{ {
PyObject *src, *mask; PyObject *src, *mask, *mask_light;
src = PyTuple_GetItem(t, 0); src = PyTuple_GetItem(t, 0);
mask = PyTuple_GetItem(t, 1); mask = PyTuple_GetItem(t, 1);
mask_light = PyTuple_GetItem(t, 2);
if (mask == Py_None) if (mask == Py_None)
mask = src; mask = src;
rendermode->draw(rm_data, &state, src, mask); rendermode->draw(rm_data, &state, src, mask, mask_light);
} }
} }
@@ -435,7 +437,7 @@ chunk_render(PyObject *self, PyObject *args) {
blockid = NULL; blockid = NULL;
} }
} }
} }
/* free up the rendermode info */ /* free up the rendermode info */
rendermode->finish(rm_data, &state); rendermode->finish(rm_data, &state);

View File

@@ -26,6 +26,8 @@ static PyMethodDef COverviewerMethods[] = {
{"alpha_over", alpha_over_wrap, METH_VARARGS, {"alpha_over", alpha_over_wrap, METH_VARARGS,
"alpha over composite function"}, "alpha over composite function"},
{"init_chunk_render", init_chunk_render, METH_VARARGS,
"Initializes the stuffs renderer."},
{"render_loop", chunk_render, METH_VARARGS, {"render_loop", chunk_render, METH_VARARGS,
"Renders stuffs"}, "Renders stuffs"},
@@ -53,12 +55,6 @@ initc_overviewer(void)
(void)Py_InitModule("c_overviewer", COverviewerMethods); (void)Py_InitModule("c_overviewer", COverviewerMethods);
/* for numpy */ /* for numpy */
import_array(); import_array();
/* initialize some required variables in iterage.c */
if (init_chunk_render()) {
fprintf(stderr, "failed to init_chunk_render\n");
exit(1); // TODO better way to indicate error?
}
init_endian(); init_endian();
} }

View File

@@ -26,7 +26,7 @@
// increment this value if you've made a change to the c extesion // increment this value if you've made a change to the c extesion
// and want to force users to rebuild // and want to force users to rebuild
#define OVERVIEWER_EXTENSION_VERSION 6 #define OVERVIEWER_EXTENSION_VERSION 8
/* Python PIL, and numpy headers */ /* Python PIL, and numpy headers */
#include <Python.h> #include <Python.h>
@@ -70,13 +70,14 @@ typedef struct {
/* the block position and type, and the block array */ /* the block position and type, and the block array */
int x, y, z; int x, y, z;
unsigned char block; unsigned char block;
PyObject *blockdata_expanded;
PyObject *blocks; PyObject *blocks;
PyObject *up_left_blocks; PyObject *up_left_blocks;
PyObject *up_right_blocks; PyObject *up_right_blocks;
PyObject *left_blocks; PyObject *left_blocks;
PyObject *right_blocks; PyObject *right_blocks;
} RenderState; } RenderState;
int init_chunk_render(void); PyObject *init_chunk_render(PyObject *self, PyObject *args);
int is_transparent(unsigned char b); int is_transparent(unsigned char b);
PyObject *chunk_render(PyObject *self, PyObject *args); PyObject *chunk_render(PyObject *self, PyObject *args);

View File

@@ -202,7 +202,7 @@ rendermode_cave_finish(void *data, RenderState *state) {
} }
static void static void
rendermode_cave_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { rendermode_cave_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
RenderModeCave* self; RenderModeCave* self;
int z, r, g, b; int z, r, g, b;
self = (RenderModeCave *)data; self = (RenderModeCave *)data;
@@ -211,7 +211,7 @@ rendermode_cave_draw(void *data, RenderState *state, PyObject *src, PyObject *ma
r = 0, g = 0, b = 0; r = 0, g = 0, b = 0;
/* draw the normal block */ /* draw the normal block */
rendermode_normal.draw(data, state, src, mask); rendermode_normal.draw(data, state, src, mask, mask_light);
/* get the colors and tint and tint */ /* get the colors and tint and tint */
/* TODO TODO for a nether mode there isn't tinting! */ /* TODO TODO for a nether mode there isn't tinting! */

View File

@@ -91,7 +91,7 @@ get_lighting_coefficient(RenderModeLighting *self, RenderState *state,
/* only do special half-step handling if no authoratative pointer was /* only do special half-step handling if no authoratative pointer was
passed in, which is a sign that we're recursing */ passed in, which is a sign that we're recursing */
if (block == 44 && authoratative == NULL) { if ((block == 44 || block == 53 || block == 67) && authoratative == NULL) {
float average_gather = 0.0f; float average_gather = 0.0f;
unsigned int average_count = 0; unsigned int average_count = 0;
int auth; int auth;
@@ -206,19 +206,19 @@ rendermode_lighting_occluded(void *data, RenderState *state) {
} }
static void static void
rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
RenderModeLighting* self; RenderModeLighting* self;
int x, y, z; int x, y, z;
/* first, chain up */ /* first, chain up */
rendermode_normal.draw(data, state, src, mask); rendermode_normal.draw(data, state, src, mask, mask_light);
self = (RenderModeLighting *)data; self = (RenderModeLighting *)data;
x = state->x, y = state->y, z = state->z; x = state->x, y = state->y, z = state->z;
if (is_transparent(state->block)) { if (is_transparent(state->block)) {
/* transparent: do shading on whole block */ /* transparent: do shading on whole block */
do_shading_with_mask(self, state, x, y, z, mask); do_shading_with_mask(self, state, x, y, z, mask_light);
} else { } else {
/* opaque: do per-face shading */ /* opaque: do per-face shading */
do_shading_with_mask(self, state, x, y, z+1, self->facemasks[0]); do_shading_with_mask(self, state, x, y, z+1, self->facemasks[0]);

View File

@@ -101,9 +101,9 @@ rendermode_mineral_occluded(void *data, RenderState *state) {
} }
static void static void
rendermode_mineral_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { rendermode_mineral_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
/* draw normally */ /* draw normally */
rendermode_overlay.draw(data, state, src, mask); rendermode_overlay.draw(data, state, src, mask, mask_light);
} }
RenderModeInterface rendermode_mineral = { RenderModeInterface rendermode_mineral = {

View File

@@ -54,9 +54,9 @@ rendermode_night_occluded(void *data, RenderState *state) {
} }
static void static void
rendermode_night_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { rendermode_night_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
/* nothing special to do */ /* nothing special to do */
rendermode_lighting.draw(data, state, src, mask); rendermode_lighting.draw(data, state, src, mask, mask_light);
} }
RenderModeInterface rendermode_night = { RenderModeInterface rendermode_night = {

View File

@@ -56,6 +56,7 @@ rendermode_normal_start(void *data, RenderState *state) {
self->leaf_texture = NULL; self->leaf_texture = NULL;
self->grass_texture = NULL; self->grass_texture = NULL;
self->tall_grass_texture = NULL;
self->facemask_top = NULL; self->facemask_top = NULL;
} else { } else {
@@ -64,6 +65,8 @@ rendermode_normal_start(void *data, RenderState *state) {
self->leaf_texture = PyObject_GetAttrString(state->textures, "biome_leaf_texture"); self->leaf_texture = PyObject_GetAttrString(state->textures, "biome_leaf_texture");
self->grass_texture = PyObject_GetAttrString(state->textures, "biome_grass_texture"); self->grass_texture = PyObject_GetAttrString(state->textures, "biome_grass_texture");
self->tall_grass_texture = PyObject_GetAttrString(state->textures, "biome_tall_grass_texture");
self->tall_fern_texture = PyObject_GetAttrString(state->textures, "biome_tall_fern_texture");
facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks"); facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks");
/* borrowed reference, needs to be incref'd if we keep it */ /* borrowed reference, needs to be incref'd if we keep it */
@@ -78,6 +81,8 @@ rendermode_normal_start(void *data, RenderState *state) {
self->leaf_texture = NULL; self->leaf_texture = NULL;
self->grass_texture = NULL; self->grass_texture = NULL;
self->tall_grass_texture = NULL;
self->tall_fern_texture = NULL;
self->facemask_top = NULL; self->facemask_top = NULL;
} }
@@ -98,6 +103,8 @@ rendermode_normal_finish(void *data, RenderState *state) {
Py_XDECREF(self->grasscolor); Py_XDECREF(self->grasscolor);
Py_XDECREF(self->leaf_texture); Py_XDECREF(self->leaf_texture);
Py_XDECREF(self->grass_texture); Py_XDECREF(self->grass_texture);
Py_XDECREF(self->tall_grass_texture);
Py_XDECREF(self->tall_fern_texture);
Py_XDECREF(self->facemask_top); Py_XDECREF(self->facemask_top);
} }
@@ -116,12 +123,21 @@ rendermode_normal_occluded(void *data, RenderState *state) {
} }
static void static void
rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
RenderModeNormal *self = (RenderModeNormal *)data; RenderModeNormal *self = (RenderModeNormal *)data;
/* first, check to see if we should use biome-compatible src, mask */ /* first, check to see if we should use biome-compatible src, mask */
if (self->biome_data && state->block == 18) { if (self->biome_data) {
src = mask = self->leaf_texture; if (state->block == 18) {
src = mask = self->leaf_texture;
} else if (state->block == 31) {
unsigned char data = getArrayByte3D(state->blockdata_expanded, state->x, state->y, state->z);
if (data == 1) {
src = mask = self->tall_grass_texture;
} else if (data == 2) {
src = mask = self->tall_fern_texture;
}
}
} }
/* draw the block! */ /* draw the block! */
@@ -150,6 +166,15 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *
color = PySequence_GetItem(self->foliagecolor, index); color = PySequence_GetItem(self->foliagecolor, index);
facemask = mask; facemask = mask;
break; break;
case 31:
/* tall grass */
if ( getArrayByte3D(state->blockdata_expanded, state->x, state->y, state->z) != 0 )
{ /* do not tint dead shrubs */
color = PySequence_GetItem(self->grasscolor, index);
facemask = mask;
break;
}
break;
default: default:
break; break;
}; };

View File

@@ -71,7 +71,7 @@ rendermode_overlay_occluded(void *data, RenderState *state) {
} }
static void static void
rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
RenderModeOverlay *self = (RenderModeOverlay *)data; RenderModeOverlay *self = (RenderModeOverlay *)data;
unsigned char r, g, b, a; unsigned char r, g, b, a;
PyObject *top_block_py, *block_py; PyObject *top_block_py, *block_py;

View File

@@ -102,9 +102,9 @@ rendermode_spawn_occluded(void *data, RenderState *state) {
} }
static void static void
rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) { rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
/* draw normally */ /* draw normally */
rendermode_overlay.draw(data, state, src, mask); rendermode_overlay.draw(data, state, src, mask, mask_light);
} }
RenderModeInterface rendermode_spawn = { RenderModeInterface rendermode_spawn = {

View File

@@ -56,7 +56,7 @@ struct _RenderModeInterface {
/* returns non-zero to skip rendering this block */ /* returns non-zero to skip rendering this block */
int (*occluded)(void *, RenderState *); int (*occluded)(void *, RenderState *);
/* last two arguments are img and mask, from texture lookup */ /* last two arguments are img and mask, from texture lookup */
void (*draw)(void *, RenderState *, PyObject *, PyObject *); void (*draw)(void *, RenderState *, PyObject *, PyObject *, PyObject *);
}; };
/* figures out the render mode to use from the given ChunkRenderer */ /* figures out the render mode to use from the given ChunkRenderer */
@@ -79,7 +79,7 @@ typedef struct {
/* grasscolor and foliagecolor lookup tables */ /* grasscolor and foliagecolor lookup tables */
PyObject *grasscolor, *foliagecolor; PyObject *grasscolor, *foliagecolor;
/* biome-compatible grass/leaf textures */ /* biome-compatible grass/leaf textures */
PyObject *grass_texture, *leaf_texture; PyObject *grass_texture, *leaf_texture, *tall_grass_texture, *tall_fern_texture;
/* top facemask for grass biome tinting */ /* top facemask for grass biome tinting */
PyObject *facemask_top; PyObject *facemask_top;
} RenderModeNormal; } RenderModeNormal;

View File

@@ -26,11 +26,14 @@ from PIL import Image, ImageEnhance, ImageOps, ImageDraw
import util import util
import composite import composite
_find_file_local_path = None
def _find_file(filename, mode="rb"): def _find_file(filename, mode="rb"):
"""Searches for the given file and returns an open handle to it. """Searches for the given file and returns an open handle to it.
This searches the following locations in this order: This searches the following locations in this order:
* the textures_path given in the config file (if present)
* The program dir (same dir as this file) * The program dir (same dir as this file)
* The program dir / textures
* On Darwin, in /Applications/Minecraft * On Darwin, in /Applications/Minecraft
* Inside minecraft.jar, which is looked for at these locations * Inside minecraft.jar, which is looked for at these locations
@@ -38,14 +41,21 @@ def _find_file(filename, mode="rb"):
* On Darwin, at $HOME/Library/Application Support/minecraft/bin/minecraft.jar * On Darwin, at $HOME/Library/Application Support/minecraft/bin/minecraft.jar
* at $HOME/.minecraft/bin/minecraft.jar * at $HOME/.minecraft/bin/minecraft.jar
* The current working directory
* The program dir / textures
""" """
if _find_file_local_path:
path = os.path.join(_find_file_local_path, filename)
if os.path.exists(path):
return open(path, mode)
programdir = util.get_program_path() programdir = util.get_program_path()
path = os.path.join(programdir, filename) path = os.path.join(programdir, filename)
if os.path.exists(path): if os.path.exists(path):
return open(path, mode) return open(path, mode)
path = os.path.join(programdir, "textures", filename)
if os.path.exists(path):
return open(path, mode)
if sys.platform == "darwin": if sys.platform == "darwin":
path = os.path.join("/Applications/Minecraft", filename) path = os.path.join("/Applications/Minecraft", filename)
@@ -73,14 +83,6 @@ def _find_file(filename, mode="rb"):
except (KeyError, IOError): except (KeyError, IOError):
pass pass
path = filename
if os.path.exists(path):
return open(path, mode)
path = os.path.join(programdir, "textures", filename)
if os.path.exists(path):
return open(path, mode)
raise IOError("Could not find the file {0}. Is Minecraft installed? If so, I couldn't find the minecraft.jar file.".format(filename)) raise IOError("Could not find the file {0}. Is Minecraft installed? If so, I couldn't find the minecraft.jar file.".format(filename))
def _load_image(filename): def _load_image(filename):
@@ -112,9 +114,6 @@ def _split_terrain(terrain):
return textures return textures
# This maps terainids to 16x16 images
terrain_images = _split_terrain(_get_terrain_image())
def transform_image(img, blockID=None): 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
@@ -258,7 +257,7 @@ 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
if blockID in (37,38,6,39,40,83,30): ## flowers, sapling, mushrooms, reeds, web if blockID in (31,32,37,38,6,39,40,83,30): ## tall grass, dead shrubs, flowers, sapling, mushrooms, reeds, web
# #
# 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
@@ -411,7 +410,7 @@ def _build_blockimages():
# 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
34, -1, 52, 48, 49,160,144, -1,176, 74, -1, -1, -1, -1, 11, -1, 34, -1, 52, 48, 49,160,144, -1,176, 74, -1, -1, -1, -1, 11, -1,
# 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 # 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
-1, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 7, 9, 4, 55, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 7, 9, 4,
# 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, -1, -1, 65, -1, -1, -1, 50, 24, -1, -1, 86, -1, -1, -1, 36, 37, -1, -1, 65, -1, -1, -1, 50, 24, -1, -1, 86, -1, -1, -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
@@ -428,7 +427,7 @@ def _build_blockimages():
# 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 # 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
34, -1, 52, 48, 49,160,144, -1,192, 74, -1, -1,- 1, -1, 11, -1, 34, -1, 52, 48, 49,160,144, -1,192, 74, -1, -1,- 1, -1, 11, -1,
# 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 # 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
-1, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 7, 8, 35, 55, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 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, -1, -1, 65, -1, -1,101, 50, 24, -1, -1, 86, -1, -1, -1, 36, 37, -1, -1, 65, -1, -1,101, 50, 24, -1, -1, 86, -1, -1, -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
@@ -454,13 +453,12 @@ def _build_blockimages():
## of the block or the texture ID ## of the block or the texture ID
img = _build_block(toptexture, sidetexture, blockID) img = _build_block(toptexture, sidetexture, blockID)
allimages.append((img.convert("RGB"), img.split()[3])) allimages.append(generate_texture_tuple(img, blockID))
# Future block types: # Future block types:
while len(allimages) < 256: while len(allimages) < 256:
allimages.append(None) allimages.append(None)
return allimages return allimages
blockmap = _build_blockimages()
def load_water(): def load_water():
"""Evidentially, the water and lava textures are not loaded from any files """Evidentially, the water and lava textures are not loaded from any files
@@ -474,16 +472,33 @@ def load_water():
watertexture = _load_image("water.png") watertexture = _load_image("water.png")
w1 = _build_block(watertexture, None) w1 = _build_block(watertexture, None)
blockmap[9] = w1.convert("RGB"), w1 blockmap[9] = generate_texture_tuple(w1,9)
w2 = _build_block(watertexture, watertexture) w2 = _build_block(watertexture, watertexture)
blockmap[8] = w2.convert("RGB"), w2 blockmap[8] = generate_texture_tuple(w2,8)
lavatexture = _load_image("lava.png") lavatexture = _load_image("lava.png")
lavablock = _build_block(lavatexture, lavatexture) lavablock = _build_block(lavatexture, lavatexture)
blockmap[10] = lavablock.convert("RGB"), lavablock blockmap[10] = generate_texture_tuple(lavablock,10)
blockmap[11] = blockmap[10] blockmap[11] = blockmap[10]
load_water()
def generate_opaque_mask(img):
""" Takes the alpha channel of the image and generates a mask
(used for lighting the block) that deprecates values of alpha
smallers than 50, and sets every other value to 255. """
alpha = img.split()[3]
pixel = alpha.load()
for x in range(img.size[0]):
for y in range(img.size[1]):
if pixel[x,y] > 25:
pixel[x,y] = 255
return alpha
def generate_texture_tuple(img, blockid):
""" This takes an image and returns the needed tuple for the
blockmap list and specialblockmap dictionary."""
return (img.convert("RGB"), img.split()[3], generate_opaque_mask(img))
def generate_special_texture(blockID, data): def generate_special_texture(blockID, data):
"""Generates a special texture, such as a correctly facing minecraft track""" """Generates a special texture, such as a correctly facing minecraft track"""
@@ -500,7 +515,7 @@ def generate_special_texture(blockID, data):
if not data & 0x10: if not data & 0x10:
colored = tintTexture(biome_grass_texture, (115, 175, 71)) colored = tintTexture(biome_grass_texture, (115, 175, 71))
composite.alpha_over(img, colored, (0, 0), colored) composite.alpha_over(img, colored, (0, 0), colored)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 6: # saplings if blockID == 6: # saplings
@@ -524,7 +539,7 @@ def generate_special_texture(blockID, data):
sidetexture = terrain_images[15] sidetexture = terrain_images[15]
img = _build_block(toptexture, sidetexture, blockID) img = _build_block(toptexture, sidetexture, blockID)
return (img.convert("RGB"),img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 9: # spring water, flowing water and waterfall water if blockID == 9: # spring water, flowing water and waterfall water
@@ -552,9 +567,9 @@ def generate_special_texture(blockID, data):
side4 = watertexture # bottom right side4 = watertexture # bottom right
else: side4 = None else: side4 = None
img = _build_full_block(top,side1,side2,side3,side4) img = _build_full_block(top,None,None,side3,side4)
return (img.convert("RGB"),img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 17: # wood: normal, birch and pines if blockID == 17: # wood: normal, birch and pines
@@ -562,21 +577,20 @@ def generate_special_texture(blockID, data):
if data == 0: if data == 0:
side = terrain_images[20] side = terrain_images[20]
img = _build_block(top, side, 17) img = _build_block(top, side, 17)
return (img.convert("RGB"), img.split()[3])
if data == 1: if data == 1:
side = terrain_images[116] side = terrain_images[116]
img = _build_block(top, side, 17) img = _build_block(top, side, 17)
return (img.convert("RGB"), img.split()[3])
if data == 2: if data == 2:
side = terrain_images[117] side = terrain_images[117]
img = _build_block(top, side, 17) img = _build_block(top, side, 17)
return (img.convert("RGB"), img.split()[3])
return generate_texture_tuple(img, blockID)
if blockID == 18: # leaves if blockID == 18: # leaves
t = tintTexture(terrain_images[52], (37, 118, 25)) t = tintTexture(terrain_images[52], (37, 118, 25))
img = _build_block(t, t, 18) img = _build_block(t, t, 18)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 26: # bed if blockID == 26: # bed
@@ -622,74 +636,58 @@ def generate_special_texture(blockID, data):
top = (top, increment) top = (top, increment)
img = _build_full_block(top, None, None, left_face, right_face) img = _build_full_block(top, None, None, left_face, right_face)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 31: # tall grass
if data == 0: # dead shrub
texture = terrain_images[55]
elif data == 1: # tall grass
texture = terrain_images[39].copy()
texture = tintTexture(texture, (115, 175, 71))
elif data == 2: # fern
texture = terrain_images[56].copy()
texture = tintTexture(texture, (115, 175, 71))
img = _build_block(texture, texture, blockID)
return generate_texture_tuple(img,31)
if blockID == 35: # wool if blockID == 35: # wool
if data == 0: # white if data == 0: # white
top = side = terrain_images[64] top = side = terrain_images[64]
img = _build_block(top, side, 35) elif data == 1: # orange
return (img.convert("RGB"), img.split()[3])
if data == 1: # orange
top = side = terrain_images[210] top = side = terrain_images[210]
img = _build_block(top, side, 35) elif data == 2: # magenta
return (img.convert("RGB"), img.split()[3])
if data == 2: # magenta
top = side = terrain_images[194] top = side = terrain_images[194]
img = _build_block(top, side, 35) elif data == 3: # light blue
return (img.convert("RGB"), img.split()[3])
if data == 3: # light blue
top = side = terrain_images[178] top = side = terrain_images[178]
img = _build_block(top, side, 35) elif data == 4: # yellow
return (img.convert("RGB"), img.split()[3])
if data == 4: # yellow
top = side = terrain_images[162] top = side = terrain_images[162]
img = _build_block(top, side, 35) elif data == 5: # light green
return (img.convert("RGB"), img.split()[3])
if data == 5: # light green
top = side = terrain_images[146] top = side = terrain_images[146]
img = _build_block(top, side, 35) elif data == 6: # pink
return (img.convert("RGB"), img.split()[3])
if data == 6: # pink
top = side = terrain_images[130] top = side = terrain_images[130]
img = _build_block(top, side, 35) elif data == 7: # grey
return (img.convert("RGB"), img.split()[3])
if data == 7: # grey
top = side = terrain_images[114] top = side = terrain_images[114]
img = _build_block(top, side, 35) elif data == 8: # light grey
return (img.convert("RGB"), img.split()[3])
if data == 8: # light grey
top = side = terrain_images[225] top = side = terrain_images[225]
img = _build_block(top, side, 35) elif data == 9: # cyan
return (img.convert("RGB"), img.split()[3])
if data == 9: # cyan
top = side = terrain_images[209] top = side = terrain_images[209]
img = _build_block(top, side, 35) elif data == 10: # purple
return (img.convert("RGB"), img.split()[3])
if data == 10: # purple
top = side = terrain_images[193] top = side = terrain_images[193]
img = _build_block(top, side, 35) elif data == 11: # blue
return (img.convert("RGB"), img.split()[3])
if data == 11: # blue
top = side = terrain_images[177] top = side = terrain_images[177]
img = _build_block(top, side, 35) elif data == 12: # brown
return (img.convert("RGB"), img.split()[3])
if data == 12: # brown
top = side = terrain_images[161] top = side = terrain_images[161]
img = _build_block(top, side, 35) elif data == 13: # dark green
return (img.convert("RGB"), img.split()[3])
if data == 13: # dark green
top = side = terrain_images[145] top = side = terrain_images[145]
img = _build_block(top, side, 35) elif data == 14: # red
return (img.convert("RGB"), img.split()[3])
if data == 14: # red
top = side = terrain_images[129] top = side = terrain_images[129]
img = _build_block(top, side, 35) elif data == 15: # black
return (img.convert("RGB"), img.split()[3])
if data == 15: # black
top = side = terrain_images[113] top = side = terrain_images[113]
img = _build_block(top, side, 35)
return (img.convert("RGB"), img.split()[3]) img = _build_block(top, side, 35)
return generate_texture_tuple(img, blockID)
if blockID in (43,44): # slab and double-slab if blockID in (43,44): # slab and double-slab
@@ -697,24 +695,16 @@ def generate_special_texture(blockID, data):
if data == 0: # stone slab if data == 0: # stone slab
top = terrain_images[6] top = terrain_images[6]
side = terrain_images[5] side = terrain_images[5]
img = _build_block(top, side, blockID)
return (img.convert("RGB"), img.split()[3])
if data == 1: # stone slab if data == 1: # stone slab
top = terrain_images[176] top = terrain_images[176]
side = terrain_images[192] side = terrain_images[192]
img = _build_block(top, side, blockID)
return (img.convert("RGB"), img.split()[3])
if data == 2: # wooden slab if data == 2: # wooden slab
top = side = terrain_images[4] top = side = terrain_images[4]
img = _build_block(top, side, blockID)
return (img.convert("RGB"), img.split()[3])
if data == 3: # cobblestone slab if data == 3: # cobblestone slab
top = side = terrain_images[16] top = side = terrain_images[16]
img = _build_block(top, side, blockID)
return (img.convert("RGB"), img.split()[3]) img = _build_block(top, side, blockID)
return generate_texture_tuple(img, blockID)
if blockID in (50,75,76): # torch, off redstone torch, on redstone torch if blockID in (50,75,76): # torch, off redstone torch, on redstone torch
@@ -767,7 +757,7 @@ def generate_special_texture(blockID, data):
composite.alpha_over(img, small_crop, (6,5)) composite.alpha_over(img, small_crop, (6,5))
composite.alpha_over(img, slice, (6,6)) composite.alpha_over(img, slice, (6,6))
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 51: # fire if blockID == 51: # fire
@@ -783,7 +773,7 @@ def generate_special_texture(blockID, data):
composite.alpha_over(img, side1, (0,6), side1) composite.alpha_over(img, side1, (0,6), side1)
composite.alpha_over(img, side2, (12,6), side2) composite.alpha_over(img, side2, (12,6), side2)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID in (53,67): # wooden and cobblestone stairs. if blockID in (53,67): # wooden and cobblestone stairs.
@@ -852,7 +842,7 @@ def generate_special_texture(blockID, data):
# touch up a (horrible) pixel # touch up a (horrible) pixel
img.putpixel((18,3),(0,0,0,0)) img.putpixel((18,3),(0,0,0,0))
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 54: # chests if blockID == 54: # chests
# First to bits of the pseudo data store if it's a single chest # First to bits of the pseudo data store if it's a single chest
@@ -894,7 +884,7 @@ def generate_special_texture(blockID, data):
else: else:
img = _build_full_block(top, None, None, back, side) img = _build_full_block(top, None, None, back, side)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 55: # redstone wire if blockID == 55: # redstone wire
@@ -972,7 +962,7 @@ def generate_special_texture(blockID, data):
img = _build_full_block(None,side1,side2,None,None,bottom) img = _build_full_block(None,side1,side2,None,None,bottom)
return (img.convert("RGB"),img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 58: # crafting table if blockID == 58: # crafting table
@@ -981,7 +971,7 @@ def generate_special_texture(blockID, data):
side4 = terrain_images[43+16+1] side4 = terrain_images[43+16+1]
img = _build_full_block(top, None, None, side3, side4, None, 58) img = _build_full_block(top, None, None, side3, side4, None, 58)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 59: # crops if blockID == 59: # crops
@@ -994,7 +984,7 @@ def generate_special_texture(blockID, data):
composite.alpha_over(img, crop1, (0,12), crop1) composite.alpha_over(img, crop1, (0,12), crop1)
composite.alpha_over(img, crop2, (6,3), crop2) composite.alpha_over(img, crop2, (6,3), crop2)
composite.alpha_over(img, crop3, (6,3), crop3) composite.alpha_over(img, crop3, (6,3), crop3)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID in (61, 62, 23): #furnace and burning furnace if blockID in (61, 62, 23): #furnace and burning furnace
@@ -1019,7 +1009,7 @@ def generate_special_texture(blockID, data):
else: # in any other direction the front can't be seen else: # in any other direction the front can't be seen
img = _build_full_block(top, None, None, side, side) img = _build_full_block(top, None, None, side, side)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 63: # singposts if blockID == 63: # singposts
@@ -1062,7 +1052,7 @@ def generate_special_texture(blockID, data):
composite.alpha_over(img, post2,(incrementx, -3),post2) composite.alpha_over(img, post2,(incrementx, -3),post2)
composite.alpha_over(img, post, (0,-2), post) composite.alpha_over(img, post, (0,-2), post)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID in (64,71): #wooden door, or iron door if blockID in (64,71): #wooden door, or iron door
@@ -1114,10 +1104,11 @@ def generate_special_texture(blockID, data):
tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT))
composite.alpha_over(img, tex, (0,6), tex) composite.alpha_over(img, tex, (0,6), tex)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 65: # ladder if blockID == 65: # ladder
img = Image.new("RGBA", (24,24), (38,92,255,0))
raw_texture = terrain_images[83] raw_texture = terrain_images[83]
#print "ladder is facing: %d" % data #print "ladder is facing: %d" % data
if data == 5: if data == 5:
@@ -1125,28 +1116,25 @@ def generate_special_texture(blockID, data):
# but since ladders can apparently be placed on transparent blocks, we # but since ladders can apparently be placed on transparent blocks, we
# have to render this thing anyway. same for data == 2 # have to render this thing anyway. same for data == 2
tex = transform_image_side(raw_texture) tex = transform_image_side(raw_texture)
img = Image.new("RGBA", (24,24), (38,92,255,0))
composite.alpha_over(img, tex, (0,6), tex) composite.alpha_over(img, tex, (0,6), tex)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if data == 2: if data == 2:
tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT)
img = Image.new("RGBA", (24,24), (38,92,255,0))
composite.alpha_over(img, tex, (12,6), tex) composite.alpha_over(img, tex, (12,6), tex)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if data == 3: if data == 3:
tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT) tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT)
img = Image.new("RGBA", (24,24), (38,92,255,0))
composite.alpha_over(img, tex, (0,0), tex) composite.alpha_over(img, tex, (0,0), tex)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if data == 4: if data == 4:
tex = transform_image_side(raw_texture) tex = transform_image_side(raw_texture)
img = Image.new("RGBA", (24,24), (38,92,255,0))
composite.alpha_over(img, tex, (12,0), tex) composite.alpha_over(img, tex, (12,0), tex)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID in (27, 28, 66): # minetrack: if blockID in (27, 28, 66): # minetrack:
img = Image.new("RGBA", (24,24), (38,92,255,0))
if blockID == 27: # powered rail if blockID == 27: # powered rail
if data & 0x8 == 0: # unpowered if data & 0x8 == 0: # unpowered
raw_straight = terrain_images[163] raw_straight = terrain_images[163]
@@ -1169,58 +1157,48 @@ def generate_special_texture(blockID, data):
## use transform_image to scale and shear ## use transform_image to scale and shear
if data == 0: if data == 0:
track = transform_image(raw_straight, blockID) track = transform_image(raw_straight, blockID)
composite.alpha_over(img, track, (0,12), track)
elif data == 6: elif data == 6:
track = transform_image(raw_corner, blockID) track = transform_image(raw_corner, blockID)
composite.alpha_over(img, track, (0,12), track)
elif data == 7: elif data == 7:
track = transform_image(raw_corner.rotate(270), blockID) track = transform_image(raw_corner.rotate(270), blockID)
composite.alpha_over(img, track, (0,12), track)
elif data == 8: elif data == 8:
# flip # flip
track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM).rotate(90), track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM).rotate(90),
blockID) blockID)
composite.alpha_over(img, track, (0,12), track)
elif data == 9: elif data == 9:
track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM), track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM),
blockID) blockID)
composite.alpha_over(img, track, (0,12), track)
elif data == 1: elif data == 1:
track = transform_image(raw_straight.rotate(90), blockID) track = transform_image(raw_straight.rotate(90), blockID)
composite.alpha_over(img, track, (0,12), track)
#slopes #slopes
elif data == 2: # slope going up in +x direction elif data == 2: # slope going up in +x direction
track = transform_image_slope(raw_straight,blockID) track = transform_image_slope(raw_straight,blockID)
track = track.transpose(Image.FLIP_LEFT_RIGHT) track = track.transpose(Image.FLIP_LEFT_RIGHT)
img = Image.new("RGBA", (24,24), (38,92,255,0))
composite.alpha_over(img, track, (2,0), track) composite.alpha_over(img, track, (2,0), track)
# the 2 pixels move is needed to fit with the adjacent tracks # the 2 pixels move is needed to fit with the adjacent tracks
return (img.convert("RGB"), img.split()[3])
elif data == 3: # slope going up in -x direction elif data == 3: # slope going up in -x direction
# tracks are sprites, in this case we are seeing the "side" of # tracks are sprites, in this case we are seeing the "side" of
# the sprite, so draw a line to make it beautiful. # the sprite, so draw a line to make it beautiful.
img = Image.new("RGBA", (24,24), (38,92,255,0))
ImageDraw.Draw(img).line([(11,11),(23,17)],fill=(164,164,164)) ImageDraw.Draw(img).line([(11,11),(23,17)],fill=(164,164,164))
# grey from track texture (exterior grey). # grey from track texture (exterior grey).
# the track doesn't start from image corners, be carefull drawing the line! # the track doesn't start from image corners, be carefull drawing the line!
return (img.convert("RGB"), img.split()[3])
elif data == 4: # slope going up in -y direction elif data == 4: # slope going up in -y direction
track = transform_image_slope(raw_straight,blockID) track = transform_image_slope(raw_straight,blockID)
img = Image.new("RGBA", (24,24), (38,92,255,0))
composite.alpha_over(img, track, (0,0), track) composite.alpha_over(img, track, (0,0), track)
return (img.convert("RGB"), img.split()[3])
elif data == 5: # slope going up in +y direction elif data == 5: # slope going up in +y direction
# same as "data == 3" # same as "data == 3"
img = Image.new("RGBA", (24,24), (38,92,255,0))
ImageDraw.Draw(img).line([(1,17),(12,11)],fill=(164,164,164)) ImageDraw.Draw(img).line([(1,17),(12,11)],fill=(164,164,164))
return (img.convert("RGB"), img.split()[3])
else: # just in case return generate_texture_tuple(img, blockID)
track = transform_image(raw_straight, blockID)
img = Image.new("RGBA", (24,24), (38,92,255,0))
composite.alpha_over(img, track, (0,12), track)
return (img.convert("RGB"), img.split()[3])
if blockID == 68: # wall sign if blockID == 68: # wall sign
@@ -1258,28 +1236,28 @@ def generate_special_texture(blockID, data):
composite.alpha_over(img, sign2,(incrementx, 2),sign2) composite.alpha_over(img, sign2,(incrementx, 2),sign2)
composite.alpha_over(img, sign, (0,3), sign) composite.alpha_over(img, sign, (0,3), sign)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 85: # fences if blockID == 85: # fences
# create needed images for Big stick fence # create needed images for Big stick fence
raw_texture = terrain_images[4]
raw_fence_top = Image.new("RGBA", (16,16), (38,92,255,0))
raw_fence_side = Image.new("RGBA", (16,16), (38,92,255,0))
fence_top_mask = Image.new("RGBA", (16,16), (38,92,255,0))
fence_side_mask = Image.new("RGBA", (16,16), (38,92,255,0))
# generate the masks images for textures of the fence
ImageDraw.Draw(fence_top_mask).rectangle((6,6,9,9),outline=(0,0,0),fill=(0,0,0))
ImageDraw.Draw(fence_side_mask).rectangle((6,1,9,15),outline=(0,0,0),fill=(0,0,0))
# create textures top and side for fence big stick fence_top = terrain_images[4].copy()
composite.alpha_over(raw_fence_top,raw_texture,(0,0),fence_top_mask) fence_side = terrain_images[4].copy()
composite.alpha_over(raw_fence_side,raw_texture,(0,0),fence_side_mask)
# generate the textures of the fence
ImageDraw.Draw(fence_top).rectangle((0,0,5,15),outline=(0,0,0,0),fill=(0,0,0,0))
ImageDraw.Draw(fence_top).rectangle((10,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0))
ImageDraw.Draw(fence_top).rectangle((0,0,15,5),outline=(0,0,0,0),fill=(0,0,0,0))
ImageDraw.Draw(fence_top).rectangle((0,10,15,15),outline=(0,0,0,0),fill=(0,0,0,0))
ImageDraw.Draw(fence_side).rectangle((0,0,15,0),outline=(0,0,0,0),fill=(0,0,0,0))
ImageDraw.Draw(fence_side).rectangle((0,0,5,15),outline=(0,0,0,0),fill=(0,0,0,0))
ImageDraw.Draw(fence_side).rectangle((10,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0))
# Create the sides and the top of the big stick # Create the sides and the top of the big stick
fence_side = transform_image_side(raw_fence_side,85) fence_side = transform_image_side(fence_side,85)
fence_other_side = fence_side.transpose(Image.FLIP_LEFT_RIGHT) fence_other_side = fence_side.transpose(Image.FLIP_LEFT_RIGHT)
fence_top = transform_image(raw_fence_top,85) fence_top = transform_image(fence_top,85)
# Darken the sides slightly. These methods also affect the alpha layer, # Darken the sides slightly. These methods also affect the alpha layer,
# so save them first (we don't want to "darken" the alpha layer making # so save them first (we don't want to "darken" the alpha layer making
@@ -1299,18 +1277,17 @@ def generate_special_texture(blockID, data):
# Now render the small sticks. # Now render the small sticks.
# Create needed images # Create needed images
raw_fence_small_side = Image.new("RGBA", (16,16), (38,92,255,0)) fence_small_side = terrain_images[4].copy()
fence_small_side_mask = Image.new("RGBA", (16,16), (38,92,255,0))
# Generate mask # Generate mask
ImageDraw.Draw(fence_small_side_mask).rectangle((10,1,15,3),outline=(0,0,0),fill=(0,0,0)) ImageDraw.Draw(fence_small_side).rectangle((0,0,15,0),outline=(0,0,0,0),fill=(0,0,0,0))
ImageDraw.Draw(fence_small_side_mask).rectangle((10,7,15,9),outline=(0,0,0),fill=(0,0,0)) ImageDraw.Draw(fence_small_side).rectangle((0,4,15,6),outline=(0,0,0,0),fill=(0,0,0,0))
ImageDraw.Draw(fence_small_side).rectangle((0,10,15,16),outline=(0,0,0,0),fill=(0,0,0,0))
# create the texture for the side of small sticks fence ImageDraw.Draw(fence_small_side).rectangle((0,0,4,15),outline=(0,0,0,0),fill=(0,0,0,0))
composite.alpha_over(raw_fence_small_side,raw_texture,(0,0),fence_small_side_mask) ImageDraw.Draw(fence_small_side).rectangle((11,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0))
# Create the sides and the top of the small sticks # Create the sides and the top of the small sticks
fence_small_side = transform_image_side(raw_fence_small_side,85) fence_small_side = transform_image_side(fence_small_side,85)
fence_small_other_side = fence_small_side.transpose(Image.FLIP_LEFT_RIGHT) fence_small_other_side = fence_small_side.transpose(Image.FLIP_LEFT_RIGHT)
# Darken the sides slightly. These methods also affect the alpha layer, # Darken the sides slightly. These methods also affect the alpha layer,
@@ -1323,18 +1300,16 @@ def generate_special_texture(blockID, data):
fence_small_side = ImageEnhance.Brightness(fence_small_side).enhance(0.9) fence_small_side = ImageEnhance.Brightness(fence_small_side).enhance(0.9)
fence_small_side.putalpha(sidealpha) fence_small_side.putalpha(sidealpha)
# Create img to compose the fence # Create img to compose the fence
img = Image.new("RGBA", (24,24), (38,92,255,0)) img = Image.new("RGBA", (24,24), (38,92,255,0))
# Position of fence small sticks in img. # Position of fence small sticks in img.
# These postitions are strange because the small sticks of the # These postitions are strange because the small sticks of the
# fence are at the very left and at the very right of the 16x16 images # fence are at the very left and at the very right of the 16x16 images
pos_top_left = (-2,0) pos_top_left = (2,3)
pos_top_right = (14,0) pos_top_right = (10,3)
pos_bottom_right = (6,4) pos_bottom_right = (10,7)
pos_bottom_left = (6,4) pos_bottom_left = (2,7)
# +x axis points top right direction # +x axis points top right direction
# +y axis points bottom right direction # +y axis points bottom right direction
@@ -1353,7 +1328,7 @@ def generate_special_texture(blockID, data):
if (data & 0b0100) == 4: if (data & 0b0100) == 4:
composite.alpha_over(img,fence_small_side, pos_bottom_right,fence_small_side) # bottom right composite.alpha_over(img,fence_small_side, pos_bottom_right,fence_small_side) # bottom right
return (img.convert("RGB"),img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID in (86,91): # pumpkins, jack-o-lantern if blockID in (86,91): # pumpkins, jack-o-lantern
@@ -1371,7 +1346,7 @@ def generate_special_texture(blockID, data):
else: # in any other direction the front can't be seen else: # in any other direction the front can't be seen
img = _build_full_block(top, None, None, side, side) img = _build_full_block(top, None, None, side, side)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 90: # portal if blockID == 90: # portal
@@ -1387,7 +1362,7 @@ def generate_special_texture(blockID, data):
if data in (2,8): if data in (2,8):
composite.alpha_over(img, otherside, (5,4), otherside) composite.alpha_over(img, otherside, (5,4), otherside)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 92: # cake! (without bites, at the moment) if blockID == 92: # cake! (without bites, at the moment)
@@ -1412,7 +1387,7 @@ def generate_special_texture(blockID, data):
composite.alpha_over(img, otherside, (12,12), otherside) composite.alpha_over(img, otherside, (12,12), otherside)
composite.alpha_over(img, top, (0,6), top) composite.alpha_over(img, top, (0,6), top)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID in (93, 94): # redstone repeaters, ON and OFF if blockID in (93, 94): # redstone repeaters, ON and OFF
@@ -1530,7 +1505,25 @@ def generate_special_texture(blockID, data):
composite.alpha_over(img, torch, static_torch, torch) composite.alpha_over(img, torch, static_torch, torch)
composite.alpha_over(img, torch, moving_torch, torch) composite.alpha_over(img, torch, moving_torch, torch)
return (img.convert("RGB"), img.split()[3]) return generate_texture_tuple(img, blockID)
if blockID == 96: # trapdoor
texture = terrain_images[84]
if data & 0x4 == 0x4: # opened trapdoor
if data & 0x3 == 0: # west
img = _build_full_block(None, None, None, None, texture)
if data & 0x3 == 1: # east
img = _build_full_block(None, texture, None, None, None)
if data & 0x3 == 2: # south
img = _build_full_block(None, None, texture, None, None)
if data & 0x3 == 3: # north
img = _build_full_block(None, None, None, texture, None)
elif data & 0x4 == 0: # closed trapdoor
img = _build_full_block((texture, 9), None, None, texture, texture)
return generate_texture_tuple(img, blockID)
return None return None
@@ -1541,11 +1534,6 @@ def tintTexture(im, c):
i.putalpha(im.split()[3]); # copy the alpha band back in. assuming RGBA i.putalpha(im.split()[3]); # copy the alpha band back in. assuming RGBA
return i return i
# generate biome (still grayscale) leaf, grass textures
biome_grass_texture = _build_block(terrain_images[0], terrain_images[38], 2)
biome_leaf_texture = _build_block(terrain_images[52], terrain_images[52], 18)
currentBiomeFile = None currentBiomeFile = None
currentBiomeData = None currentBiomeData = None
grasscolor = None grasscolor = None
@@ -1610,9 +1598,9 @@ def getBiomeData(worlddir, chunkX, chunkY):
# (when adding new blocks here and in generate_special_textures, # (when adding new blocks here and in generate_special_textures,
# please, if possible, keep the ascending order of blockid value) # please, if possible, keep the ascending order of blockid value)
special_blocks = set([ 2, 6, 9, 17, 18, 26, 23, 27, 28, 35, 43, 44, 50, special_blocks = set([ 2, 6, 9, 17, 18, 26, 23, 27, 28, 31, 35, 43, 44,
51, 53, 54, 55, 58, 59, 61, 62, 63, 64, 65, 66, 67, 50, 51, 53, 54, 55, 58, 59, 61, 62, 63, 64, 65, 66,
68, 71, 75, 76, 85, 86, 90, 91, 92, 93, 94]) 67, 68, 71, 75, 76, 85, 86, 90, 91, 92, 93, 94, 96])
# this is a map of special blockIDs to a list of all # this is a map of special blockIDs to a list of all
# possible values for ancillary data that it might have. # possible values for ancillary data that it might have.
@@ -1621,7 +1609,7 @@ special_map = {}
special_map[6] = range(16) # saplings: usual, spruce, birch and future ones (rendered as usual saplings) special_map[6] = range(16) # saplings: usual, spruce, birch and future ones (rendered as usual saplings)
special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values, uses pseudo data special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values, uses pseudo data
special_map[17] = range(4) # wood: normal, birch and pine special_map[17] = range(3) # wood: normal, birch and pine
special_map[26] = range(12) # bed, orientation special_map[26] = range(12) # bed, orientation
special_map[23] = range(6) # dispensers, orientation special_map[23] = range(6) # dispensers, orientation
special_map[27] = range(14) # powered rail, orientation/slope and powered/unpowered special_map[27] = range(14) # powered rail, orientation/slope and powered/unpowered
@@ -1654,6 +1642,7 @@ special_map[91] = range(5) # jack-o-lantern, orientation
special_map[92] = range(6) # cake! special_map[92] = range(6) # cake!
special_map[93] = range(16) # OFF redstone repeater, orientation and delay (delay not implemented) special_map[93] = range(16) # OFF redstone repeater, orientation and delay (delay not implemented)
special_map[94] = range(16) # ON redstone repeater, orientation and delay (delay not implemented) special_map[94] = range(16) # ON redstone repeater, orientation and delay (delay not implemented)
special_map[96] = range(8) # trapdoor, open, closed, orientation
# grass and leaves are graysacle in terrain.png # grass and leaves are graysacle in terrain.png
# we treat them as special so we can manually tint them # we treat them as special so we can manually tint them
@@ -1664,10 +1653,40 @@ special_map[2] = range(11) + [0x10,] # grass, grass has not ancildata but is
# small fix shows the map as expected, # small fix shows the map as expected,
# and is harmless for normal maps # and is harmless for normal maps
special_map[18] = range(16) # leaves, birch, normal or pine leaves (not implemented) special_map[18] = range(16) # leaves, birch, normal or pine leaves (not implemented)
special_map[31] = range(3) # tall grass, dead shrub, fern and tall grass itself
# placeholders that are generated in generate()
terrain_images = None
blockmap = None
biome_grass_texture = None
biome_tall_grass_texture = None
biome_tall_fern_texture = None
biome_leaf_texture = None
specialblockmap = None
specialblockmap = {} def generate(path=None):
global _find_file_local_path
for blockID in special_blocks: _find_file_local_path = path
for data in special_map[blockID]:
specialblockmap[(blockID, data)] = generate_special_texture(blockID, data) # This maps terainids to 16x16 images
global terrain_images
terrain_images = _split_terrain(_get_terrain_image())
# generate the normal blocks
global blockmap
blockmap = _build_blockimages()
load_water()
# generate biome (still grayscale) leaf, grass textures
global biome_grass_texture, biome_leaf_texture, biome_tall_grass_texture, biome_tall_fern_texture
biome_grass_texture = _build_block(terrain_images[0], terrain_images[38], 2)
biome_leaf_texture = _build_block(terrain_images[52], terrain_images[52], 18)
biome_tall_grass_texture = _build_block(terrain_images[39], terrain_images[39], 31)
biome_tall_fern_texture = _build_block(terrain_images[56], terrain_images[56], 31)
# generate the special blocks
global specialblockmap, special_blocks
specialblockmap = {}
for blockID in special_blocks:
for data in special_map[blockID]:
specialblockmap[(blockID, data)] = generate_special_texture(blockID, data)

View File

@@ -29,13 +29,13 @@ body {
font-family: monospace; font-family: monospace;
} }
#customControl { .customControl {
padding: 5px; padding: 5px;
height: 15px; height: 15px;
font-family: Arial, sans-serif; font-family: Arial, sans-serif;
} }
#customControl > div#top { .customControl > div.top {
background-color: #fff; background-color: #fff;
border: 2px solid #000; border: 2px solid #000;
text-align: center; text-align: center;
@@ -45,14 +45,14 @@ body {
cursor: pointer; cursor: pointer;
} }
#customControl > div#dropDown { .customControl > div.dropDown {
border: 1px solid #000; border: 1px solid #000;
font-size: 12px; font-size: 12px;
background-color: #fff; background-color: #fff;
display: none; display: none;
} }
#customControl > div#button { .customControl > div.button {
border: 1px solid #000; border: 1px solid #000;
font-size: 12px; font-size: 12px;
background-color: #fff; background-color: #fff;

View File

@@ -194,6 +194,7 @@ var overviewer = {
}, },
mapTypeId: overviewer.util.getDefaultMapTypeId(), mapTypeId: overviewer.util.getDefaultMapTypeId(),
streetViewControl: false, streetViewControl: false,
overviewMapControl: true,
zoomControl: overviewerConfig.map.controls.zoom, zoomControl: overviewerConfig.map.controls.zoom,
backgroundColor: overviewer.util.getMapTypeBackgroundColor( backgroundColor: overviewer.util.getMapTypeBackgroundColor(
overviewer.util.getDefaultMapTypeId()) overviewer.util.getDefaultMapTypeId())
@@ -225,27 +226,28 @@ var overviewer = {
overviewer.collections.mapTypes[i].name, overviewer.collections.mapTypes[i].name,
overviewer.collections.mapTypes[i]); overviewer.collections.mapTypes[i]);
} }
// Make the link again whenever the map changes // Jump to the hash if given
overviewer.util.initHash();
// Add live hash update listeners
// Note: It is important to add them after jumping to the hash
google.maps.event.addListener(overviewer.map, 'dragend', function() {
overviewer.util.updateHash();
});
google.maps.event.addListener(overviewer.map, 'zoom_changed', function() {
overviewer.util.updateHash();
});
// Make the link again whenever the map changes
google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function() { google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function() {
$('#'+overviewerConfig.CONST.mapDivId).css( $('#'+overviewerConfig.CONST.mapDivId).css(
'background-color', overviewer.util.getMapTypeBackgroundColor( 'background-color', overviewer.util.getMapTypeBackgroundColor(
overviewer.map.getMapTypeId())); overviewer.map.getMapTypeId()));
//smuggled this one in here for maptypeid hash generation --CounterPillow
overviewer.util.updateHash();
}); });
// Add live hash update listener
google.maps.event.addListener(overviewer.map, 'dragend', function() {
overviewer.util.updateHash();
});
google.maps.event.addListener(overviewer.map, 'zoom_changed', function() {
overviewer.util.updateHash();
});
// Jump to the hash if given
overviewer.util.initHash();
// We can now set the map to use the 'coordinate' map type
overviewer.map.setMapTypeId(overviewer.util.getDefaultMapTypeId());
}, },
/** /**
* Read through overviewer.collections.markerDatas and create Marker * Read through overviewer.collections.markerDatas and create Marker
@@ -558,8 +560,8 @@ var overviewer = {
// Adjust for the fact that we we can't figure out what Y is given // Adjust for the fact that we we can't figure out what Y is given
// only latitude and longitude, so assume Y=64. // only latitude and longitude, so assume Y=64.
point.x += 64 + 1; point.x += 64;
point.z -= 64 + 2; point.z -= 64;
return point; return point;
}, },
@@ -583,7 +585,7 @@ var overviewer = {
// Spawn button // Spawn button
var homeControlDiv = document.createElement('DIV'); var homeControlDiv = document.createElement('DIV');
var homeControl = new overviewer.classes.HomeControl(homeControlDiv); var homeControl = new overviewer.classes.HomeControl(homeControlDiv);
homeControlDiv.id = 'customControl'; $(homeControlDiv).addClass('customControl');
homeControlDiv.index = 1; homeControlDiv.index = 1;
if (overviewerConfig.map.controls.spawn) { if (overviewerConfig.map.controls.spawn) {
overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv); overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv);
@@ -678,18 +680,18 @@ var overviewer = {
'createDropDown': function(title, items) { 'createDropDown': function(title, items) {
var control = document.createElement('DIV'); var control = document.createElement('DIV');
// let's let a style sheet do most of the styling here // let's let a style sheet do most of the styling here
control.id = 'customControl'; $(control).addClass('customControl');
var controlText = document.createElement('DIV'); var controlText = document.createElement('DIV');
controlText.innerHTML = title; controlText.innerHTML = title;
var controlBorder = document.createElement('DIV'); var controlBorder = document.createElement('DIV');
controlBorder.id='top'; $(controlBorder).addClass('top');
control.appendChild(controlBorder); control.appendChild(controlBorder);
controlBorder.appendChild(controlText); controlBorder.appendChild(controlText);
var dropdownDiv = document.createElement('DIV'); var dropdownDiv = document.createElement('DIV');
dropdownDiv.id='dropDown'; $(dropdownDiv).addClass('dropDown');
control.appendChild(dropdownDiv); control.appendChild(dropdownDiv);
dropdownDiv.innerHTML=''; dropdownDiv.innerHTML='';
@@ -782,6 +784,8 @@ var overviewer = {
'initHash': function() { 'initHash': function() {
if(window.location.hash.split("/").length > 1) { if(window.location.hash.split("/").length > 1) {
overviewer.util.goToHash(); overviewer.util.goToHash();
// Clean up the hash.
overviewer.util.updateHash();
// Add a marker indicating the user-supplied position // Add a marker indicating the user-supplied position
var coordinates = overviewer.util.fromLatLngToWorld(overviewer.map.getCenter().lat(), overviewer.map.getCenter().lng()); var coordinates = overviewer.util.fromLatLngToWorld(overviewer.map.getCenter().lat(), overviewer.map.getCenter().lng());
@@ -791,14 +795,15 @@ var overviewer = {
'y': coordinates.y, 'y': coordinates.y,
'z': coordinates.z, 'z': coordinates.z,
'type': 'querypos'}]); 'type': 'querypos'}]);
} }
}, },
'setHash': function(x, y, z, zoom) { 'setHash': function(x, y, z, zoom, maptype) {
window.location.replace("#/" + Math.floor(x) + "/" + Math.floor(y) + "/" + Math.floor(z) + "/" + zoom); window.location.replace("#/" + Math.floor(x) + "/" + Math.floor(y) + "/" + Math.floor(z) + "/" + zoom + "/" + maptype);
}, },
'updateHash': function() { 'updateHash': function() {
var coordinates = overviewer.util.fromLatLngToWorld(overviewer.map.getCenter().lat(), overviewer.map.getCenter().lng()); var coordinates = overviewer.util.fromLatLngToWorld(overviewer.map.getCenter().lat(), overviewer.map.getCenter().lng());
var zoom = overviewer.map.getZoom(); var zoom = overviewer.map.getZoom();
var maptype = overviewer.map.getMapTypeId();
if (zoom == overviewerConfig.map.maxZoom) { if (zoom == overviewerConfig.map.maxZoom) {
zoom = 'max'; zoom = 'max';
} else if (zoom == overviewerConfig.map.minZoom) { } else if (zoom == overviewerConfig.map.minZoom) {
@@ -807,12 +812,22 @@ var overviewer = {
// default to (map-update friendly) negative zooms // default to (map-update friendly) negative zooms
zoom -= overviewerConfig.map.maxZoom; zoom -= overviewerConfig.map.maxZoom;
} }
overviewer.util.setHash(coordinates.x, coordinates.y, coordinates.z, zoom); overviewer.util.setHash(coordinates.x, coordinates.y, coordinates.z, zoom, maptype);
}, },
'goToHash': function() { 'goToHash': function() {
// Note: the actual data begins at coords[1], coords[0] is empty.
var coords = window.location.hash.split("/"); var coords = window.location.hash.split("/");
var latlngcoords = overviewer.util.fromWorldToLatLng(parseInt(coords[1]), parseInt(coords[2]), parseInt(coords[3])); var latlngcoords = overviewer.util.fromWorldToLatLng(parseInt(coords[1]), parseInt(coords[2]), parseInt(coords[3]));
var zoom = coords[4]; var zoom;
var maptype = '';
// The if-statements try to prevent unexpected behaviour when using incomplete hashes, e.g. older links
if (coords.length > 4) {
zoom = coords[4];
}
if (coords.length > 5) {
maptype = coords[5];
}
if (zoom == 'max') { if (zoom == 'max') {
zoom = overviewerConfig.map.maxZoom; zoom = overviewerConfig.map.maxZoom;
} else if (zoom == 'min') { } else if (zoom == 'min') {
@@ -827,6 +842,14 @@ var overviewer = {
zoom = overviewerConfig.map.defaultZoom; zoom = overviewerConfig.map.defaultZoom;
} }
} }
// If the maptype isn't set, set the default one.
if (maptype == '') {
// We can now set the map to use the 'coordinate' map type
overviewer.map.setMapTypeId(overviewer.util.getDefaultMapTypeId());
} else {
overviewer.map.setMapTypeId(maptype);
}
overviewer.map.setCenter(latlngcoords); overviewer.map.setCenter(latlngcoords);
overviewer.map.setZoom(zoom); overviewer.map.setZoom(zoom);
}, },
@@ -845,14 +868,14 @@ var overviewer = {
controlDiv.style.padding = '5px'; controlDiv.style.padding = '5px';
// Set CSS for the control border // Set CSS for the control border
var control = document.createElement('DIV'); var control = document.createElement('DIV');
control.id='top'; $(control).addClass('top');
control.title = 'Click to center the map on the Spawn'; control.title = 'Click to center the map on the Spawn';
controlDiv.appendChild(control); controlDiv.appendChild(control);
// Set CSS for the control interior // Set CSS for the control interior
var controlText = document.createElement('DIV'); var controlText = document.createElement('DIV');
controlText.innerHTML = 'Spawn'; controlText.innerHTML = 'Spawn';
controlText.id='button'; $(controlText).addClass('button');
control.appendChild(controlText); control.appendChild(controlText);
// Setup the click event listeners: simply set the map to map center // Setup the click event listeners: simply set the map to map center

View File

@@ -79,7 +79,7 @@ var overviewerConfig = {
* Set to true to turn on debug mode, which adds a grid to the map along * Set to true to turn on debug mode, which adds a grid to the map along
* with co-ordinates and a bunch of console output. * with co-ordinates and a bunch of console output.
*/ */
'debug': false, 'debug': false
}, },
/** /**
* Group definitions for objects that are partially selectable (signs and * Group definitions for objects that are partially selectable (signs and

View File

@@ -78,7 +78,8 @@ class World(object):
logging.info("Scanning regions") logging.info("Scanning regions")
regionfiles = {} regionfiles = {}
self.regions = {} self.regions = {}
for x, y, regionfile in self._iterate_regionfiles(): self.regionlist = regionlist # a list of paths
for x, y, regionfile in self._iterate_regionfiles():
mcr = self.reload_region(regionfile) mcr = self.reload_region(regionfile)
mcr.get_chunk_info() mcr.get_chunk_info()
regionfiles[(x,y)] = (x,y,regionfile,mcr) regionfiles[(x,y)] = (x,y,regionfile,mcr)
@@ -99,9 +100,6 @@ class World(object):
logging.error("Sorry, This version of Minecraft-Overviewer only works with the new McRegion chunk format") logging.error("Sorry, This version of Minecraft-Overviewer only works with the new McRegion chunk format")
sys.exit(1) sys.exit(1)
if self.useBiomeData:
textures.prepareBiomeData(worlddir)
# stores Points Of Interest to be mapped with markers # stores Points Of Interest to be mapped with markers
# a list of dictionaries, see below for an example # a list of dictionaries, see below for an example
self.POI = [] self.POI = []
@@ -277,16 +275,22 @@ class World(object):
"""Returns an iterator of all of the region files, along with their """Returns an iterator of all of the region files, along with their
coordinates coordinates
Note: the regionlist here will be used to determinte the size of the
world.
Returns (regionx, regiony, filename)""" Returns (regionx, regiony, filename)"""
join = os.path.join join = os.path.join
if regionlist is not None: if regionlist is not None:
for path in regionlist: for path in regionlist:
if path.endswith("\n"): path = path.strip()
path = path[:-1]
f = os.path.basename(path) f = os.path.basename(path)
if f.startswith("r.") and f.endswith(".mcr"): if f.startswith("r.") and f.endswith(".mcr"):
p = f.split(".") p = f.split(".")
logging.debug("Using path %s from regionlist", f)
yield (int(p[1]), int(p[2]), join(self.worlddir, 'region', f)) yield (int(p[1]), int(p[2]), join(self.worlddir, 'region', f))
else:
logging.warning("Ignore path '%s' in regionlist", f)
else: else:
for path in glob(os.path.join(self.worlddir, 'region') + "/r.*.*.mcr"): for path in glob(os.path.join(self.worlddir, 'region') + "/r.*.*.mcr"):
dirpath, f = os.path.split(path) dirpath, f = os.path.split(path)