0

Working rendering with the new McRegion format.

Notes:
 * Currently only works with -p 1
 * Caching is mostly compatible with existing caches, but not completly.
   This needs more testing and more code reviews
 * There are probably many code paths that will throw exceptions.
 * Not ready for general use yet, but is OK for testing
This commit is contained in:
Andrew Chin
2011-02-23 22:13:35 -05:00
parent 5f5bda948e
commit 05ea562060
4 changed files with 142 additions and 85 deletions

111
chunk.py
View File

@@ -20,6 +20,7 @@ import hashlib
import logging
import time
import math
import sys
import nbt
import textures
@@ -45,10 +46,13 @@ image
# alpha_over extension, BUT this extension may fall back to PIL's
# paste(), which DOES need the workaround.)
def get_lvldata(filename):
"""Takes a filename and returns the Level struct, which contains all the
def get_lvldata(filename, x, y):
"""Takes a filename and chunkcoords and returns the Level struct, which contains all the
level info"""
return nbt.load(filename)[1]['Level']
d = nbt.load_from_region(filename, x, y)
if not d: raise NoSuchChunk(x,y)
return d[1]['Level']
def get_blockarray(level):
"""Takes the level struct as returned from get_lvldata, and returns the
@@ -124,14 +128,13 @@ fluid_blocks = set([8,9,10,11])
# (glass, half blocks)
nospawn_blocks = set([20,44])
def find_oldimage(chunkfile, cached, cave):
destdir, filename = os.path.split(chunkfile)
filename_split = filename.split(".")
blockid = ".".join(filename_split[1:3])
def find_oldimage(chunkXY, cached, cave):
# TODO update this
blockid = "%d.%d" % chunkXY
# Get the name of the existing image.
moredirs, dir2 = os.path.split(destdir)
dir1 = os.path.basename(moredirs)
dir1 = world.base36encode(chunkXY[0]%64)
dir2 = world.base36encode(chunkXY[1]%64)
cachename = '/'.join((dir1, dir2))
oldimg = oldimg_path = None
@@ -150,12 +153,16 @@ def check_cache(chunkfile, oldimg):
except OSError:
return False
def render_and_save(chunkfile, cachedir, worldobj, oldimg, cave=False, queue=None):
# chunkcoords should be the coordinates of a possible chunk. it may not exist
def render_and_save(chunkcoords, cachedir, worldobj, oldimg, cave=False, queue=None):
"""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, worldobj, oldimg, queue)
chunkcoords is a tuple: (chunkX, chunkY)
If the chunk doesn't exist, return None.
Else, returns the image file location"""
a = ChunkRenderer(chunkcoords, cachedir, worldobj, oldimg, queue)
try:
return a.render_and_save(cave)
except ChunkCorrupt:
@@ -177,36 +184,53 @@ def render_and_save(chunkfile, cachedir, worldobj, oldimg, cave=False, queue=Non
class ChunkCorrupt(Exception):
pass
class NoSuchChunk(Exception):
pass
class ChunkRenderer(object):
def __init__(self, chunkfile, cachedir, worldobj, oldimg, queue):
"""Make a new chunk renderer for the given chunkfile.
chunkfile should be a full path to the .dat file to process
def __init__(self, chunkcoords, cachedir, worldobj, oldimg, queue):
"""Make a new chunk renderer for the given chunk coordinates.
chunkcoors should be a tuple: (chunkX, chunkY)
cachedir is a directory to save the resulting chunk images to
"""
self.queue = queue
# derive based on worlddir and chunkcoords
self.regionfile = os.path.join(worldobj.worlddir, "region",
"r.%d.%d.mcr" % (chunkcoords[0] // 32, chunkcoords[1]//32))
if not os.path.exists(chunkfile):
raise ValueError("Could not find chunkfile")
self.chunkfile = chunkfile
destdir, filename = os.path.split(self.chunkfile)
filename_split = filename.split(".")
chunkcoords = filename_split[1:3]
if not os.path.exists(self.regionfile):
raise ValueError("Could not find regionfile: %s" % self.regionfile)
## TODO TODO all of this class
#destdir, filename = os.path.split(self.chunkfile)
#filename_split = filename.split(".")
#chunkcoords = filename_split[1:3]
self.coords = map(world.base36decode, chunkcoords)
self.blockid = ".".join(chunkcoords)
#self.coords = map(world.base36decode, chunkcoords)
self.blockid = "%d.%d" % chunkcoords
# chunk coordinates (useful to converting local block coords to
# global block coords)
self.chunkX = int(filename_split[1], base=36)
self.chunkY = int(filename_split[2], base=36)
self.chunkX = chunkcoords[0]
self.chunkY = chunkcoords[1]
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
# of destdir and use that
moredirs, dir2 = os.path.split(destdir)
_, dir1 = os.path.split(moredirs)
self.cachedir = os.path.join(cachedir, dir1, dir2)
##moredirs, dir2 = os.path.split(destdir)
##_, dir1 = os.path.split(moredirs)
self.cachedir = os.path.join(cachedir,
world.base36encode(self.chunkX%64),
world.base36encode(self.chunkY%64))
#logging.debug("cache location for this chunk: %s", self.cachedir)
self.oldimg, self.oldimg_path = oldimg
@@ -229,9 +253,12 @@ class ChunkRenderer(object):
"""Loads and returns the level structure"""
if not hasattr(self, "_level"):
try:
self._level = get_lvldata(self.chunkfile)
self._level = get_lvldata(self.regionfile, self.chunkX, self.chunkY)
except NoSuchChunk, e:
#logging.debug("Skipping non-existant chunk")
raise
except Exception, e:
logging.warning("Error opening chunk file %s. It may be corrupt. %s", self.chunkfile, e)
logging.warning("Error opening chunk file %s. It may be corrupt. %s", self.regionfile, e)
raise ChunkCorrupt(str(e))
return self._level
level = property(_load_level)
@@ -259,13 +286,13 @@ class ChunkRenderer(object):
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])
chunk_path = self.world.get_region_path(self.chunkX - 1, self.chunkY)
try:
chunk_data = get_lvldata(chunk_path)
chunk_data = get_lvldata(chunk_path, self.chunkX-1, self.chunkY)
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:
except NoSuchChunk:
self._left_skylight = None
self._left_blocklight = None
self._left_blocks = None
@@ -293,13 +320,13 @@ class ChunkRenderer(object):
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)
chunk_path = self.world.get_region_path(self.chunkX, self.chunkY + 1)
try:
chunk_data = get_lvldata(chunk_path)
chunk_data = get_lvldata(chunk_path, self.chunkX, self.chunkY+1)
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:
except NoSuchChunk:
self._right_skylight = None
self._right_blocklight = None
self._right_blocks = None
@@ -448,6 +475,7 @@ class ChunkRenderer(object):
is up to date, this method doesn't render anything.
"""
blockid = self.blockid
# Reasons for the code to get to this point:
# 1) An old image doesn't exist
@@ -457,13 +485,18 @@ class ChunkRenderer(object):
# the image was invalid and deleted (sort of the same as (1))
# What /should/ the image be named, go ahead and hash the block array
dest_filename = "img.{0}.{1}.{2}.png".format(
try:
dest_filename = "img.{0}.{1}.{2}.png".format(
blockid,
"cave" if cave else "nocave",
self._hash_blockarray(),
)
except NoSuchChunk, e:
return None
dest_path = os.path.join(self.cachedir, dest_filename)
#logging.debug("cache filename: %s", dest_path)
if self.oldimg:
if dest_filename == self.oldimg:
@@ -479,6 +512,7 @@ class ChunkRenderer(object):
# either corrupt or out of date
os.unlink(self.oldimg_path)
# Render the chunk
img = self.chunk_render(cave=cave)
# Save it
@@ -488,6 +522,7 @@ class ChunkRenderer(object):
os.unlink(dest_path)
raise
# Return its location
#raise Exception("early exit")
return dest_path
def calculate_darkness(self, skylight, blocklight):