143 lines
6.0 KiB
Python
143 lines
6.0 KiB
Python
import numpy
|
|
from PIL import Image
|
|
from itertools import izip, count
|
|
|
|
import nbt
|
|
import textures
|
|
from textures import texturemap as txtarray
|
|
|
|
# General note about pasting transparent image objects onto an image with an
|
|
# alpha channel:
|
|
# If you use the image as its own mask, it will work fine only if the alpha
|
|
# channel is binary. If there's any translucent parts, then the alpha channel
|
|
# of the dest image will have its alpha channel modified. To prevent this:
|
|
# first use im.split() and take the third item which is the alpha channel and
|
|
# use that as the mask. Then take the image and use im.convert("RGB") to strip
|
|
# the image from its alpha channel, and use that as the source to paste()
|
|
|
|
def get_lvldata(filename):
|
|
"""Takes a filename and returns the Level struct, which contains all the
|
|
level info"""
|
|
return nbt.load(filename)[1]['Level']
|
|
|
|
def get_blockarray(level):
|
|
"""Takes the level struct as returned from get_lvldata, and returns the
|
|
Block array, which just contains all the block ids"""
|
|
return numpy.frombuffer(level['Blocks'], dtype=numpy.uint8).reshape((16,16,128))
|
|
|
|
def get_blockarray_fromfile(filename):
|
|
"""Same as get_blockarray except takes a filename and uses get_lvldata to
|
|
open it. This is a shortcut"""
|
|
level = get_lvldata(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))
|
|
|
|
# This set holds blocks ids that can be seen through
|
|
transparent_blocks = set([0, 8, 9, 18, 20, 37, 38, 39, 40, 50, 51, 52, 53, 59, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 79, 83, 85])
|
|
|
|
def chunk_render(chunkfile, img=None, xoff=0, yoff=0, cave=False):
|
|
level = get_lvldata(chunkfile)
|
|
blocks = get_blockarray(level)
|
|
if cave:
|
|
skylight = get_skylight_array(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
|
|
|
|
# Don't render
|
|
|
|
|
|
# Each block is 24x24
|
|
# The next block on the X axis adds 12px to x and subtracts 6px from y in the image
|
|
# The next block on the Y axis adds 12px to x and adds 6px to y in the image
|
|
# The next block up on the Z axis subtracts 12 from y axis in the image
|
|
|
|
# Since there are 16x16x128 blocks in a chunk, the image will be 384x1728
|
|
# (height is 128*24 high, plus the size of the horizontal plane: 16*12)
|
|
if not img:
|
|
img = Image.new("RGBA", (384, 1728))
|
|
|
|
for x in xrange(15,-1,-1):
|
|
for y in xrange(16):
|
|
imgx = xoff + x*12 + y*12
|
|
imgy = yoff - x*6 + y*6 + 128*12 + 16*12//2
|
|
for z in xrange(128):
|
|
try:
|
|
|
|
blockid = blocks[x,y,z]
|
|
t = textures.blockmap[blockid]
|
|
if not t:
|
|
continue
|
|
|
|
# Check if this block is occluded
|
|
if cave and (
|
|
x == 0 and y != 15 and z != 127
|
|
):
|
|
# If it's on the x face, only render if there's a
|
|
# transparent block in the y+1 direction OR the z-1
|
|
# direction
|
|
if (
|
|
blocks[x,y+1,z] not in transparent_blocks and
|
|
blocks[x,y,z+1] not in transparent_blocks
|
|
):
|
|
continue
|
|
elif cave and (
|
|
y == 15 and x != 0 and z != 127
|
|
):
|
|
# If it's on the facing y face, only render if there's
|
|
# a transparent block in the x-1 direction OR the z-1
|
|
# direction
|
|
if (
|
|
blocks[x-1,y,z] not in transparent_blocks and
|
|
blocks[x,y,z+1] not in transparent_blocks
|
|
):
|
|
continue
|
|
elif cave and (
|
|
y == 15 and x == 0
|
|
):
|
|
# If it's on the facing edge, only render if what's
|
|
# above it is transparent
|
|
if (
|
|
blocks[x,y,z+1] not in transparent_blocks
|
|
):
|
|
continue
|
|
elif (
|
|
# Normal block or not cave mode, check sides for
|
|
# transparentcy or render unconditionally if it's
|
|
# on a shown face
|
|
x != 0 and y != 15 and z != 127 and
|
|
blocks[x-1,y,z] not in transparent_blocks and
|
|
blocks[x,y+1,z] not in transparent_blocks and
|
|
blocks[x,y,z+1] not in transparent_blocks
|
|
):
|
|
# Don't render if all sides aren't transparent and
|
|
# we're not on the edge
|
|
continue
|
|
|
|
img.paste(t[0], (imgx, imgy), t[1])
|
|
|
|
finally:
|
|
# Do this no mater how the above block exits
|
|
imgy -= 12
|
|
|
|
return img
|