diff --git a/overviewer_core/rendermodes.py b/overviewer_core/rendermodes.py index 739e085..4b7fa7c 100644 --- a/overviewer_core/rendermodes.py +++ b/overviewer_core/rendermodes.py @@ -93,34 +93,45 @@ class DepthTinting(RenderPrimitive): self._depth_colors = depth_colors return depth_colors -# Render 3 blending masks for lighting -# first is top (+Z), second is left (-X), third is right (+Y) -def generate_facemasks(): - white = Image.new("L", (24,24), 255) +class Lighting(RenderPrimitive): + name = "lighting" + options = { + "strength": ("how dark to make the shadows, from 0.0 to 1.0", 1.0), + "night": ("whether to use nighttime skylight settings", False), + "color": ("whether to use colored light", False), + } + + @property + def facemasks(self): + facemasks = getattr(self, "_facemasks", None) + if facemasks: + return facemasks + + white = Image.new("L", (24,24), 255) + + top = Image.new("L", (24,24), 0) + left = Image.new("L", (24,24), 0) + whole = Image.new("L", (24,24), 0) + + toppart = textures.Textures.transform_image_top(white) + leftpart = textures.Textures.transform_image_side(white) + + # using the real PIL paste here (not alpha_over) because there is + # no alpha channel (and it's mode "L") + top.paste(toppart, (0,0)) + left.paste(leftpart, (0,6)) + right = left.transpose(Image.FLIP_LEFT_RIGHT) + + # Manually touch up 6 pixels that leave a gap, like in + # textures._build_block() + for x,y in [(13,23), (17,21), (21,19)]: + right.putpixel((x,y), 255) + for x,y in [(3,4), (7,2), (11,0)]: + top.putpixel((x,y), 255) - top = Image.new("L", (24,24), 0) - left = Image.new("L", (24,24), 0) - whole = Image.new("L", (24,24), 0) - - toppart = textures.Textures.transform_image_top(white) - leftpart = textures.Textures.transform_image_side(white) - - # using the real PIL paste here (not alpha_over) because there is - # no alpha channel (and it's mode "L") - top.paste(toppart, (0,0)) - left.paste(leftpart, (0,6)) - right = left.transpose(Image.FLIP_LEFT_RIGHT) - - # Manually touch up 6 pixels that leave a gap, like in - # textures._build_block() - for x,y in [(13,23), (17,21), (21,19)]: - right.putpixel((x,y), 255) - for x,y in [(3,4), (7,2), (11,0)]: - top.putpixel((x,y), 255) - - # special fix for chunk boundary stipple - for x,y in [(13,11), (17,9), (21,7)]: - right.putpixel((x,y), 0) - - return (top, left, right) -facemasks = generate_facemasks() + # special fix for chunk boundary stipple + for x,y in [(13,11), (17,9), (21,7)]: + right.putpixel((x,y), 0) + + self._facemasks = (top, left, right) + return self._facemasks diff --git a/overviewer_core/src/rendermode-lighting.c b/overviewer_core/src/primitives/lighting.c similarity index 83% rename from overviewer_core/src/rendermode-lighting.c rename to overviewer_core/src/primitives/lighting.c index a6eca00..a2d357f 100644 --- a/overviewer_core/src/rendermode-lighting.c +++ b/overviewer_core/src/primitives/lighting.c @@ -15,9 +15,37 @@ * with the Overviewer. If not, see . */ -#include "overviewer.h" +#include "../overviewer.h" #include +typedef struct { + PyObject *facemasks_py; + PyObject *facemasks[3]; + + /* extra data, loaded off the chunk class */ + PyObject *skylight, *blocklight; + PyObject *left_skylight, *left_blocklight; + PyObject *right_skylight, *right_blocklight; + PyObject *up_left_skylight, *up_left_blocklight; + PyObject *up_right_skylight, *up_right_blocklight; + + /* light color image, loaded if color_light is True */ + PyObject *lightcolor; + + /* can be overridden in derived rendermodes to control lighting + arguments are data, skylight, blocklight, return RGB */ + void (*calculate_light_color)(void *, unsigned char, unsigned char, unsigned char *, unsigned char *, unsigned char *); + + /* can be set to 0 in derived modes to indicate that lighting the chunk + * sides is actually important. Right now, this is used in cave mode + */ + int skip_sides; + + float strength; + int color; + int night; +} RenderPrimitiveLighting; + /* figures out the color from a given skylight and blocklight, used in lighting calculations */ static void @@ -35,7 +63,7 @@ static void calculate_light_color_fancy(void *data, unsigned char skylight, unsigned char blocklight, unsigned char *r, unsigned char *g, unsigned char *b) { - RenderModeLighting *mode = (RenderModeLighting *)(data); + RenderPrimitiveLighting *mode = (RenderPrimitiveLighting *)(data); unsigned int index; PyObject *color; @@ -70,7 +98,7 @@ static void calculate_light_color_fancy_night(void *data, unsigned char skylight, unsigned char blocklight, unsigned char *r, unsigned char *g, unsigned char *b) { - RenderModeLighting *mode = (RenderModeLighting *)(data); + RenderPrimitiveLighting *mode = (RenderPrimitiveLighting *)(data); unsigned int index; PyObject *color; @@ -95,7 +123,7 @@ calculate_light_color_fancy_night(void *data, */ inline unsigned char -estimate_blocklevel(RenderModeLighting *self, RenderState *state, +estimate_blocklevel(RenderPrimitiveLighting *self, RenderState *state, int x, int y, int z, int *authoratative) { /* placeholders for later data arrays, coordinates */ @@ -185,7 +213,7 @@ estimate_blocklevel(RenderModeLighting *self, RenderState *state, } inline void -get_lighting_color(RenderModeLighting *self, RenderState *state, +get_lighting_color(RenderPrimitiveLighting *self, RenderState *state, int x, int y, int z, unsigned char *r, unsigned char *g, unsigned char *b) { @@ -273,7 +301,10 @@ get_lighting_color(RenderModeLighting *self, RenderState *state, if (block == 10 || block == 11) { /* lava blocks should always be lit! */ - return 0.0f; + *r = 255; + *g = 255; + *b = 255; + return; } self->calculate_light_color(self, MIN(skylevel, 15), MIN(blocklevel, 15), r, g, b); @@ -281,7 +312,7 @@ get_lighting_color(RenderModeLighting *self, RenderState *state, /* does per-face occlusion checking for do_shading_with_mask */ inline int -rendermode_lighting_is_face_occluded(RenderState *state, int skip_sides, int x, int y, int z) { +lighting_is_face_occluded(RenderState *state, int skip_sides, int x, int y, int z) { /* first, check for occlusion if the block is in the local chunk */ if (x >= 0 && x < 16 && y >= 0 && y < 16 && z >= 0 && z < 128) { unsigned char block = getArrayByte3D(state->blocks, x, y, z); @@ -315,52 +346,41 @@ rendermode_lighting_is_face_occluded(RenderState *state, int skip_sides, int x, /* shades the drawn block with the given facemask, based on the lighting results from (x, y, z) */ static inline void -do_shading_with_mask(RenderModeLighting *self, RenderState *state, +do_shading_with_mask(RenderPrimitiveLighting *self, RenderState *state, int x, int y, int z, PyObject *mask) { unsigned char r, g, b; - float comp_shade_strength; + float comp_strength; /* check occlusion */ - if (rendermode_lighting_is_face_occluded(state, self->skip_sides, x, y, z)) + if (lighting_is_face_occluded(state, self->skip_sides, x, y, z)) return; get_lighting_color(self, state, x, y, z, &r, &g, &b); - comp_shade_strength = 1.0 - self->shade_strength; + comp_strength = 1.0 - self->strength; - r += (255 - r) * comp_shade_strength; - g += (255 - g) * comp_shade_strength; - b += (255 - b) * comp_shade_strength; + r += (255 - r) * comp_strength; + g += (255 - g) * comp_strength; + b += (255 - b) * comp_strength; tint_with_mask(state->img, r, g, b, 255, mask, state->imgx, state->imgy, 0, 0); } static int -rendermode_lighting_start(void *data, RenderState *state, PyObject *options) { - RenderModeLighting* self; - - /* first, chain up */ - int ret = rendermode_normal.start(data, state, options); - if (ret != 0) - return ret; +lighting_start(void *data, RenderState *state, PyObject *support) { + RenderPrimitiveLighting* self; + self = (RenderPrimitiveLighting *)data; - self = (RenderModeLighting *)data; - - /* skip sides by default */ - self->skip_sides = 1; + /* don't skip sides by default */ + self->skip_sides = 0; - self->shade_strength = 1.0; - if (!render_mode_parse_option(options, "shade_strength", "f", &(self->shade_strength))) + if (!render_mode_parse_option(support, "strength", "f", &(self->strength))) return 1; - - self->night = 0; - if (!render_mode_parse_option(options, "night", "i", &(self->night))) + if (!render_mode_parse_option(support, "night", "i", &(self->night))) return 1; - - self->color_light = 0; - if (!render_mode_parse_option(options, "color_light", "i", &(self->color_light))) + if (!render_mode_parse_option(support, "color", "i", &(self->color))) return 1; - self->facemasks_py = PyObject_GetAttrString(state->support, "facemasks"); + self->facemasks_py = PyObject_GetAttrString(support, "facemasks"); // borrowed references, don't need to be decref'd self->facemasks[0] = PyTuple_GetItem(self->facemasks_py, 0); self->facemasks[1] = PyTuple_GetItem(self->facemasks_py, 1); @@ -383,12 +403,12 @@ rendermode_lighting_start(void *data, RenderState *state, PyObject *options) { self->calculate_light_color = calculate_light_color; } - if (self->color_light) { - self->lightcolor = PyObject_CallMethod(state->textures, "loadLightColor", ""); + if (self->color) { + self->lightcolor = PyObject_CallMethod(state->textures, "load_light_color", ""); if (self->lightcolor == Py_None) { Py_DECREF(self->lightcolor); self->lightcolor = NULL; - self->color_light = 0; + self->color = 0; } else { if (self->night) { self->calculate_light_color = calculate_light_color_fancy_night; @@ -404,8 +424,8 @@ rendermode_lighting_start(void *data, RenderState *state, PyObject *options) { } static void -rendermode_lighting_finish(void *data, RenderState *state) { - RenderModeLighting *self = (RenderModeLighting *)data; +lighting_finish(void *data, RenderState *state) { + RenderPrimitiveLighting *self = (RenderPrimitiveLighting *)data; Py_DECREF(self->facemasks_py); @@ -419,32 +439,14 @@ rendermode_lighting_finish(void *data, RenderState *state) { Py_DECREF(self->up_left_blocklight); Py_DECREF(self->up_right_skylight); Py_DECREF(self->up_right_blocklight); - - /* now chain up */ - rendermode_normal.finish(data, state); -} - -static int -rendermode_lighting_occluded(void *data, RenderState *state, int x, int y, int z) { - /* no special occlusion here */ - return rendermode_normal.occluded(data, state, x, y, z); -} - -static int -rendermode_lighting_hidden(void *data, RenderState *state, int x, int y, int z) { - /* no special hiding here */ - return rendermode_normal.hidden(data, state, x, y, z); } static void -rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { - RenderModeLighting* self; +lighting_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { + RenderPrimitiveLighting* self; int x, y, z; - /* first, chain up */ - rendermode_normal.draw(data, state, src, mask, mask_light); - - self = (RenderModeLighting *)data; + self = (RenderPrimitiveLighting *)data; x = state->x, y = state->y, z = state->z; if ((state->block == 9) || (state->block == 79)) { /* special case for water and ice */ @@ -473,22 +475,11 @@ rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject } } -const RenderModeOption rendermode_lighting_options[] = { - {"shade_strength", "how dark to make the shadows, from 0.0 to 1.0 (default: 1.0)"}, - {"night", "whether to use nighttime skylight settings (default: False)"}, - {"color_light", "whether to use colored light (default: False)"}, - {NULL, NULL} -}; - -RenderModeInterface rendermode_lighting = { - "lighting", "Lighting", - "draw shadows from the lighting data", - rendermode_lighting_options, - &rendermode_normal, - sizeof(RenderModeLighting), - rendermode_lighting_start, - rendermode_lighting_finish, - rendermode_lighting_occluded, - rendermode_lighting_hidden, - rendermode_lighting_draw, +RenderPrimitiveInterface primitive_lighting = { + "lighting", sizeof(RenderPrimitiveLighting), + lighting_start, + lighting_finish, + NULL, + NULL, + lighting_draw, }; diff --git a/overviewer_core/src/rendermodes.h b/overviewer_core/src/rendermodes.h index 3d835ec..eb544ca 100644 --- a/overviewer_core/src/rendermodes.h +++ b/overviewer_core/src/rendermodes.h @@ -117,39 +117,6 @@ typedef struct { } RenderModeOverlay; extern RenderModeInterface rendermode_overlay; -/* LIGHTING */ -typedef struct { - /* inherits from normal render mode */ - RenderModeNormal parent; - - PyObject *facemasks_py; - PyObject *facemasks[3]; - - /* extra data, loaded off the chunk class */ - PyObject *skylight, *blocklight; - PyObject *left_skylight, *left_blocklight; - PyObject *right_skylight, *right_blocklight; - PyObject *up_left_skylight, *up_left_blocklight; - PyObject *up_right_skylight, *up_right_blocklight; - - /* light color image, loaded if color_light is True */ - PyObject *lightcolor; - - /* can be overridden in derived rendermodes to control lighting - arguments are data, skylight, blocklight, return RGB */ - void (*calculate_light_color)(void *, unsigned char, unsigned char, unsigned char *, unsigned char *, unsigned char *); - - /* can be set to 0 in derived modes to indicate that lighting the chunk - * sides is actually important. Right now, this is used in cave mode - */ - int skip_sides; - - float shade_strength; - int color_light; - int night; -} RenderModeLighting; -extern RenderModeInterface rendermode_lighting; - /* exposed so it can be used in other per-face occlusion checks */ int rendermode_lighting_is_face_occluded(RenderState *state, int skip_sides, int x, int y, int z); diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index d9b5739..4f628e2 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -56,8 +56,11 @@ class Textures(object): def __getstate__(self): # we must get rid of the huge image lists, and other images attributes = self.__dict__.copy() - for attr in ['terrain_images', 'blockmap', 'biome_grass_texture', 'watertexture', 'lavatexture']: - del attributes[attr] + for attr in ['terrain_images', 'blockmap', 'biome_grass_texture', 'watertexture', 'lavatexture', 'lightcolor']: + try: + del attributes[attr] + except KeyError: + pass return attributes def __setstate__(self, attrs): # regenerate textures, if needed @@ -245,6 +248,18 @@ class Textures(object): lavatexture = self.load_image("lava.png") self.lavatexture = lavatexture return lavatexture + + def load_light_color(self): + """Helper function to load the light color texture.""" + if hasattr(self, "lightcolor"): + return self.lightcolor + try: + lightcolor = list(_load_image("light_normal.png").getdata()) + except Exception: + logging.warning("Light color image could not be found.") + lightcolor = None + self.lightcolor = lightcolor + return lightcolor def _split_terrain(self, terrain): """Builds and returns a length 256 array of each 16x16 chunk @@ -650,24 +665,6 @@ def getBiomeData(worlddir, chunkX, chunkY): currentBiomeData = data return data -## -## Color Light -## - -lightcolor = None -lightcolor_checked = False -def loadLightColor(): - global lightcolor, lightcolor_checked - - if not lightcolor_checked: - lightcolor_checked = True - try: - lightcolor = list(_load_image("light_normal.png").getdata()) - except Exception: - logging.warning("Light color image could not be found.") - lightcolor = None - return lightcolor - ## ## The other big one: @material and associated framework ##