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:
@@ -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/ *
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
},
|
||||
|
||||
@@ -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)):
|
||||
|
||||
@@ -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()]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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>
|
||||
|
||||
49
overviewer_core/src/primitives/clear-base.c
Normal file
49
overviewer_core/src/primitives/clear-base.c
Normal 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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
92
overviewer_core/src/primitives/overlay-spawn.c
Normal file
92
overviewer_core/src/primitives/overlay-spawn.c
Normal 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,
|
||||
};
|
||||
99
overviewer_core/src/primitives/overlay.c
Normal file
99
overviewer_core/src/primitives/overlay.c
Normal 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,
|
||||
};
|
||||
31
overviewer_core/src/primitives/overlay.h
Normal file
31
overviewer_core/src/primitives/overlay.h
Normal 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);
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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__ */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user