initial (and incorrect) drawing code
This commit is contained in:
168
chunk.py
168
chunk.py
@@ -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
|
||||||
|
|
||||||
|
|||||||
30
quadtree.py
30
quadtree.py
@@ -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):
|
||||||
|
|||||||
Reference in New Issue
Block a user