0

Merge branch 'dtt-c-render' into overlays

Conflicts:
	overviewer.py
	setup.py
This commit is contained in:
Aaron Griffith
2011-03-29 05:57:56 -04:00
18 changed files with 215 additions and 72 deletions

View File

@@ -177,8 +177,8 @@ class ChunkRenderer(object):
self.queue = queue
self.regionfile = worldobj.get_region_path(*chunkcoords)
if not os.path.exists(self.regionfile):
raise ValueError("Could not find regionfile: %s" % self.regionfile)
#if not os.path.exists(self.regionfile):
# raise ValueError("Could not find regionfile: %s" % self.regionfile)
## TODO TODO all of this class
@@ -187,7 +187,7 @@ class ChunkRenderer(object):
#chunkcoords = filename_split[1:3]
#self.coords = map(world.base36decode, chunkcoords)
self.blockid = "%d.%d" % chunkcoords
#self.blockid = "%d.%d" % chunkcoords
# chunk coordinates (useful to converting local block coords to
# global block coords)
@@ -197,12 +197,6 @@ class ChunkRenderer(object):
self.world = worldobj
self.rendermode = rendermode
if self.world.useBiomeData:
# make sure we've at least *tried* to load the color arrays in this process...
textures.prepareBiomeData(self.world.worlddir)
if not textures.grasscolor or not textures.foliagecolor:
raise Exception("Can't find grasscolor.png or foliagecolor.png")
def _load_level(self):
"""Loads and returns the level structure"""
if not hasattr(self, "_level"):
@@ -412,21 +406,6 @@ class ChunkRenderer(object):
For cave mode, all blocks that have any direct sunlight are not
rendered, and blocks are drawn with a color tint depending on their
depth."""
blocks = self.blocks
pseudo_ancildata_blocks = set([85])
left_blocks = self.left_blocks
right_blocks = self.right_blocks
if cave:
# Cave mode. Actually go through and 0 out all blocks that are not in a
# cave, so that it only renders caves.
# 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[self.skylight != 0] = 21
blockData = get_blockdata_array(self.level)
blockData_expanded = numpy.empty((16,16,128), dtype=numpy.uint8)
@@ -435,7 +414,6 @@ class ChunkRenderer(object):
# Odd elements get the upper 4 bits
blockData_expanded[:,:,1::2] = blockData >> 4
tileEntities = get_tileentity_data(self.level)
# Each block is 24x24
# The next block on the X axis adds 12px to x and subtracts 6px from y in the image
@@ -449,6 +427,7 @@ class ChunkRenderer(object):
c_overviewer.render_loop(self, img, xoff, yoff, blockData_expanded)
tileEntities = get_tileentity_data(self.level)
for entity in tileEntities:
if entity['id'] == 'Sign':
msg=' \n'.join([entity['Text1'], entity['Text2'], entity['Text3'], entity['Text4']])

View File

@@ -2,8 +2,11 @@
var config = {
fileExt: '{imgformat}',
tileSize: 384,
defaultZoom: 1,
defaultZoom: 2,
maxZoom: {maxzoom},
// center on this point, in world coordinates, ex:
//center: [0,0,0],
center: {spawn_coords},
cacheMinutes: 0, // Change this to have browsers automatically request new images every x minutes
bg_color: '#1A1A1A',
debug: false
@@ -51,10 +54,3 @@ var mapTypeData=[
var mapTypeData = {maptypedata};
// Please leave the following variables here:
var markerCollection = {}; // holds groups of markers
var map;
var markersInit = false;
var regionsInit = false;

View File

@@ -94,6 +94,9 @@ class MapGen(object):
config = config.replace(
"{imgformat}", str(imgformat))
config = config.replace("{spawn_coords}",
json.dumps(list(self.world.spawn)))
# create generated map type data, from given quadtrees
# FIXME hook this into render_modes in setup.py, somehow
overlay_types = ['spawn']
@@ -131,6 +134,11 @@ class MapGen(object):
self.web_assets_hook(self)
return
def finalize(self):
if self.skipjs:
return
# since we will only discover PointsOfInterest in chunks that need to be
# [re]rendered, POIs like signs in unchanged chunks will not be listed
# in self.world.POI. To make sure we don't remove these from markers.js
@@ -157,5 +165,3 @@ class MapGen(object):
output.write(' // ]},\n')
output.write('];')
if self.web_assets_hook:
self.web_assets_hook(self)

View File

@@ -58,14 +58,16 @@ def main():
except NotImplementedError:
cpus = 1
avail_rendermodes = c_overviewer.get_render_modes()
parser = ConfigOptionParser(usage=helptext, config="settings.py")
parser.add_option("-V", "--version", dest="version", help="Displays version information and then exits", action="store_true")
parser.add_option("-p", "--processes", dest="procs", help="How many worker processes to start. Default %s" % cpus, default=cpus, action="store", type="int")
parser.add_option("-z", "--zoom", dest="zoom", help="Sets the zoom level manually instead of calculating it. This can be useful if you have outlier chunks that make your world too big. This value will make the highest zoom level contain (2**ZOOM)^2 tiles", action="store", type="int", configFileOnly=True)
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", commandLineOnly=True)
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.")
# TODO hook this up to render_modes in setup.py
parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render type: normal (default), lighting, night, or spawn.", type="choice", choices=["normal", "lighting", "night", "spawn"], required=True, default="normal", listify=True)
parser.add_option("--rendermodes", dest="rendermode", help="Specifies the render types, separated by commas. Use --list-rendermodes to list them all.", type="choice", choices=avail_rendermodes, required=True, default=avail_rendermodes[0], listify=True)
parser.add_option("--list-rendermodes", dest="list_rendermodes", action="store_true", help="List available render modes and exit.", commandLineOnly=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.", configFileOnly=True )
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%", configFileOnly=True)
parser.add_option("--web-assets-hook", dest="web_assets_hook", help="If provided, run this function after the web assets have been copied, but before actual tile rendering begins. It should accept a QuadtreeGen object as its only argument.", action="store", metavar="SCRIPT", type="function", configFileOnly=True)
@@ -90,6 +92,13 @@ def main():
pass
sys.exit(0)
if options.list_rendermodes:
rendermode_info = map(c_overviewer.get_render_mode_info, avail_rendermodes)
name_width = max(map(lambda i: len(i['name']), rendermode_info))
for info in rendermode_info:
print "{name:{0}} {description}".format(name_width, **info)
sys.exit(0)
if len(args) < 1:
print "You need to give me your world number or directory"
parser.print_help()
@@ -196,6 +205,8 @@ def main():
# render the tiles!
r.go(options.procs)
m.finalize()
def delete_all(worlddir, tiledir):
# TODO should we delete tiledir here too?

View File

@@ -333,7 +333,7 @@ class QuadtreeGen(object):
def render_worldtile(self, chunks, colstart, colend, rowstart, rowend, path):
def render_worldtile(self, chunks, colstart, colend, rowstart, rowend, path, poi_queue=None):
"""Renders just the specified chunks into a tile and save it. Unlike usual
python conventions, rowend and colend are inclusive. Additionally, the
chunks around the edges are half-way cut off (so that neighboring tiles
@@ -434,6 +434,8 @@ class QuadtreeGen(object):
# Compile this image
tileimg = Image.new("RGBA", (width, height), (38,92,255,0))
world = self.world
rendermode = self.rendermode
# col colstart will get drawn on the image starting at x coordinates -(384/2)
# row rowstart will get drawn on the image starting at y coordinates -(192/2)
for col, row, chunkx, chunky, regionfile in chunks:
@@ -441,8 +443,10 @@ class QuadtreeGen(object):
ypos = -96 + (row-rowstart)*96
# draw the chunk!
# TODO POI queue
chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), self, False, None)
a = chunk.ChunkRenderer((chunkx, chunky), world, rendermode, poi_queue)
a.chunk_render(tileimg, xpos, ypos, None)
# chunk.render_to_image((chunkx, chunky), tileimg, (xpos, ypos), self, False, None)
# Save them
tileimg.save(imgpath)

View File

@@ -14,6 +14,7 @@
# with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
import multiprocessing
import Queue
import itertools
from itertools import cycle, islice
import os
@@ -59,6 +60,13 @@ def pool_initializer(rendernode):
#stash the quadtree objects in a global variable after fork() for windows compat.
global child_rendernode
child_rendernode = rendernode
for quadtree in rendernode.quadtrees:
if quadtree.world.useBiomeData:
import textures
# make sure we've at least *tried* to load the color arrays in this process...
textures.prepareBiomeData(quadtree.world.worlddir)
if not textures.grasscolor or not textures.foliagecolor:
raise Exception("Can't find grasscolor.png or foliagecolor.png")
#http://docs.python.org/library/itertools.html
def roundrobin(iterables):
@@ -84,10 +92,22 @@ class RenderNode(object):
self.quadtrees = quadtrees
#bind an index value to the quadtree so we can find it again
#and figure out which worlds are where
i = 0
self.worlds = []
for q in quadtrees:
q._render_index = i
i += 1
if q.world not in self.worlds:
self.worlds.append(q.world)
manager = multiprocessing.Manager()
# queue for receiving interesting events from the renderer
# (like the discovery of signs!
#stash into the world object like we stash an index into the quadtree
for world in self.worlds:
world.poi_q = manager.Queue()
def print_statusline(self, complete, total, level, unconditional=False):
if unconditional:
@@ -111,12 +131,14 @@ class RenderNode(object):
# Create a pool
if procs == 1:
pool = FakePool()
global child_rendernode
child_rendernode = self
pool_initializer(self)
else:
pool = multiprocessing.Pool(processes=procs,initializer=pool_initializer,initargs=(self,))
#warm up the pool so it reports all the worker id's
if logging.getLogger().level >= 10:
pool.map(bool,xrange(multiprocessing.cpu_count()),1)
else:
pool.map_async(bool,xrange(multiprocessing.cpu_count()),1)
quadtrees = self.quadtrees
@@ -151,6 +173,19 @@ class RenderNode(object):
timestamp = timestamp2
count_to_remove = (1000//batch_size)
if count_to_remove < len(results):
for world in self.worlds:
try:
while (1):
# an exception will break us out of this loop
item = world.poi_q.get(block=False)
if item[0] == "newpoi":
if item[1] not in world.POI:
#print "got an item from the queue!"
world.POI.append(item[1])
elif item[0] == "removePOI":
world.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], world.persistentData['POI'])
except Queue.Empty:
pass
while count_to_remove > 0:
count_to_remove -= 1
complete += results.popleft().get()
@@ -166,6 +201,19 @@ class RenderNode(object):
while len(results) > 0:
complete += results.popleft().get()
self.print_statusline(complete, total, 1)
for world in self.worlds:
try:
while (1):
# an exception will break us out of this loop
item = world.poi_q.get(block=False)
if item[0] == "newpoi":
if item[1] not in world.POI:
#print "got an item from the queue!"
world.POI.append(item[1])
elif item[0] == "removePOI":
world.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], world.persistentData['POI'])
except Queue.Empty:
pass
self.print_statusline(complete, total, 1, True)
@@ -275,6 +323,7 @@ def render_worldtile_batch(batch):
rowstart = job[3]
rowend = job[4]
path = job[5]
poi_queue = quadtree.world.poi_q
path = quadtree.full_tiledir+os.sep+path
# (even if tilechunks is empty, render_worldtile will delete
# existing images if appropriate)
@@ -282,7 +331,7 @@ def render_worldtile_batch(batch):
tilechunks = quadtree.get_chunks_in_range(colstart, colend, rowstart,rowend)
#logging.debug(" tilechunks: %r", tilechunks)
quadtree.render_worldtile(tilechunks,colstart, colend, rowstart, rowend, path)
quadtree.render_worldtile(tilechunks,colstart, colend, rowstart, rowend, path, poi_queue)
return count
@catch_keyboardinterrupt

View File

@@ -50,13 +50,15 @@ except AttributeError:
numpy_include = numpy.get_numpy_include()
# used to figure out what files to compile
# TODO and, potentially, to check which are available
render_modes = ['normal', 'lighting', 'night', 'spawn', 'overlay']
render_modes = ['normal', 'overlay', 'lighting', 'night', 'spawn']
c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/endian.c', 'src/rendermodes.c']
c_overviewer_files += map(lambda mode: 'src/rendermode-%s.c' % (mode,), render_modes)
setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], extra_link_args=[]))
c_overviewer_includes = ['src/overviewer.h', 'src/rendermodes.h']
setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include], depends=c_overviewer_includes, extra_link_args=[]))
# tell build_ext to build the extension in-place
# (NOT in build/)
setup_kwargs['options']['build_ext'] = {'inplace' : 1}

View File

@@ -23,7 +23,7 @@
static int endianness = UNKNOWN_ENDIAN;
void init_endian() {
void init_endian(void) {
/* figure out what our endianness is! */
short word = 0x0001;
char* byte = (char*)(&word);

View File

@@ -22,6 +22,10 @@ static PyMethodDef COverviewerMethods[] = {
"alpha over composite function"},
{"render_loop", chunk_render, METH_VARARGS,
"Renders stuffs"},
{"get_render_modes", get_render_modes, METH_VARARGS,
"returns available render modes"},
{"get_render_mode_info", get_render_mode_info, METH_VARARGS,
"returns info for a particular render mode"},
{NULL, NULL, 0, NULL} /* Sentinel */
};

View File

@@ -228,6 +228,7 @@ rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject
}
RenderModeInterface rendermode_lighting = {
"lighting", "draw shadows from the lighting data",
sizeof(RenderModeLighting),
rendermode_lighting_start,
rendermode_lighting_finish,

View File

@@ -60,6 +60,7 @@ rendermode_night_draw(void *data, RenderState *state, PyObject *src, PyObject *m
}
RenderModeInterface rendermode_night = {
"night", "like \"lighting\", except at night",
sizeof(RenderModeNight),
rendermode_night_start,
rendermode_night_finish,

View File

@@ -164,6 +164,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *
}
RenderModeInterface rendermode_normal = {
"normal", "nothing special, just render the blocks",
sizeof(RenderModeNormal),
rendermode_normal_start,
rendermode_normal_finish,

View File

@@ -119,6 +119,7 @@ rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject
}
RenderModeInterface rendermode_overlay = {
"overlay", "base rendermode for informational overlays",
sizeof(RenderModeOverlay),
rendermode_overlay_start,
rendermode_overlay_finish,

View File

@@ -105,6 +105,7 @@ rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *m
}
RenderModeInterface rendermode_spawn = {
"spawn", "draws a red overlay where monsters can spawn at night",
sizeof(RenderModeSpawn),
rendermode_spawn_start,
rendermode_spawn_finish,

View File

@@ -18,21 +18,84 @@
#include "overviewer.h"
#include <string.h>
/* list of all render modes, ending in NULL
all of these will be available to the user, so DON'T include modes
that are only useful as a base for other modes. */
static RenderModeInterface *render_modes[] = {
&rendermode_normal,
&rendermode_lighting,
&rendermode_night,
&rendermode_spawn,
NULL
};
/* decides which render mode to use */
RenderModeInterface *get_render_mode(RenderState *state) {
/* default: normal */
RenderModeInterface *iface = &rendermode_normal;
unsigned int i;
/* default: NULL --> an error */
RenderModeInterface *iface = NULL;
PyObject *rendermode_py = PyObject_GetAttrString(state->self, "rendermode");
const char *rendermode = PyString_AsString(rendermode_py);
if (strcmp(rendermode, "lighting") == 0) {
iface = &rendermode_lighting;
} else if (strcmp(rendermode, "night") == 0) {
iface = &rendermode_night;
} else if (strcmp(rendermode, "spawn") == 0) {
iface = &rendermode_spawn;
for (i = 0; render_modes[i] != NULL; i++) {
if (strcmp(render_modes[i]->name, rendermode) == 0) {
iface = render_modes[i];
break;
}
}
Py_DECREF(rendermode_py);
return iface;
}
/* bindings for python -- get all the rendermode names */
PyObject *get_render_modes(PyObject *self, PyObject *args) {
PyObject *modes;
unsigned int i;
if (!PyArg_ParseTuple(args, ""))
return NULL;
modes = PyList_New(0);
if (modes == NULL)
return NULL;
for (i = 0; render_modes[i] != NULL; i++) {
PyObject *name = PyString_FromString(render_modes[i]->name);
PyList_Append(modes, name);
Py_DECREF(name);
}
return modes;
}
/* more bindings -- return info for a given rendermode name */
PyObject *get_render_mode_info(PyObject *self, PyObject *args) {
const char* rendermode;
PyObject *info;
unsigned int i;
if (!PyArg_ParseTuple(args, "s", &rendermode))
return NULL;
info = PyDict_New();
if (info == NULL)
return NULL;
for (i = 0; render_modes[i] != NULL; i++) {
if (strcmp(render_modes[i]->name, rendermode) == 0) {
PyObject *tmp;
tmp = PyString_FromString(render_modes[i]->name);
PyDict_SetItemString(info, "name", tmp);
Py_DECREF(tmp);
tmp = PyString_FromString(render_modes[i]->description);
PyDict_SetItemString(info, "description", tmp);
Py_DECREF(tmp);
return info;
}
}
Py_DECREF(info);
Py_RETURN_NONE;
}

View File

@@ -29,7 +29,7 @@
* (see rendermode-night.c for a simple example derived from
* the "lighting" mode)
*
* * add a condition to get_render_mode() in rendermodes.c
* * add your mode to the list in rendermodes.c
*/
#ifndef __RENDERMODES_H_INCLUDED__
@@ -39,6 +39,11 @@
/* rendermode interface */
typedef struct {
/* the name of this mode */
const char* name;
/* the short description of this render mode */
const char* description;
/* the size of the local storage for this rendermode */
unsigned int data_size;
@@ -53,6 +58,9 @@ typedef struct {
/* figures out the render mode to use from the given ChunkRenderer */
RenderModeInterface *get_render_mode(RenderState *state);
/* python bindings */
PyObject *get_render_modes(PyObject *self, PyObject *args);
PyObject *get_render_mode_info(PyObject *self, PyObject *args);
/* individual rendermode interface declarations follow */

View File

@@ -1,3 +1,10 @@
var markerCollection = {}; // holds groups of markers
var map;
var markersInit = false;
var regionsInit = false;
var prevInfoWindow = null;
function prepareSignMarker(marker, item) {
@@ -275,8 +282,12 @@ function initialize() {
var query = location.search.substring(1);
var lat = 0.5;
var lng = 0.5;
var defaultCenter = fromWorldToLatLng(config.center[0],
config.center[1],
config.center[2]);
var lat = defaultCenter.lat();
var lng = defaultCenter.lng();
var zoom = config.defaultZoom;
var pairs = query.split("&");
for (var i=0; i<pairs.length; i++) {

View File

@@ -84,9 +84,10 @@ class World(object):
self.regionfiles = regionfiles
# set the number of region file handles we will permit open at any time before we start closing them
# self.regionlimit = 1000
# the max number of chunks we will keep before removing them
self.chunklimit = 1024*6 # this should be a multipule of the max chunks per region or things could get wonky ???
# the max number of chunks we will keep before removing them (includes emptry chunks)
self.chunklimit = 1024
self.chunkcount = 0
self.empty_chunk = [None,None]
logging.debug("Done scanning regions")
# figure out chunk format is in use
@@ -117,6 +118,7 @@ class World(object):
# some defaults
self.persistentData = dict(POI=[])
def get_region_path(self, chunkX, chunkY):
"""Returns the path to the region that contains chunk (chunkX, chunkY)
"""
@@ -131,16 +133,18 @@ class World(object):
chunks = regioninfo[2]
chunk_data = chunks.get((x,y))
if chunk_data is None:
nbt = self.load_region(filename).load_chunk(x, y)
if nbt is None:
chunks[(x,y)] = [None,None]
return None ## return none. I think this is who we should indicate missing chunks
#raise IOError("No such chunk in region: (%i, %i)" % (x, y))
#prune the cache if required
if self.chunkcount > self.chunklimit: #todo: make the emptying the chunk cache slightly less crazy
[self.reload_region(regionfile) for regionfile in self.regions if regionfile <> filename]
self.chunkcount = 0
self.chunkcount += 1
nbt = self.load_region(filename).load_chunk(x, y)
if nbt is None:
chunks[(x,y)] = self.empty_chunk
return None ## return none. I think this is who we should indicate missing chunks
#raise IOError("No such chunk in region: (%i, %i)" % (x, y))
#we cache the transformed data, not it's raw form
data = nbt.read_all()
level = data[1]['Level']
@@ -222,6 +226,7 @@ class World(object):
self.POI.append( dict(x=spawnX, y=spawnY, z=spawnZ,
msg="Spawn", type="spawn", chunk=(inChunkX,inChunkZ)))
self.spawn = (spawnX, spawnY, spawnZ)
def go(self, procs):
"""Scan the world directory, to fill in