Merge branch 'lighting' of http://github.com/agrif/Minecraft-Overviewer into lighting
This commit is contained in:
14
README.rst
14
README.rst
@@ -190,6 +190,18 @@ Options
|
||||
a certain date. Or perhaps you can incrementally update your map by passing
|
||||
in a subset of chunks each time. It's up to you!
|
||||
|
||||
--lighting
|
||||
This option enables map lighting, using lighting information stored by
|
||||
Minecraft inside the chunks. This will make your map prettier, at the cost
|
||||
of update speed.
|
||||
|
||||
Note that for existing, unlit maps, you may want to clear your cache
|
||||
(with -d) before updating the map to use lighting. Otherwise, only updated
|
||||
chunks will have lighting enabled.
|
||||
|
||||
--night
|
||||
This option enables --lighting, and renders the world at night.
|
||||
|
||||
Viewing the Results
|
||||
-------------------
|
||||
Within the output directory you will find two things: an index.html file, and a
|
||||
@@ -247,8 +259,6 @@ An incomplete list of things I want to do soon is:
|
||||
doors, and the like. Right now they are either not rendered at all, or
|
||||
rendered as if they were a cube, so it looks funny.
|
||||
|
||||
* Add lighting
|
||||
|
||||
* Some kind of graphical interface.
|
||||
|
||||
* A Windows exe for easier access for Windows users.
|
||||
|
||||
292
chunk.py
292
chunk.py
@@ -14,13 +14,14 @@
|
||||
# with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import numpy
|
||||
from PIL import Image, ImageDraw
|
||||
from PIL import Image, ImageDraw, ImageEnhance
|
||||
import os.path
|
||||
import hashlib
|
||||
import logging
|
||||
|
||||
import nbt
|
||||
import textures
|
||||
import world
|
||||
|
||||
"""
|
||||
This module has routines related to rendering one particular chunk into an
|
||||
@@ -54,11 +55,26 @@ def get_blockarray_fromfile(filename):
|
||||
return get_blockarray(level)
|
||||
|
||||
def get_skylight_array(level):
|
||||
"""Returns the skylight array. Remember this is 4 bits per block, so divide
|
||||
the z component by 2 when accessing the array. and mask off the top or
|
||||
bottom 4 bits if it's odd or even respectively
|
||||
"""
|
||||
return numpy.frombuffer(level['SkyLight'], dtype=numpy.uint8).reshape((16,16,64))
|
||||
"""Returns the skylight array. This is 4 bits per block, but it is
|
||||
expanded for you so you may index it normally."""
|
||||
skylight = numpy.frombuffer(level['SkyLight'], dtype=numpy.uint8).reshape((16,16,64))
|
||||
# this array is 2 blocks per byte, so expand it
|
||||
skylight_expanded = numpy.empty((16,16,128), dtype=numpy.uint8)
|
||||
# Even elements get the lower 4 bits
|
||||
skylight_expanded[:,:,::2] = skylight & 0x0F
|
||||
# Odd elements get the upper 4 bits
|
||||
skylight_expanded[:,:,1::2] = (skylight & 0xF0) >> 4
|
||||
return skylight_expanded
|
||||
|
||||
def get_blocklight_array(level):
|
||||
"""Returns the blocklight array. This is 4 bits per block, but it
|
||||
is expanded for you so you may index it normally."""
|
||||
# expand just like get_skylight_array()
|
||||
blocklight = numpy.frombuffer(level['BlockLight'], dtype=numpy.uint8).reshape((16,16,64))
|
||||
blocklight_expanded = numpy.empty((16,16,128), dtype=numpy.uint8)
|
||||
blocklight_expanded[:,:,::2] = blocklight & 0x0F
|
||||
blocklight_expanded[:,:,1::2] = (blocklight & 0xF0) >> 4
|
||||
return blocklight_expanded
|
||||
|
||||
def get_blockdata_array(level):
|
||||
"""Returns the ancillary data from the 'Data' byte array. Data is packed
|
||||
@@ -84,12 +100,12 @@ def iterate_chunkblocks(xoff,yoff):
|
||||
transparent_blocks = set([0, 6, 8, 9, 18, 20, 37, 38, 39, 40, 44, 50, 51, 52, 53,
|
||||
59, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 81, 83, 85])
|
||||
|
||||
def render_and_save(chunkfile, cachedir, cave=False):
|
||||
def render_and_save(chunkfile, cachedir, worldobj, cave=False):
|
||||
"""Used as the entry point for the multiprocessing workers (since processes
|
||||
can't target bound methods) or to easily render and save one chunk
|
||||
|
||||
Returns the image file location"""
|
||||
a = ChunkRenderer(chunkfile, cachedir)
|
||||
a = ChunkRenderer(chunkfile, cachedir, worldobj)
|
||||
try:
|
||||
return a.render_and_save(cave)
|
||||
except ChunkCorrupt:
|
||||
@@ -112,7 +128,7 @@ class ChunkCorrupt(Exception):
|
||||
pass
|
||||
|
||||
class ChunkRenderer(object):
|
||||
def __init__(self, chunkfile, cachedir):
|
||||
def __init__(self, chunkfile, cachedir, worldobj):
|
||||
"""Make a new chunk renderer for the given chunkfile.
|
||||
chunkfile should be a full path to the .dat file to process
|
||||
cachedir is a directory to save the resulting chunk images to
|
||||
@@ -121,7 +137,11 @@ class ChunkRenderer(object):
|
||||
raise ValueError("Could not find chunkfile")
|
||||
self.chunkfile = chunkfile
|
||||
destdir, filename = os.path.split(self.chunkfile)
|
||||
self.blockid = ".".join(filename.split(".")[1:3])
|
||||
|
||||
chunkcoords = filename.split(".")[1:3]
|
||||
self.coords = map(world.base36decode, chunkcoords)
|
||||
self.blockid = ".".join(chunkcoords)
|
||||
self.world = worldobj
|
||||
|
||||
# Cachedir here is the base directory of the caches. We need to go 2
|
||||
# levels deeper according to the chunk file. Get the last 2 components
|
||||
@@ -155,6 +175,88 @@ class ChunkRenderer(object):
|
||||
self._blocks = get_blockarray(self._load_level())
|
||||
return self._blocks
|
||||
blocks = property(_load_blocks)
|
||||
|
||||
def _load_skylight(self):
|
||||
"""Loads and returns skylight array"""
|
||||
if not hasattr(self, "_skylight"):
|
||||
self._skylight = get_skylight_array(self.level)
|
||||
return self._skylight
|
||||
skylight = property(_load_skylight)
|
||||
|
||||
def _load_blocklight(self):
|
||||
"""Loads and returns blocklight array"""
|
||||
if not hasattr(self, "_blocklight"):
|
||||
self._blocklight = get_blocklight_array(self.level)
|
||||
return self._blocklight
|
||||
blocklight = property(_load_blocklight)
|
||||
|
||||
def _load_left(self):
|
||||
"""Loads and sets data from lower-left chunk"""
|
||||
chunk_path = self.world.get_chunk_path(self.coords[0] - 1, self.coords[1])
|
||||
try:
|
||||
chunk_data = get_lvldata(chunk_path)
|
||||
self._left_skylight = get_skylight_array(chunk_data)
|
||||
self._left_blocklight = get_blocklight_array(chunk_data)
|
||||
self._left_blocks = get_blockarray(chunk_data)
|
||||
except IOError:
|
||||
self._left_skylight = None
|
||||
self._left_blocklight = None
|
||||
self._left_blocks = None
|
||||
|
||||
def _load_left_blocks(self):
|
||||
"""Loads and returns lower-left block array"""
|
||||
if not hasattr(self, "_left_blocks"):
|
||||
self._load_left()
|
||||
return self._left_blocks
|
||||
left_blocks = property(_load_left_blocks)
|
||||
|
||||
def _load_left_skylight(self):
|
||||
"""Loads and returns lower-left skylight array"""
|
||||
if not hasattr(self, "_left_skylight"):
|
||||
self._load_left()
|
||||
return self._left_skylight
|
||||
left_skylight = property(_load_left_skylight)
|
||||
|
||||
def _load_left_blocklight(self):
|
||||
"""Loads and returns lower-left blocklight array"""
|
||||
if not hasattr(self, "_left_blocklight"):
|
||||
self._load_left()
|
||||
return self._left_blocklight
|
||||
left_blocklight = property(_load_left_blocklight)
|
||||
|
||||
def _load_right(self):
|
||||
"""Loads and sets data from lower-right chunk"""
|
||||
chunk_path = self.world.get_chunk_path(self.coords[0], self.coords[1] + 1)
|
||||
try:
|
||||
chunk_data = get_lvldata(chunk_path)
|
||||
self._right_skylight = get_skylight_array(chunk_data)
|
||||
self._right_blocklight = get_blocklight_array(chunk_data)
|
||||
self._right_blocks = get_blockarray(chunk_data)
|
||||
except IOError:
|
||||
self._right_skylight = None
|
||||
self._right_blocklight = None
|
||||
self._right_blocks = None
|
||||
|
||||
def _load_right_blocks(self):
|
||||
"""Loads and returns lower-right block array"""
|
||||
if not hasattr(self, "_right_blocks"):
|
||||
self._load_right()
|
||||
return self._right_blocks
|
||||
right_blocks = property(_load_right_blocks)
|
||||
|
||||
def _load_right_skylight(self):
|
||||
"""Loads and returns lower-right skylight array"""
|
||||
if not hasattr(self, "_right_skylight"):
|
||||
self._load_right()
|
||||
return self._right_skylight
|
||||
right_skylight = property(_load_right_skylight)
|
||||
|
||||
def _load_right_blocklight(self):
|
||||
"""Loads and returns lower-right blocklight array"""
|
||||
if not hasattr(self, "_right_blocklight"):
|
||||
self._load_right()
|
||||
return self._right_blocklight
|
||||
right_blocklight = property(_load_right_blocklight)
|
||||
|
||||
def _hash_blockarray(self):
|
||||
"""Finds a hash of the block array"""
|
||||
@@ -243,6 +345,108 @@ class ChunkRenderer(object):
|
||||
# Return its location
|
||||
return dest_path
|
||||
|
||||
def calculate_darkness(self, skylight, blocklight):
|
||||
"""Takes a raw blocklight and skylight, and returns a value
|
||||
between 0.0 (fully lit) and 1.0 (fully black) that can be used as
|
||||
an alpha value for a blend with a black source image. It mimics
|
||||
Minecraft lighting calculations."""
|
||||
if not self.world.night:
|
||||
# Daytime
|
||||
return 1.0 - pow(0.8, 15 - max(blocklight, skylight))
|
||||
else:
|
||||
# Nighttime
|
||||
return 1.0 - pow(0.8, 15 - max(blocklight, skylight - 11))
|
||||
|
||||
def get_lighting_coefficient(self, x, y, z, norecurse=False):
|
||||
"""Calculates the lighting coefficient for the given
|
||||
coordinate, using default lighting and peeking into
|
||||
neighboring chunks, if needed. A lighting coefficient of 1.0
|
||||
means fully black.
|
||||
|
||||
Returns a tuple (coefficient, occluded), where occluded is
|
||||
True if the given coordinate is filled with a solid block, and
|
||||
therefore the returned coefficient is just the default."""
|
||||
|
||||
# placeholders for later data arrays, coordinates
|
||||
blocks = None
|
||||
skylight = None
|
||||
blocklight = None
|
||||
local_x = x
|
||||
local_y = y
|
||||
local_z = z
|
||||
is_local_chunk = False
|
||||
|
||||
# find out what chunk we're in, and translate accordingly
|
||||
if x >= 0 and y < 16:
|
||||
blocks = self.blocks
|
||||
skylight = self.skylight
|
||||
blocklight = self.blocklight
|
||||
is_local_chunk = True
|
||||
elif x < 0:
|
||||
local_x += 16
|
||||
blocks = self.left_blocks
|
||||
skylight = self.left_skylight
|
||||
blocklight = self.left_blocklight
|
||||
elif y >= 16:
|
||||
local_y -= 16
|
||||
blocks = self.right_blocks
|
||||
skylight = self.right_skylight
|
||||
blocklight = self.right_blocklight
|
||||
|
||||
# make sure we have a correctly-ranged coordinates and enough
|
||||
# info about the chunk
|
||||
if not (blocks != None and skylight != None and blocklight != None and
|
||||
local_x >= 0 and local_x < 16 and local_y >= 0 and local_y < 16 and
|
||||
local_z >= 0 and local_z < 128):
|
||||
# we have no useful info, return default
|
||||
return (self.calculate_darkness(15, 0), False)
|
||||
|
||||
blocktype = blocks[local_x, local_y, local_z]
|
||||
|
||||
# special handling for half-blocks
|
||||
# (don't recurse more than once!)
|
||||
if blocktype == 44 and not norecurse:
|
||||
# average gathering variables
|
||||
averagegather = 0.0
|
||||
averagecount = 0
|
||||
|
||||
# how bright we need before we consider a side "lit"
|
||||
threshold = self.calculate_darkness(0, 0)
|
||||
# iterate through all the sides of the block
|
||||
sides = [(x-1, y, z), (x+1, y, z), (x, y, z-1), (x, y, z+1), (x, y-1, z), (x, y+1, z)]
|
||||
|
||||
for side in sides:
|
||||
val, occ = self.get_lighting_coefficient(*side, norecurse=True)
|
||||
if (not occ) and (val < threshold):
|
||||
averagegather += val
|
||||
averagecount += 1
|
||||
|
||||
# if at least one side was lit, return the average
|
||||
if averagecount > 0:
|
||||
return (averagegather / averagecount, False)
|
||||
|
||||
# calculate the return...
|
||||
occluded = not (blocktype in transparent_blocks)
|
||||
|
||||
# only calculate the non-default coefficient if we're not occluded
|
||||
if (blocktype == 10) or (blocktype == 11):
|
||||
# lava blocks should always be lit!
|
||||
coefficient = 0.0
|
||||
elif occluded:
|
||||
coefficient = self.calculate_darkness(15, 0)
|
||||
else:
|
||||
coefficient = self.calculate_darkness(skylight[local_x, local_y, local_z], blocklight[local_x, local_y, local_z])
|
||||
|
||||
# only say we're occluded if the point is in the CURRENT
|
||||
# chunk, so that we don't get obvious inter-chunk dependencies
|
||||
# (we want this here so we still have the default coefficient
|
||||
# for occluded blocks, even when we don't report them as
|
||||
# occluded)
|
||||
if not is_local_chunk:
|
||||
occluded = False
|
||||
|
||||
return (coefficient, occluded)
|
||||
|
||||
def chunk_render(self, img=None, xoff=0, yoff=0, cave=False):
|
||||
"""Renders a chunk with the given parameters, and returns the image.
|
||||
If img is given, the chunk is rendered to that image object. Otherwise,
|
||||
@@ -252,24 +456,16 @@ class ChunkRenderer(object):
|
||||
rendered, and blocks are drawn with a color tint depending on their
|
||||
depth."""
|
||||
blocks = self.blocks
|
||||
|
||||
if cave:
|
||||
skylight = get_skylight_array(self.level)
|
||||
# Cave mode. Actually go through and 0 out all blocks that are not in a
|
||||
# cave, so that it only renders caves.
|
||||
|
||||
# 1st task: this array is 2 blocks per byte, expand it so we can just
|
||||
# do a bitwise and on the arrays
|
||||
skylight_expanded = numpy.empty((16,16,128), dtype=numpy.uint8)
|
||||
# Even elements get the lower 4 bits
|
||||
skylight_expanded[:,:,::2] = skylight & 0x0F
|
||||
# Odd elements get the upper 4 bits
|
||||
skylight_expanded[:,:,1::2] = skylight >> 4
|
||||
|
||||
# Places where the skylight is not 0 (there's some amount of skylight
|
||||
# touching it) change it to something that won't get rendered, AND
|
||||
# won't get counted as "transparent".
|
||||
blocks = blocks.copy()
|
||||
blocks[skylight_expanded != 0] = 21
|
||||
blocks[self.skylight != 0] = 21
|
||||
|
||||
blockData = get_blockdata_array(self.level)
|
||||
blockData_expanded = numpy.empty((16,16,128), dtype=numpy.uint8)
|
||||
@@ -359,9 +555,37 @@ class ChunkRenderer(object):
|
||||
# Draw the actual block on the image. For cave images,
|
||||
# tint the block with a color proportional to its depth
|
||||
if cave:
|
||||
# no lighting for cave -- depth is probably more useful
|
||||
img.paste(Image.blend(t[0],depth_colors[z],0.3), (imgx, imgy), t[1])
|
||||
else:
|
||||
img.paste(t[0], (imgx, imgy), t[1])
|
||||
if not self.world.lighting:
|
||||
# no lighting at all
|
||||
img.paste(t[0], (imgx, imgy), t[1])
|
||||
elif blockid in transparent_blocks:
|
||||
# transparent means draw the whole
|
||||
# block shaded with the current
|
||||
# block's light
|
||||
black_coeff, _ = self.get_lighting_coefficient(x, y, z)
|
||||
img.paste(Image.blend(t[0], black_color, black_coeff), (imgx, imgy), t[1])
|
||||
else:
|
||||
# draw each face lit appropriately,
|
||||
# but first just draw the block
|
||||
img.paste(t[0], (imgx, imgy), t[1])
|
||||
|
||||
# top face
|
||||
black_coeff, face_occlude = self.get_lighting_coefficient(x, y, z + 1)
|
||||
if not face_occlude:
|
||||
img.paste((0,0,0), (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff))
|
||||
|
||||
# left face
|
||||
black_coeff, face_occlude = self.get_lighting_coefficient(x - 1, y, z)
|
||||
if not face_occlude:
|
||||
img.paste((0,0,0), (imgx, imgy), ImageEnhance.Brightness(facemasks[1]).enhance(black_coeff))
|
||||
|
||||
# right face
|
||||
black_coeff, face_occlude = self.get_lighting_coefficient(x, y + 1, z)
|
||||
if not face_occlude:
|
||||
img.paste((0,0,0), (imgx, imgy), ImageEnhance.Brightness(facemasks[2]).enhance(black_coeff))
|
||||
|
||||
# Draw edge lines
|
||||
if blockid in (44,): # step block
|
||||
@@ -380,6 +604,32 @@ class ChunkRenderer(object):
|
||||
|
||||
return img
|
||||
|
||||
# Render 3 blending masks for lighting
|
||||
# first is top (+Z), second is left (-X), third is right (+Y)
|
||||
def generate_facemasks():
|
||||
white = Image.new("L", (24,24), 255)
|
||||
|
||||
top = Image.new("L", (24,24), 0)
|
||||
left = Image.new("L", (24,24), 0)
|
||||
whole = Image.new("L", (24,24), 0)
|
||||
|
||||
toppart = textures.transform_image(white)
|
||||
leftpart = textures.transform_image_side(white)
|
||||
|
||||
top.paste(toppart, (0,0))
|
||||
left.paste(leftpart, (0,6))
|
||||
right = left.transpose(Image.FLIP_LEFT_RIGHT)
|
||||
|
||||
# Manually touch up 6 pixels that leave a gap, like in
|
||||
# textures._build_block()
|
||||
for x,y in [(13,23), (17,21), (21,19)]:
|
||||
right.putpixel((x,y), 255)
|
||||
for x,y in [(3,4), (7,2), (11,0)]:
|
||||
top.putpixel((x,y), 255)
|
||||
|
||||
return (top, left, right)
|
||||
facemasks = generate_facemasks()
|
||||
black_color = Image.new("RGB", (24,24), (0,0,0))
|
||||
|
||||
# Render 128 different color images for color coded depth blending in cave mode
|
||||
def generate_depthcolors():
|
||||
|
||||
4
gmap.py
4
gmap.py
@@ -48,6 +48,8 @@ def main():
|
||||
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")
|
||||
parser.add_option("--cachedir", dest="cachedir", help="Sets the directory where the Overviewer will save chunk images, which is an intermediate step before the tiles are generated. You must use the same directory each time to gain any benefit from the cache. If not set, this defaults to your world directory.")
|
||||
parser.add_option("--chunklist", dest="chunklist", help="A file containing, on each line, a path to a chunkfile to update. Instead of scanning the world directory for chunks, it will just use this list. Normal caching rules still apply.")
|
||||
parser.add_option("--lighting", dest="lighting", help="Renders shadows using light data from each chunk.", action="store_true")
|
||||
parser.add_option("--night", dest="night", help="Renders shadows using light data from each chunk, as if it were night. Implies --lighting.", action="store_true")
|
||||
parser.add_option("--imgformat", dest="imgformat", help="The image output format to use. Currently supported: png(default), jpg. NOTE: png will always be used as the intermediate image format.")
|
||||
parser.add_option("--optimize-img", dest="optimizeimg", help="If using png, perform image file size optimizations on the output. Specify 1 for pngcrush, 2 for pngcrush+optipng+advdef. This may double (or more) render times, but will produce up to 30% smaller images. NOTE: requires corresponding programs in $PATH or %PATH%")
|
||||
parser.add_option("-q", "--quiet", dest="quiet", action="count", default=0, help="Print less output. You can specify this option multiple times.")
|
||||
@@ -114,7 +116,7 @@ def main():
|
||||
logging.debug("Current log level: {0}".format(logging.getLogger().level))
|
||||
|
||||
# First generate the world's chunk images
|
||||
w = world.WorldRenderer(worlddir, cachedir, chunklist=chunklist)
|
||||
w = world.WorldRenderer(worlddir, cachedir, chunklist=chunklist, lighting=options.lighting, night=options.night)
|
||||
w.go(options.procs)
|
||||
|
||||
# Now generate the tiles
|
||||
|
||||
64
textures.py
64
textures.py
@@ -114,7 +114,7 @@ def _split_terrain(terrain):
|
||||
# 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
|
||||
by a factor of 2. Returns the resulting image, which will be 24x12 pixels
|
||||
|
||||
@@ -147,7 +147,7 @@ def _transform_image(img, blockID=None):
|
||||
newimg = img.transform((24,12), Image.AFFINE, transform)
|
||||
return newimg
|
||||
|
||||
def _transform_image_side(img, blockID=None):
|
||||
def transform_image_side(img, blockID=None):
|
||||
"""Takes an image and shears it for the left side of the cube (reflect for
|
||||
the right side)"""
|
||||
|
||||
@@ -186,13 +186,13 @@ def _build_block(top, side, blockID=None):
|
||||
"""
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
|
||||
top = _transform_image(top, blockID)
|
||||
top = transform_image(top, blockID)
|
||||
|
||||
if not side:
|
||||
img.paste(top, (0,0), top)
|
||||
return img
|
||||
|
||||
side = _transform_image_side(side, blockID)
|
||||
side = transform_image_side(side, blockID)
|
||||
|
||||
otherside = side.transpose(Image.FLIP_LEFT_RIGHT)
|
||||
|
||||
@@ -345,25 +345,25 @@ def generate_special_texture(blockID, data):
|
||||
raw_straight = terrain_images[128]
|
||||
raw_corner = terrain_images[112]
|
||||
|
||||
## use _transform_image to scale and shear
|
||||
## use transform_image to scale and shear
|
||||
if data == 0:
|
||||
track = _transform_image(raw_straight, blockID)
|
||||
track = transform_image(raw_straight, blockID)
|
||||
elif data == 6:
|
||||
track = _transform_image(raw_corner, blockID)
|
||||
track = transform_image(raw_corner, blockID)
|
||||
elif data == 7:
|
||||
track = _transform_image(raw_corner.rotate(270), blockID)
|
||||
track = transform_image(raw_corner.rotate(270), blockID)
|
||||
elif data == 8:
|
||||
# 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)
|
||||
elif data == 9:
|
||||
track = _transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM),
|
||||
track = transform_image(raw_corner.transpose(Image.FLIP_TOP_BOTTOM),
|
||||
blockID)
|
||||
elif data == 1:
|
||||
track = _transform_image(raw_straight.rotate(90), blockID)
|
||||
track = transform_image(raw_straight.rotate(90), blockID)
|
||||
else:
|
||||
# TODO render carts that slop up or down
|
||||
track = _transform_image(raw_straight, blockID)
|
||||
track = transform_image(raw_straight, blockID)
|
||||
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
img.paste(track, (0,12), track)
|
||||
@@ -371,8 +371,8 @@ def generate_special_texture(blockID, data):
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
if blockID == 59: # crops
|
||||
raw_crop = terrain_images[88+data]
|
||||
crop1 = _transform_image(raw_crop, blockID)
|
||||
crop2 = _transform_image_side(raw_crop, blockID)
|
||||
crop1 = transform_image(raw_crop, blockID)
|
||||
crop2 = transform_image_side(raw_crop, blockID)
|
||||
crop3 = crop2.transpose(Image.FLIP_LEFT_RIGHT)
|
||||
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
@@ -382,9 +382,9 @@ def generate_special_texture(blockID, data):
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
if blockID == 61: #furnace
|
||||
top = _transform_image(terrain_images[1])
|
||||
side1 = _transform_image_side(terrain_images[45])
|
||||
side2 = _transform_image_side(terrain_images[44]).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
top = transform_image(terrain_images[1])
|
||||
side1 = transform_image_side(terrain_images[45])
|
||||
side2 = transform_image_side(terrain_images[44]).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
|
||||
@@ -394,9 +394,9 @@ def generate_special_texture(blockID, data):
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
if blockID == 62: # lit furnace
|
||||
top = _transform_image(terrain_images[1])
|
||||
side1 = _transform_image_side(terrain_images[45])
|
||||
side2 = _transform_image_side(terrain_images[45+16]).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
top = transform_image(terrain_images[1])
|
||||
side1 = transform_image_side(terrain_images[45])
|
||||
side2 = transform_image_side(terrain_images[45+16]).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
|
||||
@@ -412,22 +412,22 @@ def generate_special_texture(blockID, data):
|
||||
# normally this ladder would be obsured by the block it's attached to
|
||||
# but since ladders can apparently be placed on transparent blocks, we
|
||||
# have to render this thing anyway. same for data == 2
|
||||
tex = _transform_image_side(raw_texture)
|
||||
tex = transform_image_side(raw_texture)
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
img.paste(tex, (0,6), tex)
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
if data == 2:
|
||||
tex = _transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
img.paste(tex, (12,6), tex)
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
if data == 3:
|
||||
tex = _transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
tex = transform_image_side(raw_texture).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
img.paste(tex, (0,0), tex)
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
if data == 4:
|
||||
tex = _transform_image_side(raw_texture)
|
||||
tex = transform_image_side(raw_texture)
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
img.paste(tex, (12,0), tex)
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
@@ -449,36 +449,36 @@ def generate_special_texture(blockID, data):
|
||||
img = Image.new("RGBA", (24,24), (38,92,255,0))
|
||||
if (data & 0x03) == 0:
|
||||
if not swung:
|
||||
tex = _transform_image_side(raw_door)
|
||||
tex = transform_image_side(raw_door)
|
||||
img.paste(tex, (0,6), tex)
|
||||
else:
|
||||
# flip first to set the doornob on the correct side
|
||||
tex = _transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT))
|
||||
tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT))
|
||||
tex = tex.transpose(Image.FLIP_LEFT_RIGHT)
|
||||
img.paste(tex, (0,0), tex)
|
||||
|
||||
if (data & 0x03) == 1:
|
||||
if not swung:
|
||||
tex = _transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
img.paste(tex, (0,0), tex)
|
||||
else:
|
||||
tex = _transform_image_side(raw_door)
|
||||
tex = transform_image_side(raw_door)
|
||||
img.paste(tex, (12,0), tex)
|
||||
|
||||
if (data & 0x03) == 2:
|
||||
if not swung:
|
||||
tex = _transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT))
|
||||
tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT))
|
||||
img.paste(tex, (12,0), tex)
|
||||
else:
|
||||
tex = _transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
img.paste(tex, (12,6), tex)
|
||||
|
||||
if (data & 0x03) == 3:
|
||||
if not swung:
|
||||
tex = _transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)).transpose(Image.FLIP_LEFT_RIGHT)
|
||||
img.paste(tex, (12,6), tex)
|
||||
else:
|
||||
tex = _transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT))
|
||||
tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT))
|
||||
img.paste(tex, (0,6), tex)
|
||||
|
||||
return (img.convert("RGB"), img.split()[3])
|
||||
|
||||
31
world.py
31
world.py
@@ -92,9 +92,11 @@ class WorldRenderer(object):
|
||||
files to update. If it includes a trailing newline, it is stripped, so you
|
||||
can pass in file handles just fine.
|
||||
"""
|
||||
def __init__(self, worlddir, cachedir, chunklist=None):
|
||||
def __init__(self, worlddir, cachedir, chunklist=None, lighting=False, night=False):
|
||||
self.worlddir = worlddir
|
||||
self.caves = False
|
||||
self.lighting = lighting or night
|
||||
self.night = night
|
||||
self.cachedir = cachedir
|
||||
|
||||
self.chunklist = chunklist
|
||||
@@ -135,7 +137,18 @@ class WorldRenderer(object):
|
||||
inclusion_set.add((col, row))
|
||||
|
||||
return inclusion_set
|
||||
|
||||
|
||||
def get_chunk_path(self, chunkX, chunkY):
|
||||
"""Returns the path to the chunk file at (chunkX, chunkY), if
|
||||
it exists."""
|
||||
|
||||
chunkFile = "%s/%s/c.%s.%s.dat" % (base36encode(chunkX % 64),
|
||||
base36encode(chunkY % 64),
|
||||
base36encode(chunkX),
|
||||
base36encode(chunkY))
|
||||
|
||||
return os.path.join(self.worlddir, chunkFile)
|
||||
|
||||
def findTrueSpawn(self):
|
||||
"""Adds the true spawn location to self.POI. The spawn Y coordinate
|
||||
is almost always the default of 64. Find the first air block above
|
||||
@@ -152,11 +165,9 @@ class WorldRenderer(object):
|
||||
chunkY = spawnZ/16
|
||||
|
||||
## The filename of this chunk
|
||||
chunkFile = os.path.join(base36encode(chunkX % 64), base36encode(chunkY % 64),
|
||||
"c.%s.%s.dat" % (base36encode(chunkX), base36encode(chunkY)))
|
||||
chunkFile = self.get_chunk_path(chunkX, chunkY)
|
||||
|
||||
|
||||
data=nbt.load(os.path.join(self.worlddir, chunkFile))[1]
|
||||
data=nbt.load(chunkFile)[1]
|
||||
level = data['Level']
|
||||
blockArray = numpy.frombuffer(level['Blocks'], dtype=numpy.uint8).reshape((16,16,128))
|
||||
|
||||
@@ -238,12 +249,12 @@ class WorldRenderer(object):
|
||||
if inclusion_set and (col, row) not in inclusion_set:
|
||||
# Skip rendering, just find where the existing image is
|
||||
_, imgpath = chunk.ChunkRenderer(chunkfile,
|
||||
self.cachedir).find_oldimage(False)
|
||||
self.cachedir, self).find_oldimage(False)
|
||||
if imgpath:
|
||||
results[(col, row)] = imgpath
|
||||
continue
|
||||
|
||||
result = chunk.render_and_save(chunkfile, self.cachedir, cave=self.caves)
|
||||
result = chunk.render_and_save(chunkfile, self.cachedir, self, cave=self.caves)
|
||||
results[(col, row)] = result
|
||||
if i > 0:
|
||||
if 1000 % i == 0 or i % 1000 == 0:
|
||||
@@ -256,13 +267,13 @@ class WorldRenderer(object):
|
||||
if inclusion_set and (col, row) not in inclusion_set:
|
||||
# Skip rendering, just find where the existing image is
|
||||
_, imgpath = chunk.ChunkRenderer(chunkfile,
|
||||
self.cachedir).find_oldimage(False)
|
||||
self.cachedir, self).find_oldimage(False)
|
||||
if imgpath:
|
||||
results[(col, row)] = imgpath
|
||||
continue
|
||||
|
||||
result = pool.apply_async(chunk.render_and_save,
|
||||
args=(chunkfile,self.cachedir),
|
||||
args=(chunkfile,self.cachedir,self),
|
||||
kwds=dict(cave=self.caves))
|
||||
asyncresults.append((col, row, result))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user