0

initial (and incorrect) drawing code

This commit is contained in:
Aaron Griffith
2011-03-01 16:45:04 -05:00
parent 83d7a36ef4
commit 33b10f7065
2 changed files with 47 additions and 151 deletions

168
chunk.py
View File

@@ -128,54 +128,21 @@ fluid_blocks = set([8,9,10,11])
# (glass, half blocks) # (glass, half blocks)
nospawn_blocks = set([20,44]) nospawn_blocks = set([20,44])
def find_oldimage(chunkXY, cached, cave):
blockid = "%d.%d" % chunkXY
# Get the name of the existing image.
dir1 = world.base36encode(chunkXY[0]%64)
dir2 = world.base36encode(chunkXY[1]%64)
cachename = '/'.join((dir1, dir2))
oldimg = oldimg_path = None
key = ".".join((blockid, "cave" if cave else "nocave"))
if key in cached[cachename]:
oldimg_path = cached[cachename][key]
_, oldimg = os.path.split(oldimg_path)
#logging.debug("Found cached image {0}".format(oldimg))
return oldimg, oldimg_path
def check_cache(world, chunkXY, oldimg):
"""Returns True is oldimg is OK to use (i.e. not stale)"""
# TODO read to the region file and get the timestamp??
# TODO currently, just use the mtime on the region file
# TODO (which will cause a single chunk update to invalidate everything in the region
if not oldimg[1]: return False
chunkfile = os.path.join(world.worlddir, "region", "r.%d.%d.mcr" % (chunkXY[0]//32, chunkXY[1]//32))
with open(chunkfile, "rb") as f:
region = nbt.MCRFileReader(f)
mtime = region.get_chunk_timestamp(chunkXY[0], chunkXY[1])
#logging.debug("checking cache %s against %s %d", chunkfile, oldimg[1], mtime)
try:
if mtime <= os.path.getmtime(oldimg[1]):
return True
return False
except OSError:
return False
# chunkcoords should be the coordinates of a possible chunk. it may not exist # 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): def render_to_image(chunkcoords, img, imgcoords, quadtreeobj, cave=False, queue=None):
"""Used as the entry point for the multiprocessing workers (since processes """Used to render a chunk to a tile in quadtree.py.
can't target bound methods) or to easily render and save one chunk
chunkcoords is a tuple: (chunkX, chunkY) chunkcoords is a tuple: (chunkX, chunkY)
imgcoords is as well: (imgX, imgY), which represents the "origin"
to use for drawing.
If the chunk doesn't exist, return None. If the chunk doesn't exist, return False.
Else, returns the image file location""" Else, returns True."""
a = ChunkRenderer(chunkcoords, cachedir, worldobj, oldimg, queue) a = ChunkRenderer(chunkcoords, quadtreeobj.world, quadtreeobj, queue)
try: try:
return a.render_and_save(cave) a.chunk_render(img, imgcoords[0], imgcoords[1], cave)
return True
except ChunkCorrupt: except ChunkCorrupt:
# This should be non-fatal, but should print a warning # This should be non-fatal, but should print a warning
pass pass
@@ -191,6 +158,7 @@ def render_and_save(chunkcoords, cachedir, worldobj, oldimg, cave=False, queue=N
# entire program, instead of this process dying and the parent waiting # entire program, instead of this process dying and the parent waiting
# forever for it to finish. # forever for it to finish.
raise Exception() raise Exception()
return False
class ChunkCorrupt(Exception): class ChunkCorrupt(Exception):
pass pass
@@ -199,17 +167,15 @@ class NoSuchChunk(Exception):
pass pass
class ChunkRenderer(object): class ChunkRenderer(object):
def __init__(self, chunkcoords, cachedir, worldobj, oldimg, queue): def __init__(self, chunkcoords, worldobj, quadtreeobj, queue):
"""Make a new chunk renderer for the given chunk coordinates. """Make a new chunk renderer for the given chunk coordinates.
chunkcoors should be a tuple: (chunkX, chunkY) chunkcoors should be a tuple: (chunkX, chunkY)
cachedir is a directory to save the resulting chunk images to cachedir is a directory to save the resulting chunk images to
""" """
self.queue = queue self.queue = queue
# derive based on worlddir and chunkcoords
self.regionfile = os.path.join(worldobj.worlddir, "region", self.regionfile = worldobj.get_region_path(*chunkcoords)
"r.%d.%d.mcr" % (chunkcoords[0] // 32, chunkcoords[1]//32))
if not os.path.exists(self.regionfile): if not os.path.exists(self.regionfile):
raise ValueError("Could not find regionfile: %s" % self.regionfile) raise ValueError("Could not find regionfile: %s" % self.regionfile)
@@ -227,23 +193,8 @@ class ChunkRenderer(object):
self.chunkX = chunkcoords[0] self.chunkX = chunkcoords[0]
self.chunkY = chunkcoords[1] self.chunkY = chunkcoords[1]
self.world = worldobj self.world = worldobj
self.quadtree = quadtreeobj
# 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,
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
if self.world.useBiomeData: if self.world.useBiomeData:
# 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...
@@ -251,15 +202,6 @@ class ChunkRenderer(object):
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")
if not os.path.exists(self.cachedir):
try:
os.makedirs(self.cachedir)
except OSError, e:
import errno
if e.errno != errno.EEXIST:
raise
def _load_level(self): def _load_level(self):
"""Loads and returns the level structure""" """Loads and returns the level structure"""
if not hasattr(self, "_level"): if not hasattr(self, "_level"):
@@ -480,69 +422,12 @@ class ChunkRenderer(object):
self._digest = digest[:6] self._digest = digest[:6]
return self._digest return self._digest
def render_and_save(self, cave=False):
"""Render the chunk using chunk_render, and then save it to a file in
the same directory as the source image. If the file already exists and
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
# 2) An old image exists, but the chunk was more recently modified (the
# image was NOT checked if it was valid)
# 3) An old image exists, the chunk was not modified more recently, but
# 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
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:
# There is an existing file, the chunk has a newer mtime, but the
# hashes match.
# Before we return it, update its mtime so the next round
# doesn't have to check the hash
# TODO confirm hash checking is correct (it should be)
os.utime(dest_path, None)
logging.debug("Using cached image, and updating utime")
return dest_path
else:
# Remove old image for this chunk. Anything already existing is
# either corrupt or out of date
os.unlink(self.oldimg_path)
logging.debug("doing a real real render")
# Render the chunk
img = self.chunk_render(cave=cave)
# Save it
try:
img.save(dest_path)
except:
os.unlink(dest_path)
raise
# Return its location
#raise Exception("early exit")
return dest_path
def calculate_darkness(self, skylight, blocklight): def calculate_darkness(self, skylight, blocklight):
"""Takes a raw blocklight and skylight, and returns a value """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 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 an alpha value for a blend with a black source image. It mimics
Minecraft lighting calculations.""" Minecraft lighting calculations."""
if not self.world.night: if not self.quadtree.night:
# Daytime # Daytime
return 1.0 - pow(0.8, 15 - max(blocklight, skylight)) return 1.0 - pow(0.8, 15 - max(blocklight, skylight))
else: else:
@@ -662,7 +547,7 @@ class ChunkRenderer(object):
# won't get counted as "transparent". # won't get counted as "transparent".
blocks = blocks.copy() blocks = blocks.copy()
blocks[self.skylight != 0] = 21 blocks[self.skylight != 0] = 21
blockData = get_blockdata_array(self.level) blockData = get_blockdata_array(self.level)
blockData_expanded = numpy.empty((16,16,128), dtype=numpy.uint8) blockData_expanded = numpy.empty((16,16,128), dtype=numpy.uint8)
# Even elements get the lower 4 bits # Even elements get the lower 4 bits
@@ -691,6 +576,11 @@ class ChunkRenderer(object):
for x,y,z,imgx,imgy in iterate_chunkblocks(xoff,yoff): for x,y,z,imgx,imgy in iterate_chunkblocks(xoff,yoff):
blockid = blocks[x,y,z] blockid = blocks[x,y,z]
if imgx > img.size[0] + 12 or imgx < -12:
continue
if imgy > img.size[1] + 12 or imgy < -12:
continue
# the following blocks don't have textures that can be pre-computed from the blockid # the following blocks don't have textures that can be pre-computed from the blockid
# alone. additional data is required. # alone. additional data is required.
@@ -817,7 +707,7 @@ class ChunkRenderer(object):
# no lighting for cave -- depth is probably more useful # no lighting for cave -- depth is probably more useful
composite.alpha_over(img, Image.blend(t[0],depth_colors[z],0.3), (imgx, imgy), t[1]) composite.alpha_over(img, Image.blend(t[0],depth_colors[z],0.3), (imgx, imgy), t[1])
else: else:
if not self.world.lighting: if not self.quadtree.lighting:
# no lighting at all # no lighting at all
composite.alpha_over(img, t[0], (imgx, imgy), t[1]) composite.alpha_over(img, t[0], (imgx, imgy), t[1])
elif blockid in transparent_blocks: elif blockid in transparent_blocks:
@@ -825,7 +715,7 @@ class ChunkRenderer(object):
# block shaded with the current # block shaded with the current
# block's light # block's light
black_coeff, _ = self.get_lighting_coefficient(x, y, z) black_coeff, _ = self.get_lighting_coefficient(x, y, z)
if self.world.spawn and black_coeff > 0.8 and blockid in solid_blocks and not ( if self.quadtree.spawn and black_coeff > 0.8 and blockid in solid_blocks and not (
blockid in nospawn_blocks or ( blockid in nospawn_blocks or (
z != 127 and (blocks[x,y,z+1] in solid_blocks or blocks[x,y,z+1] in fluid_blocks) z != 127 and (blocks[x,y,z+1] in solid_blocks or blocks[x,y,z+1] in fluid_blocks)
) )
@@ -841,7 +731,7 @@ class ChunkRenderer(object):
# top face # top face
black_coeff, face_occlude = self.get_lighting_coefficient(x, y, z + 1) black_coeff, face_occlude = self.get_lighting_coefficient(x, y, z + 1)
# Use red instead of black for spawnable blocks # Use red instead of black for spawnable blocks
if self.world.spawn and black_coeff > 0.8 and blockid in solid_blocks and not ( if self.quadtree.spawn and black_coeff > 0.8 and blockid in solid_blocks and not (
blockid in nospawn_blocks or ( blockid in nospawn_blocks or (
z != 127 and (blocks[x,y,z+1] in solid_blocks or blocks[x,y,z+1] in fluid_blocks) z != 127 and (blocks[x,y,z+1] in solid_blocks or blocks[x,y,z+1] in fluid_blocks)
) )
@@ -892,13 +782,15 @@ class ChunkRenderer(object):
msg=msg, msg=msg,
chunk= (self.chunkX, self.chunkY), chunk= (self.chunkX, self.chunkY),
) )
self.queue.put(["newpoi", newPOI]) if self.queue:
self.queue.put(["newpoi", newPOI])
# check to see if there are any signs in the persistentData list that are from this chunk. # check to see if there are any signs in the persistentData list that are from this chunk.
# if so, remove them from the persistentData list (since they're have been added to the world.POI # if so, remove them from the persistentData list (since they're have been added to the world.POI
# list above. # list above
self.queue.put(['removePOI', (self.chunkX, self.chunkY)]) if self.queue:
self.queue.put(['removePOI', (self.chunkX, self.chunkY)])
return img return img

View File

@@ -32,6 +32,7 @@ from time import gmtime, strftime, sleep
from PIL import Image from PIL import Image
import nbt import nbt
import chunk
from optimizeimages import optimize_image from optimizeimages import optimize_image
import composite import composite
@@ -95,6 +96,11 @@ class QuadtreeGen(object):
assert(imgformat) assert(imgformat)
self.imgformat = imgformat self.imgformat = imgformat
self.optimizeimg = optimizeimg self.optimizeimg = optimizeimg
# TODO placeholders (use config!)
self.lighting = False
self.night = False
self.spawn = False
# Make the destination dir # Make the destination dir
if not os.path.exists(destdir): if not os.path.exists(destdir):
@@ -308,9 +314,8 @@ class QuadtreeGen(object):
# Put this in the pool # Put this in the pool
# (even if tilechunks is empty, render_worldtile will delete # (even if tilechunks is empty, render_worldtile will delete
# existing images if appropriate) # existing images if appropriate)
yield pool.apply_async(func=render_worldtile, args= (tilechunks, yield pool.apply_async(func=render_worldtile, args= (self,
colstart, colend, rowstart, rowend, dest, self.imgformat, tilechunks, colstart, colend, rowstart, rowend, dest))
self.optimizeimg))
def _apply_render_inntertile(self, pool, zoom): def _apply_render_inntertile(self, pool, zoom):
"""Same as _apply_render_worltiles but for the inntertile routine. """Same as _apply_render_worltiles but for the inntertile routine.
@@ -530,7 +535,7 @@ def render_innertile(dest, name, imgformat, optimizeimg):
optimize_image(imgpath, imgformat, optimizeimg) optimize_image(imgpath, imgformat, optimizeimg)
@catch_keyboardinterrupt @catch_keyboardinterrupt
def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat, optimizeimg): def render_worldtile(quadtree, chunks, colstart, colend, rowstart, rowend, path):
"""Renders just the specified chunks into a tile and save it. Unlike usual """Renders just the specified chunks into a tile and save it. Unlike usual
python conventions, rowend and colend are inclusive. Additionally, the python conventions, rowend and colend are inclusive. Additionally, the
chunks around the edges are half-way cut off (so that neighboring tiles chunks around the edges are half-way cut off (so that neighboring tiles
@@ -539,7 +544,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat
chunks is a list of (col, row, chunkx, chunky, filename) of chunk chunks is a list of (col, row, chunkx, chunky, filename) of chunk
images that are relevant to this call (with their associated regions) images that are relevant to this call (with their associated regions)
The image is saved to path+".ext" and a hash is saved to path+".hash" The image is saved to path+"."+quadtree.imgformat
If there are no chunks, this tile is not saved (if it already exists, it is If there are no chunks, this tile is not saved (if it already exists, it is
deleted) deleted)
@@ -550,6 +555,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat
There is no return value There is no return value
""" """
# width of one chunk is 384. Each column is half a chunk wide. The total # width of one chunk is 384. Each column is half a chunk wide. The total
# width is (384 + 192*(numcols-1)) since the first column contributes full # width is (384 + 192*(numcols-1)) since the first column contributes full
# width, and each additional one contributes half since they're staggered. # width, and each additional one contributes half since they're staggered.
@@ -577,10 +583,7 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat
# way above this one are possibly visible in this tile). Render them # way above this one are possibly visible in this tile). Render them
# anyways just in case). "chunks" should include up to rowstart-16 # anyways just in case). "chunks" should include up to rowstart-16
# Before we render any tiles, check the hash of each image in this tile to imgpath = path + "." + quadtree.imgformat
# see if it's changed.
# TODO remove hash files?
imgpath = path + "." + imgformat
# first, remove chunks from `chunks` that don't actually exist in # first, remove chunks from `chunks` that don't actually exist in
# their region files # their region files
@@ -643,14 +646,15 @@ def render_worldtile(chunks, colstart, colend, rowstart, rowend, path, imgformat
xpos = -192 + (col-colstart)*192 xpos = -192 + (col-colstart)*192
ypos = -96 + (row-rowstart)*96 ypos = -96 + (row-rowstart)*96
# TODO draw chunks! # draw the chunk!
#composite.alpha_over(tileimg, chunkimg.convert("RGB"), (xpos, ypos), chunkimg) # TODO cave, queue arguments
chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), quadtree, False, None)
# Save them # Save them
tileimg.save(imgpath) tileimg.save(imgpath)
if optimizeimg: if quadtree.optimizeimg:
optimize_image(imgpath, imgformat, optimizeimg) optimize_image(imgpath, quadtree.imgformat, quadtree.optimizeimg)
class FakeResult(object): class FakeResult(object):
def __init__(self, res): def __init__(self, res):