0

Merge branch 'rewrite' into anvil

Conflicts:
	overviewer_core/src/overviewer.h
	overviewer_core/tileset.py
	overviewer_core/util.py
	overviewer_core/world.py
This commit is contained in:
Aaron Griffith
2012-02-26 19:39:58 -05:00
21 changed files with 636 additions and 468 deletions

View File

@@ -6,4 +6,5 @@ include sample.settings.py
recursive-include contrib/ *.py
recursive-include overviewer_core/ *.py
recursive-include overviewer_core/src/ *.c *.h
recursive-include overviewer_core/src/primitives/ *.c *.h
recursive-include overviewer_core/data/ *

View File

@@ -430,6 +430,26 @@ SmoothLighting
(same as Lighting)
ClearBase
Forces the background to be transparent. Use this in place of Base
for rendering pure overlays.
SpawnOverlay
Color the map red in areas where monsters can spawn. Either use
this on top of other modes, or on top of ClearBase to create a
pure overlay.
MineralOverlay
Color the map according to what minerals can be found
underneath. Either use this on top of other modes, or on top of
ClearBase to create a pure overlay.
**Options**
minerals
A list of (blockid, (r, g, b)) tuples to use as colors. If not
provided, a default list of common minerals is used.
Defining Custom Rendermodes
---------------------------
Each rendermode primitive listed above is a Python *class* that is automatically

View File

@@ -365,8 +365,11 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
if rset == None: # indicates no such dimension was found:
logging.error("Sorry, you requested dimension '%s' for %s, but I couldn't find it", render['dimension'], render_name)
return 1
# If this is to be a rotated regionset, wrap it in a RotatedRegionSet
# object
if (render['northdirection'] > 0):
rset = rset.rotate(render['northdirection'])
rset = world.RotatedRegionSet(rset, render['northdirection'])
logging.debug("Using RegionSet %r", rset)
# create our TileSet from this RegionSet
@@ -380,6 +383,12 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
tset = tileset.TileSet(rset, assetMrg, tex, tileSetOpts, tileset_dir)
tilesets.append(tset)
# Do tileset preprocessing here, before we start dispatching jobs
for ts in tilesets:
ts.do_preprocessing()
# Output initial static data and configuration
assetMrg.initialize(tilesets)
# multiprocessing dispatcher
if config['processes'] == 1:

View File

@@ -72,7 +72,23 @@ directory.
# TODO based on the type, so something
POI[regionset.name].append
def finalize(self, tilesets):
def initialize(self, tilesets):
"""Similar to finalize() but calls the tilesets' get_initial_data()
instead of get_persistent_data() to compile the generated javascript
config.
"""
return self.finalize(tilesets, True)
def finalize(self, tilesets, initial=False):
"""Called to output the generated javascript and all static files to
the output directory
"""
if not initial:
get_data = lambda tileset: tileset.get_persistent_data()
else:
get_data = lambda tileset: tileset.get_initial_data()
# dictionary to hold the overviewerConfig.js settings that we will dumps
dump = dict()
@@ -94,7 +110,7 @@ directory.
# based on the tilesets we have, group them by worlds
worlds = []
for tileset in tilesets:
full_name = tileset.get_persistent_data()['world']
full_name = get_data(tileset)['world']
if full_name not in worlds:
worlds.append(full_name)
@@ -120,7 +136,7 @@ directory.
for tileset in tilesets:
dump['tilesets'].append(tileset.get_persistent_data())
dump['tilesets'].append(get_data(tileset))
# write a blank image
blank = Image.new("RGBA", (1,1), tileset.options.get('bgcolor'))
@@ -128,11 +144,10 @@ directory.
jsondump = json.dumps(dump, indent=4)
with codecs.open(os.path.join(self.outputdir, 'overviewerConfig.js'), 'w', encoding='UTF-8') as f:
f.write("var overviewerConfig = " + jsondump + ";\n")
with util.FileReplacer(os.path.join(self.outputdir, "overviewerConfig.js")) as tmpfile:
with codecs.open(tmpfile, 'w', encoding='UTF-8') as f:
f.write("var overviewerConfig = " + jsondump + ";\n")
# copy web assets into destdir:
global_assets = os.path.join(util.get_program_path(), "overviewer_core", "data", "web_assets")
if not os.path.isdir(global_assets):
@@ -143,23 +158,16 @@ directory.
js_src = os.path.join(util.get_program_path(), "overviewer_core", "data", "js_src")
if not os.path.isdir(js_src):
js_src = os.path.join(util.get_program_path(), "js_src")
with open(os.path.join(self.outputdir, "overviewer.js"), "w") as fout:
# first copy in js_src/overviewer.js
with open(os.path.join(js_src, "overviewer.js")) as f:
fout.write(f.read())
# now copy in the rest
for js in os.listdir(js_src):
if not js.endswith("overviewer.js"):
with open(os.path.join(js_src,js)) as f:
fout.write(f.read())
# do the same with the local copy, if we have it
# TODO
# if self.web_assets_path:
# util.mirror_dir(self.web_assets_path, self.outputdir)
with util.FileReplacer(os.path.join(self.outputdir, "overviewer.js")) as tmpfile:
with open(tmpfile, "w") as fout:
# first copy in js_src/overviewer.js
with open(os.path.join(js_src, "overviewer.js"), 'r') as f:
fout.write(f.read())
# now copy in the rest
for js in os.listdir(js_src):
if not js.endswith("overviewer.js") and js.endswith(".js"):
with open(os.path.join(js_src,js)) as f:
fout.write(f.read())
# helper function to get a label for the given rendermode
def get_render_mode_label(rendermode):
info = get_render_mode_info(rendermode)
@@ -177,7 +185,6 @@ directory.
versionstr = "%s (%s)" % (overviewer_version.VERSION, overviewer_version.HASH[:7])
index = index.replace("{version}", versionstr)
with codecs.open(os.path.join(self.outputdir, "index.html"), 'w', encoding='UTF-8') as output:
output.write(index)
with util.FileReplacer(indexpath) as indexpath:
with codecs.open(indexpath, 'w', encoding='UTF-8') as output:
output.write(index)

View File

@@ -36,21 +36,23 @@ overviewer.views.WorldView = Backbone.View.extend({
overviewer.views.WorldSelectorView = Backbone.View.extend({
initialize: function() {
// a div will have already been created for us, we just
// need to register it with the google maps control
var selectBox = document.createElement('select');
$.each(overviewer.collections.worldViews, function(index, elem) {
var o = document.createElement("option");
o.value = elem.model.get("name");
o.innerHTML = elem.model.get("name");
$(o).data("viewObj", elem);
selectBox.appendChild(o);
if(overviewer.collections.worldViews.length > 1) {
// a div will have already been created for us, we just
// need to register it with the google maps control
var selectBox = document.createElement('select');
$.each(overviewer.collections.worldViews, function(index, elem) {
var o = document.createElement("option");
o.value = elem.model.get("name");
o.innerHTML = elem.model.get("name");
$(o).data("viewObj", elem);
selectBox.appendChild(o);
});
});
this.el.appendChild(selectBox);
overviewer.map.controls[google.maps.ControlPosition.TOP_LEFT].push(this.el);
},
this.el.appendChild(selectBox);
overviewer.map.controls[google.maps.ControlPosition.TOP_LEFT].push(this.el);
}
},
events: {
"change select": "changeWorld"
},

View File

@@ -49,14 +49,9 @@ class Dispatcher(object):
"""
# TODO use status callback
# preprocessing
for tileset in tilesetlist:
tileset.do_preprocessing()
# setup tilesetlist
self.setup_tilesets(tilesetlist)
# iterate through all possible phases
num_phases = [tileset.get_num_phases() for tileset in tilesetlist]
for phase in xrange(max(num_phases)):

View File

@@ -146,6 +146,45 @@ class Lighting(RenderPrimitive):
class SmoothLighting(Lighting):
name = "smooth-lighting"
class ClearBase(RenderPrimitive):
name = "clear-base"
class Overlay(RenderPrimitive):
name = "overlay"
@property
def whitecolor(self):
whitecolor = getattr(self, "_whitecolor", None)
if whitecolor:
return whitecolor
white = Image.new("RGBA", (24,24), (255, 255, 255, 255))
self._whitecolor = white
return white
@property
def facemask_top(self):
facemask_top = getattr(self, "_facemask_top", None)
if facemask_top:
return facemask_top
white = Image.new("L", (24,24), 255)
top = Image.new("L", (24,24), 0)
toppart = textures.Textures.transform_image_top(white)
top.paste(toppart, (0,0))
for x,y in [(3,4), (7,2), (11,0)]:
top.putpixel((x,y), 255)
self._facemask_top = top
return top
class SpawnOverlay(Overlay):
name = "overlay-spawn"
class MineralOverlay(Overlay):
name = "overlay-mineral"
options = {
'minerals' : ('a list of (blockid, (r, g, b)) tuples for coloring minerals', None),
}
# Built-in rendermodes for your convenience!
normal = [Base(), EdgeLines()]
lighting = [Base(), EdgeLines(), Lighting()]

View File

@@ -71,7 +71,7 @@ renders = Setting(required=True, default={},
"optimizeimg": Setting(required=True, validator=validateOptImg, default=0),
"nomarkers": Setting(required=False, validator=validateBool, default=None),
"texturepath": Setting(required=False, validator=validateTexturePath, default=None),
"renderchecks": Setting(required=True, validator=validateInt, default=0),
"renderchecks": Setting(required=False, validator=validateInt, default=None),
"rerenderprob": Setting(required=True, validator=validateFloat, default=0),
# Remove this eventually (once people update their configs)

View File

@@ -89,3 +89,12 @@ class Signal(object):
# convenience
def __call__(self, *args, **kwargs):
self.emit(*args, **kwargs)
# force pickled signals to redirect to existing signals
def __getstate__(self):
return self.fullname
def __setstate__(self, fullname):
for attr in dir(self.signals[fullname]):
if attr.startswith('_'):
continue
setattr(self, attr, getattr(self.signals[fullname], attr))

View File

@@ -26,7 +26,7 @@
// increment this value if you've made a change to the c extesion
// and want to force users to rebuild
#define OVERVIEWER_EXTENSION_VERSION 25
#define OVERVIEWER_EXTENSION_VERSION 26
/* Python PIL, and numpy headers */
#include <Python.h>

View File

@@ -0,0 +1,49 @@
/*
* This file is part of the Minecraft Overviewer.
*
* Minecraft Overviewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Minecraft Overviewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../overviewer.h"
static int
clear_base_occluded(void *data, RenderState *state, int x, int y, int z) {
if ( (x != 0) && (y != 15) && (z != 127) &&
!render_mode_hidden(state->rendermode, x-1, y, z) &&
!render_mode_hidden(state->rendermode, x, y, z+1) &&
!render_mode_hidden(state->rendermode, x, y+1, z) &&
!is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) &&
!is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) &&
!is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) {
return 1;
}
return 0;
}
static void
clear_base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
/* clear the draw space -- set alpha to 0 within mask */
tint_with_mask(state->img, 255, 255, 255, 0, mask, state->imgx, state->imgy, 0, 0);
}
RenderPrimitiveInterface primitive_clear_base = {
"clear-base",
0,
NULL,
NULL,
clear_base_occluded,
NULL,
clear_base_draw,
};

View File

@@ -15,7 +15,14 @@
* with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "overviewer.h"
#include "overlay.h"
typedef struct {
/* inherits from overlay */
RenderPrimitiveOverlay parent;
void *minerals;
} RenderPrimitiveMineral;
struct MineralColor {
unsigned char blockid;
@@ -43,15 +50,16 @@ static struct MineralColor default_minerals[] = {
static void get_color(void *data, RenderState *state,
unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) {
int x = state->x, y = state->y, z_max = state->z + 1, z;
int x = state->x, z = state->z, y_max, y;
int max_i = -1;
RenderModeMineral* self = (RenderModeMineral *)data;
RenderPrimitiveMineral* self = (RenderPrimitiveMineral *)data;
struct MineralColor *minerals = (struct MineralColor *)(self->minerals);
*a = 0;
for (z = 0; z <= z_max; z++) {
y_max = state->y + 1;
for (y = state->chunky * -16; y <= y_max; y++) {
int i, tmp;
unsigned char blockid = getArrayByte3D(state->blocks, x, y, z);
unsigned short blockid = get_data(state, BLOCKS, x, y, z);
for (i = 0; (max_i == -1 || i < max_i) && minerals[i].blockid != 0; i++) {
if (minerals[i].blockid == blockid) {
@@ -59,7 +67,7 @@ static void get_color(void *data, RenderState *state,
*g = minerals[i].g;
*b = minerals[i].b;
tmp = (128 - z_max + z) * 2 - 40;
tmp = (128 - y_max + y) * 2 - 40;
*a = MIN(MAX(0, tmp), 255);
max_i = i;
@@ -70,20 +78,21 @@ static void get_color(void *data, RenderState *state,
}
static int
rendermode_mineral_start(void *data, RenderState *state, PyObject *options) {
overlay_mineral_start(void *data, RenderState *state, PyObject *support) {
PyObject *opt;
RenderModeMineral* self;
RenderPrimitiveMineral* self;
/* first, chain up */
int ret = rendermode_overlay.start(data, state, options);
int ret = primitive_overlay.start(data, state, support);
if (ret != 0)
return ret;
/* now do custom initializations */
self = (RenderModeMineral *)data;
self = (RenderPrimitiveMineral *)data;
opt = PyDict_GetItemString(options, "minerals");
if (opt) {
if (!render_mode_parse_option(support, "minerals", "O", &(opt)))
return 1;
if (opt && opt != Py_None) {
struct MineralColor *minerals = NULL;
Py_ssize_t minerals_size = 0, i;
/* create custom minerals */
@@ -110,6 +119,7 @@ rendermode_mineral_start(void *data, RenderState *state, PyObject *options) {
} else {
self->minerals = default_minerals;
}
Py_XDECREF(opt);
/* setup custom color */
self->parent.get_color = get_color;
@@ -118,50 +128,24 @@ rendermode_mineral_start(void *data, RenderState *state, PyObject *options) {
}
static void
rendermode_mineral_finish(void *data, RenderState *state) {
overlay_mineral_finish(void *data, RenderState *state) {
/* first free all *our* stuff */
RenderModeMineral* self = (RenderModeMineral *)data;
RenderPrimitiveMineral* self = (RenderPrimitiveMineral *)data;
if (self->minerals && self->minerals != default_minerals) {
free(self->minerals);
}
/* now, chain up */
rendermode_overlay.finish(data, state);
primitive_overlay.finish(data, state);
}
static int
rendermode_mineral_occluded(void *data, RenderState *state, int x, int y, int z) {
/* no special occlusion here */
return rendermode_overlay.occluded(data, state, x, y, z);
}
static int
rendermode_mineral_hidden(void *data, RenderState *state, int x, int y, int z) {
/* no special hiding here */
return rendermode_overlay.hidden(data, state, x, y, z);
}
static void
rendermode_mineral_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
/* draw normally */
rendermode_overlay.draw(data, state, src, mask, mask_light);
}
const RenderModeOption rendermode_mineral_options[] = {
{"minerals", "a list of (blockid, (r, g, b)) tuples for coloring minerals"},
{NULL, NULL}
};
RenderModeInterface rendermode_mineral = {
"mineral", "Mineral",
"draws a colored overlay showing where ores are located",
rendermode_mineral_options,
&rendermode_overlay,
sizeof(RenderModeMineral),
rendermode_mineral_start,
rendermode_mineral_finish,
rendermode_mineral_occluded,
rendermode_mineral_hidden,
rendermode_mineral_draw,
RenderPrimitiveInterface primitive_overlay_mineral = {
"overlay-mineral",
sizeof(RenderPrimitiveMineral),
overlay_mineral_start,
overlay_mineral_finish,
NULL,
NULL,
overlay_draw,
};

View File

@@ -0,0 +1,92 @@
/*
* This file is part of the Minecraft Overviewer.
*
* Minecraft Overviewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Minecraft Overviewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "overlay.h"
#include <math.h>
typedef struct {
/* inherits from overlay */
RenderPrimitiveOverlay parent;
} RenderPrimitiveSpawn;
static void get_color(void *data, RenderState *state,
unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) {
RenderPrimitiveSpawn* self = (RenderPrimitiveSpawn *)data;
int x = state->x, y = state->y, z = state->z;
int y_light = y + 1;
unsigned char blocklight, skylight;
PyObject *block_py;
/* set a nice, pretty red color */
*r = 229;
*g = 36;
*b = 38;
/* default to no overlay, until told otherwise */
*a = 0;
if (block_has_property(state->block, NOSPAWN)) {
/* nothing can spawn on this */
return;
}
blocklight = get_data(state, BLOCKLIGHT, x, y_light, z);
skylight = get_data(state, SKYLIGHT, x, y_light, z);
if (MAX(blocklight, skylight) <= 7) {
/* hostile mobs spawn in daylight */
*a = 240;
} else if (MAX(blocklight, skylight - 11) <= 7) {
/* hostile mobs spawn at night */
*a = 150;
}
}
static int
overlay_spawn_start(void *data, RenderState *state, PyObject *support) {
RenderPrimitiveSpawn* self;
/* first, chain up */
int ret = primitive_overlay.start(data, state, support);
if (ret != 0)
return ret;
/* now do custom initializations */
self = (RenderPrimitiveSpawn *)data;
self->parent.get_color = get_color;
return 0;
}
static void
overlay_spawn_finish(void *data, RenderState *state) {
RenderPrimitiveSpawn* self = (RenderPrimitiveSpawn *)data;
/* chain up */
primitive_overlay.finish(data, state);
}
RenderPrimitiveInterface primitive_overlay_spawn = {
"overlay-spawn",
sizeof(RenderPrimitiveSpawn),
overlay_spawn_start,
overlay_spawn_finish,
NULL,
NULL,
overlay_draw,
};

View File

@@ -0,0 +1,99 @@
/*
* This file is part of the Minecraft Overviewer.
*
* Minecraft Overviewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Minecraft Overviewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "overlay.h"
static void get_color(void *data, RenderState *state,
unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) {
*r = 200;
*g = 200;
*b = 255;
*a = 155;
}
static int
overlay_start(void *data, RenderState *state, PyObject *support) {
PyObject *facemasks_py;
RenderPrimitiveOverlay *self = (RenderPrimitiveOverlay *)data;
self->facemask_top = PyObject_GetAttrString(support, "facemask_top");
self->white_color = PyObject_GetAttrString(support, "whitecolor");
self->get_color = get_color;
return 0;
}
static void
overlay_finish(void *data, RenderState *state) {
RenderPrimitiveOverlay *self = (RenderPrimitiveOverlay *)data;
Py_DECREF(self->facemask_top);
Py_DECREF(self->white_color);
}
void
overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
RenderPrimitiveOverlay *self = (RenderPrimitiveOverlay *)data;
unsigned char r, g, b, a;
unsigned short top_block;
// exactly analogous to edge-line code for these special blocks
int increment=0;
if (state->block == 44) // half-step
increment=6;
else if (state->block == 78) // snow
increment=9;
/* skip rendering the overlay if we can't see it */
top_block = get_data(state, BLOCKS, state->x, state->y+1, state->z);
if (!is_transparent(top_block)) {
return;
}
/* check to be sure this block is solid/fluid */
if (block_has_property(top_block, SOLID) || block_has_property(top_block, FLUID)) {
/* top block is fluid or solid, skip drawing */
return;
}
/* check to be sure this block is solid/fluid */
if (!block_has_property(state->block, SOLID) && !block_has_property(state->block, FLUID)) {
/* not fluid or solid, skip drawing the overlay */
return;
}
/* get our color info */
self->get_color(data, state, &r, &g, &b, &a);
/* do the overlay */
if (a > 0) {
alpha_over(state->img, self->white_color, self->facemask_top, state->imgx, state->imgy + increment, 0, 0);
tint_with_mask(state->img, r, g, b, a, self->facemask_top, state->imgx, state->imgy + increment, 0, 0);
}
}
RenderPrimitiveInterface primitive_overlay = {
"overlay",
sizeof(RenderPrimitiveOverlay),
overlay_start,
overlay_finish,
NULL,
NULL,
overlay_draw,
};

View File

@@ -0,0 +1,31 @@
/*
* This file is part of the Minecraft Overviewer.
*
* Minecraft Overviewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Minecraft Overviewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../overviewer.h"
typedef struct {
/* top facemask and white color image, for drawing overlays */
PyObject *facemask_top, *white_color;
/* can be overridden in derived classes to control
overlay alpha and color
last four vars are r, g, b, a out */
void (*get_color)(void *, RenderState *,
unsigned char *, unsigned char *, unsigned char *, unsigned char *);
} RenderPrimitiveOverlay;
extern RenderPrimitiveInterface primitive_overlay;
void overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light);

View File

@@ -1,132 +0,0 @@
/*
* This file is part of the Minecraft Overviewer.
*
* Minecraft Overviewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Minecraft Overviewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "overviewer.h"
static void get_color(void *data, RenderState *state,
unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) {
*r = 200;
*g = 200;
*b = 255;
*a = 155;
}
static int
rendermode_overlay_start(void *data, RenderState *state, PyObject *options) {
PyObject *facemasks_py;
RenderModeOverlay *self = (RenderModeOverlay *)data;
facemasks_py = PyObject_GetAttrString(state->support, "facemasks");
/* borrowed reference, needs to be incref'd if we keep it */
self->facemask_top = PyTuple_GetItem(facemasks_py, 0);
Py_INCREF(self->facemask_top);
Py_DECREF(facemasks_py);
self->white_color = PyObject_GetAttrString(state->support, "white_color");
self->get_color = get_color;
return 0;
}
static void
rendermode_overlay_finish(void *data, RenderState *state) {
RenderModeOverlay *self = (RenderModeOverlay *)data;
Py_DECREF(self->facemask_top);
Py_DECREF(self->white_color);
}
static int
rendermode_overlay_occluded(void *data, RenderState *state, int x, int y, int z) {
if ( (x != 0) && (y != 15) && (z != 127) &&
!render_mode_hidden(state->rendermode, x-1, y, z) &&
!render_mode_hidden(state->rendermode, x, y, z+1) &&
!render_mode_hidden(state->rendermode, x, y+1, z) &&
!is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) &&
!is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) &&
!is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) {
return 1;
}
return 0;
}
static int
rendermode_overlay_hidden(void *data, RenderState *state, int x, int y, int z) {
/* overlays hide nothing by default */
return 0;
}
static void
rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
RenderModeOverlay *self = (RenderModeOverlay *)data;
unsigned char r, g, b, a;
// exactly analogous to edge-line code for these special blocks
int increment=0;
if (state->block == 44) // half-step
increment=6;
else if (state->block == 78) // snow
increment=9;
/* clear the draw space -- set alpha to 0 within mask */
tint_with_mask(state->img, 255, 255, 255, 0, mask, state->imgx, state->imgy, 0, 0);
/* skip rendering the overlay if we can't see it */
if (state->z != 127) {
unsigned char top_block = getArrayByte3D(state->blocks, state->x, state->y, state->z+1);
if (!is_transparent(top_block)) {
return;
}
/* check to be sure this block is solid/fluid */
if (block_has_property(top_block, SOLID) || block_has_property(top_block, FLUID)) {
/* top block is fluid or solid, skip drawing */
return;
}
}
/* check to be sure this block is solid/fluid */
if (!block_has_property(state->block, SOLID) && !block_has_property(state->block, FLUID)) {
/* not fluid or solid, skip drawing the overlay */
return;
}
/* get our color info */
self->get_color(data, state, &r, &g, &b, &a);
/* do the overlay */
if (a > 0) {
alpha_over(state->img, self->white_color, self->facemask_top, state->imgx, state->imgy + increment, 0, 0);
tint_with_mask(state->img, r, g, b, a, self->facemask_top, state->imgx, state->imgy + increment, 0, 0);
}
}
RenderModeInterface rendermode_overlay = {
"overlay", "Overlay",
"base rendermode for informational overlays",
NULL,
NULL,
sizeof(RenderModeOverlay),
rendermode_overlay_start,
rendermode_overlay_finish,
rendermode_overlay_occluded,
rendermode_overlay_hidden,
rendermode_overlay_draw,
};

View File

@@ -1,122 +0,0 @@
/*
* This file is part of the Minecraft Overviewer.
*
* Minecraft Overviewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Minecraft Overviewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "overviewer.h"
#include <math.h>
static void get_color(void *data, RenderState *state,
unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) {
RenderModeSpawn* self = (RenderModeSpawn *)data;
int x = state->x, y = state->y, z = state->z;
int z_light = z + 1;
unsigned char blocklight, skylight;
PyObject *block_py;
/* set a nice, pretty red color */
*r = 229;
*g = 36;
*b = 38;
/* default to no overlay, until told otherwise */
*a = 0;
if (block_has_property(state->block, NOSPAWN)) {
/* nothing can spawn on this */
return;
}
blocklight = getArrayByte3D(self->blocklight, x, y, MIN(127, z_light));
/* if we're at the top, force 15 (brightest!) skylight */
if (z_light == 128) {
skylight = 15;
} else {
skylight = getArrayByte3D(self->skylight, x, y, z_light);
}
if (MAX(blocklight, skylight) <= 7) {
/* hostile mobs spawn in daylight */
*a = 240;
} else if (MAX(blocklight, skylight - 11) <= 7) {
/* hostile mobs spawn at night */
*a = 150;
}
}
static int
rendermode_spawn_start(void *data, RenderState *state, PyObject *options) {
RenderModeSpawn* self;
/* first, chain up */
int ret = rendermode_overlay.start(data, state, options);
if (ret != 0)
return ret;
/* now do custom initializations */
self = (RenderModeSpawn *)data;
self->blocklight = get_chunk_data(state, CURRENT, BLOCKLIGHT, 1);
self->skylight = get_chunk_data(state, CURRENT, SKYLIGHT, 1);
/* setup custom color */
self->parent.get_color = get_color;
return 0;
}
static void
rendermode_spawn_finish(void *data, RenderState *state) {
/* first free all *our* stuff */
RenderModeSpawn* self = (RenderModeSpawn *)data;
Py_DECREF(self->blocklight);
Py_DECREF(self->skylight);
/* now, chain up */
rendermode_overlay.finish(data, state);
}
static int
rendermode_spawn_occluded(void *data, RenderState *state, int x, int y, int z) {
/* no special occlusion here */
return rendermode_overlay.occluded(data, state, x, y, z);
}
static int
rendermode_spawn_hidden(void *data, RenderState *state, int x, int y, int z) {
/* no special hiding here */
return rendermode_overlay.hidden(data, state, x, y, z);
}
static void
rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
/* draw normally */
rendermode_overlay.draw(data, state, src, mask, mask_light);
}
RenderModeInterface rendermode_spawn = {
"spawn", "Spawn",
"draws a red overlay where monsters can spawn at night",
NULL,
&rendermode_overlay,
sizeof(RenderModeSpawn),
rendermode_spawn_start,
rendermode_spawn_finish,
rendermode_spawn_occluded,
rendermode_spawn_hidden,
rendermode_spawn_draw,
};

View File

@@ -102,38 +102,4 @@ void render_mode_draw(RenderMode *self, PyObject *img, PyObject *mask, PyObject
works like PyArg_ParseTuple on a support object */
int render_mode_parse_option(PyObject *support, const char *name, const char *format, ...);
/* XXX individual rendermode interface declarations follow */
#ifdef OLD_MODES
/* OVERLAY */
typedef struct {
/* top facemask and white color image, for drawing overlays */
PyObject *facemask_top, *white_color;
/* can be overridden in derived classes to control
overlay alpha and color
last four vars are r, g, b, a out */
void (*get_color)(void *, RenderState *,
unsigned char *, unsigned char *, unsigned char *, unsigned char *);
} RenderModeOverlay;
extern RenderModeInterface rendermode_overlay;
/* SPAWN */
typedef struct {
/* inherits from overlay */
RenderModeOverlay parent;
PyObject *skylight, *blocklight;
} RenderModeSpawn;
extern RenderModeInterface rendermode_spawn;
/* MINERAL */
typedef struct {
/* inherits from overlay */
RenderModeOverlay parent;
void *minerals;
} RenderModeMineral;
extern RenderModeInterface rendermode_mineral;
#endif /* OLD_MODES */
#endif /* __RENDERMODES_H_INCLUDED__ */

View File

@@ -29,6 +29,7 @@ from itertools import product, izip
from PIL import Image
from .util import iterate_base4, convert_coords, unconvert_coords, roundrobin
from .util import FileReplacer
from .optimizeimages import optimize_image
import c_overviewer
@@ -189,7 +190,9 @@ class TileSet(object):
renderchecks
An integer indicating how to determine which tiles need updating
and which don't. This is one of three levels:
and which don't. This key is optional; if not specified, an
appropriate mode is determined from the persistent config obtained
from the asset manager. This is one of three levels:
0
Only render tiles that have chunks with a greater mtime than
@@ -258,8 +261,38 @@ class TileSet(object):
self.am = assetmanagerobj
self.textures = texturesobj
self.last_rendertime = self.am.get_tileset_config(self.options.get("name")).get('last_rendertime', 0)
self.this_rendertime = time.time()
config = self.am.get_tileset_config(self.options.get("name"))
self.config = config
self.last_rendertime = config.get('last_rendertime', 0)
if "renderchecks" not in self.options:
if not config:
# No persistent config? This is a full render then.
self.options['renderchecks'] = 2
logging.debug("This is the first time rendering %s. Doing" +
" a full-render",
self.options['name'])
elif config.get("render_in_progress", False):
# The last render must have been interrupted. The default should be
# 1 then, not 0
logging.warning(
"The last render for %s didn't finish. I'll be " +
"scanning all the tiles to make sure everything's up "+
"to date.",
self.options['name'],
)
logging.warning("You won't get percentage progress for "+
"this run only, because I don't know how many tiles "+
"need rendering. I'll be checking them as I go")
self.options['renderchecks'] = 1
else:
logging.debug("No rendercheck mode specified for %s. "+
"Rendering tile whose chunks have changed since %s",
self.options['name'],
time.strftime("%x %X", time.localtime(self.last_rendertime)),
)
self.options['renderchecks'] = 0
# Throughout the class, self.outputdir is an absolute path to the
# directory where we output tiles. It is assumed to exist.
@@ -298,8 +331,11 @@ class TileSet(object):
logging.warning("Just letting you know, your map requries %s zoom levels. This is REALLY big!",
self.treedepth)
# Do any tile re-arranging if necessary
self._rearrange_tiles()
# Do any tile re-arranging if necessary. Skip if there was no config
# from the asset-manager, which typically indicates this is a new
# render
if self.config:
self._rearrange_tiles()
# Do the chunk scan here
self.dirtytree = self._chunk_scan()
@@ -383,6 +419,20 @@ class TileSet(object):
name = str(tilepath[-1])
self._render_compositetile(dest, name)
def get_initial_data(self):
"""This is called similarly to get_persistent_data, but is called after
do_preprocessing but before any work is acutally done.
"""
d = self.get_persistent_data()
# This is basically the same as get_persistent_data() with the
# following exceptions:
# * last_rendertime is not changed
# * A key "render_in_progress" is set to True
d['last_rendertime'] = self.last_rendertime
d['render_in_progress'] = True
return d
def get_persistent_data(self):
"""Returns a dictionary representing the persistent data of this
TileSet. Typically this is called by AssetManager
@@ -400,7 +450,7 @@ class TileSet(object):
bgcolor = bgcolorformat(self.options.get('bgcolor')),
world = self.options.get('worldname_orig') +
(" - " + self.options.get('dimension') if self.options.get('dimension') != 'default' else ''),
last_rendertime = self.this_rendertime,
last_rendertime = self.max_chunk_mtime,
imgextension = self.imgextension,
)
try:
@@ -463,16 +513,17 @@ class TileSet(object):
"""
try:
curdepth = get_dirdepth(self.outputdir)
except Exception:
logging.critical("Could not determine existing tile tree depth. Does it exist?")
raise
curdepth = self.config['zoomLevels']
except KeyError:
return
if curdepth == 1:
# Skip a depth 1 tree. A depth 1 tree pretty much can't happen, so
# when we detect this it usually means the tree is actually empty
return
logging.debug("Current tree depth was detected to be %s. Target tree depth is %s", curdepth, self.treedepth)
logging.debug("Current tree depth for %s is reportedly %s. Target tree depth is %s",
self.options['name'],
curdepth, self.treedepth)
if self.treedepth != curdepth:
if self.treedepth > curdepth:
logging.warning("Your map seems to have expanded beyond its previous bounds.")
@@ -648,8 +699,8 @@ class TileSet(object):
dirty.add(tile.path)
t = int(time.time()-stime)
logging.debug("%s finished chunk scan. %s chunks scanned in %s second%s",
self, chunkcount, t,
logging.debug("Finished chunk scan for %s. %s chunks scanned in %s second%s",
self.options['name'], chunkcount, t,
"s" if t != 1 else "")
self.max_chunk_mtime = max_chunk_mtime
@@ -728,22 +779,23 @@ class TileSet(object):
img.paste(quad, path[0])
except Exception, e:
logging.warning("Couldn't open %s. It may be corrupt. Error was '%s'", path[1], e)
logging.warning("I'm going to try and delete it. You will need to run the render again")
logging.warning("I'm going to try and delete it. You will need to run the render again and with --check-tiles")
try:
os.unlink(path[1])
except Exception, e:
logging.error("While attempting to delete corrupt image %s, an error was encountered. You will need to delete it yourself. Error was '%s'", path[1], e)
# Save it
if imgformat == 'jpg':
img.save(imgpath, quality=self.options['imgquality'], subsampling=0)
else: # png
img.save(imgpath)
if self.options['optimizeimg']:
optimize_image(imgpath, imgformat, self.options['optimizeimg'])
with FileReplacer(imgpath) as tmppath:
if imgformat == 'jpg':
img.save(tmppath, "jpeg", quality=self.options['imgquality'], subsampling=0)
else: # png
img.save(tmppath, "png")
if self.options['optimizeimg']:
optimize_image(tmppath, imgformat, self.options['optimizeimg'])
os.utime(imgpath, (max_mtime, max_mtime))
os.utime(tmppath, (max_mtime, max_mtime))
def _render_rendertile(self, tile):
"""Renders the given render-tile.
@@ -829,15 +881,16 @@ class TileSet(object):
#draw.text((96,96), "c,r: %s,%s" % (col, row), fill='red')
# Save them
if self.imgextension == 'jpg':
tileimg.save(imgpath, quality=self.options['imgquality'], subsampling=0)
else: # png
tileimg.save(imgpath)
with FileReplacer(imgpath) as tmppath:
if self.imgextension == 'jpg':
tileimg.save(tmppath, "jpeg", quality=self.options['imgquality'], subsampling=0)
else: # png
tileimg.save(tmppath, "png")
if self.options['optimizeimg']:
optimize_image(imgpath, self.imgextension, self.options['optimizeimg'])
if self.options['optimizeimg']:
optimize_image(tmppath, self.imgextension, self.options['optimizeimg'])
os.utime(imgpath, (max_chunk_mtime, max_chunk_mtime))
os.utime(tmppath, (max_chunk_mtime, max_chunk_mtime))
def _iterate_and_check_tiles(self, path):
"""A generator function over all tiles that need rendering in the
@@ -946,34 +999,6 @@ class TileSet(object):
# Nope.
yield path, max_child_mtime, False
def get_dirdepth(outputdir):
"""Returns the current depth of the tree on disk
"""
# Traverses down the first directory until it reaches one with no
# subdirectories. While all paths of the tree may not exist, all paths
# of the tree should and are assumed to be the same depth
# This function returns a list of all subdirectories of the given
# directory. It's slightly more complicated than you'd think it should be
# because one must turn directory names returned by os.listdir into
# relative/absolute paths before they can be passed to os.path.isdir()
getsubdirs = lambda directory: [
abssubdir
for abssubdir in
(os.path.join(directory,subdir) for subdir in os.listdir(directory))
if os.path.isdir(abssubdir)
]
depth = 1
subdirs = getsubdirs(outputdir)
while subdirs:
subdirs = getsubdirs(subdirs[0])
depth += 1
return depth
######################
# The following two functions define the mapping from chunks to tiles and back.
# The mapping from chunks to tiles (get_tiles_by_chunk()) is used during the

View File

@@ -152,6 +152,74 @@ def unconvert_coords(col, row):
# col - row = chunkx + chunkx => (col - row)/2 = chunkx
return ((col - row) / 2, (col + row) / 2)
# Define a context manager to handle atomic renaming or "just forget it write
# straight to the file" depending on whether os.rename provides atomic
# overwrites.
# Detect whether os.rename will overwrite files
import tempfile
with tempfile.NamedTemporaryFile() as f1:
with tempfile.NamedTemporaryFile() as f2:
try:
os.rename(f1.name,f2.name)
except OSError:
renameworks = False
else:
renameworks = True
# re-make this file so it can be deleted without error
open(f1.name, 'w').close()
del tempfile,f1,f2
doc = """This class acts as a context manager for files that are to be written
out overwriting an existing file.
The parameter is the destination filename. The value returned into the context
is the filename that should be used. On systems that support an atomic
os.rename(), the filename will actually be a temporary file, and it will be
atomically replaced over the destination file on exit.
On systems that don't support an atomic rename, the filename returned is the
filename given.
If an error is encountered, the file is attempted to be removed, and the error
is propagated.
Example:
with FileReplacer("config") as configname:
with open(configout, 'w') as configout:
configout.write(newconfig)
"""
if renameworks:
class FileReplacer(object):
__doc__ = doc
def __init__(self, destname):
self.destname = destname
self.tmpname = destname + ".tmp"
def __enter__(self):
# rename works here. Return a temporary filename
return self.tmpname
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type:
# error
try:
os.remove(self.tmpname)
except Exception, e:
logging.warning("An error was raised, so I was doing "
"some cleanup first, but I couldn't remove "
"'%s'!", self.tmpname)
else:
# atomic rename into place
os.rename(self.tmpname, self.destname)
else:
class FileReplacer(object):
__doc__ = doc
def __init__(self, destname):
self.destname = destname
def __enter__(self):
return self.destname
def __exit__(self, exc_type, exc_val, exc_tb):
return
del renameworks
# Logging related classes are below
# Some cool code for colored logging:

View File

@@ -34,10 +34,10 @@ class ChunkDoesntExist(Exception):
def log_other_exceptions(func):
"""A decorator that prints out any errors that are not
ChunkDoesntExist errors. This decorates get_chunk because the C
code is likely to swallow exceptions, so this will at least make
them visible.
ChunkDoesntExist errors. This should decorate any functions or
methods called by the C code, such as get_chunk(), because the C
code is likely to swallow exceptions. This will at least make them
visible.
"""
functools.wraps(func)
def newfunc(*args):
@@ -359,9 +359,6 @@ class RegionSet(object):
return chunk_data
def rotate(self, north_direction):
return RotatedRegionSet(self.regiondir, north_direction)
def iterate_chunks(self):
"""Returns an iterator over all chunk metadata in this world. Iterates
over tuples of integers (x,z,mtime) for each chunk. Other chunk data
@@ -412,6 +409,33 @@ class RegionSet(object):
x = int(p[1])
y = int(p[2])
yield (x, y, path)
class RegionSetWrapper(object):
"""This is the base class for all "wrappers" of RegionSet objects. A
wrapper is an object that acts similarly to a subclass: some methods are
overridden and functionality is changed, others may not be. The difference
here is that these wrappers may wrap each other, forming chains.
In fact, subclasses of this object may act exactly as if they've subclassed
the original RegionSet object, except the first parameter of the
constructor is a regionset object, not a regiondir.
This class must implement the full public interface of RegionSet objects
"""
def __init__(self, rsetobj):
self._r = rsetobj
def get_type(self):
return self._r.get_type()
def get_biome_data(self, x, z):
return self._r.get_biome_data(x,z)
def get_chunk(self, x, z):
return self._r.get_chunk(x,z)
def iterate_chunks(self):
return self._r.iterate_chunks()
def get_chunk_mtime(self, x, z):
return self._r.get_chunk_mtime(x,z)
# see RegionSet.rotate. These values are chosen so that they can be
# passed directly to rot90; this means that they're the number of
@@ -421,7 +445,7 @@ UPPER_RIGHT = 1 ## - Return the world such that north is down the +X axis (rotat
LOWER_RIGHT = 2 ## - Return the world such that north is down the +Z axis (rotate 180 degrees)
LOWER_LEFT = 3 ## - Return the world such that north is down the -X axis (rotate 90 degrees clockwise)
class RotatedRegionSet(RegionSet):
class RotatedRegionSet(RegionSetWrapper):
"""A regionset, only rotated such that north points in the given direction
"""
@@ -433,39 +457,41 @@ class RotatedRegionSet(RegionSet):
_ROTATE_180 = lambda x,z: (-x,-z)
# These take rotated coords and translate into un-rotated coords
_unrotation_funcs = {
0: _NO_ROTATION,
1: _ROTATE_COUNTERCLOCKWISE,
2: _ROTATE_180,
3: _ROTATE_CLOCKWISE,
}
_unrotation_funcs = [
_NO_ROTATION,
_ROTATE_COUNTERCLOCKWISE,
_ROTATE_180,
_ROTATE_CLOCKWISE,
]
# These translate un-rotated coordinates into rotated coordinates
_rotation_funcs = {
0: _NO_ROTATION,
1: _ROTATE_CLOCKWISE,
2: _ROTATE_180,
3: _ROTATE_COUNTERCLOCKWISE,
}
_rotation_funcs = [
_NO_ROTATION,
_ROTATE_CLOCKWISE,
_ROTATE_180,
_ROTATE_COUNTERCLOCKWISE,
]
def __init__(self, regiondir, north_dir):
def __init__(self, rsetobj, north_dir):
self.north_dir = north_dir
self.unrotate = self._unrotation_funcs[north_dir]
self.rotate = self._rotation_funcs[north_dir]
super(RotatedRegionSet, self).__init__(regiondir)
super(RotatedRegionSet, self).__init__(rsetobj)
# Re-initialize upon unpickling
# Re-initialize upon unpickling. This is needed because we store a couple
# lambda functions as instance variables
def __getstate__(self):
return (self.regiondir, self.north_dir)
return (self._r, self.north_dir)
def __setstate__(self, args):
self.__init__(args[0], args[1])
def get_chunk(self, x, z):
x,z = self.unrotate(x,z)
chunk_data = super(RotatedRegionSet, self).get_chunk(x,z)
chunk_data = dict(super(RotatedRegionSet, self).get_chunk(x,z))
for section in chunk_data['Sections']:
section = dict(section)
for arrayname in ['Blocks', 'Data', 'SkyLight', 'BlockLight']:
array = section[arrayname]
# Since the anvil change, arrays are arranged with axes Y,Z,X