diff --git a/docs/design/designdoc.rst b/docs/design/designdoc.rst index 2d1fb04..869ff46 100644 --- a/docs/design/designdoc.rst +++ b/docs/design/designdoc.rst @@ -589,5 +589,72 @@ Caching Lighting ======== +Minecraft stores precomputed lighting information in the chunk files +themselves, so rendering shadows on the map is a simple matter of +interpreting this data, then adding a few extra steps to the render +process. These few extra steps may be found in +``rendermode-lighting.c`` or ``rendermode-smooth-lighting.c``, +depending on the exact method used. + +Each chunk contains two lighting arrays, each of which contains one +value between 0 and 15 for each block. These two arrays are the +BlockLight array, containing light received from other blocks, and the +SkyLight array, containing light received from the sky. Storing these +two seperately makes it easier to switch between daytime and +nighttime. To turn these two values into one value between 0 and 1 +representing how much light there is in a block, we use the following +equation (where l\ :sub:`b` and l\ :sub:`s` are the block light and +sky light values, respectively): + +.. image:: lighting/light-eqn.png + :alt: c = 0.8^{15 - min(l_b, l_s)} + +For night lighting, the sky light values are shifted down by 11 before +this lighting coefficient is calculated. + +Each block of light data applies to all the block faces that touch +it. So, each solid block doesn't receive lighting from the block it's +in, but from the three blocks it touches above, to the left, and to +the right. For transparent blocks with potentially strange shapes, +lighting is approximated by using the local block lighting on the +entire image. + +.. image:: lighting/lighting-process.png + :alt: The lighting process + +For some blocks, notably half-steps and stairs, Minecraft doesn't +generate valid lighting data in the local block like it does for all +other transparent blocks. In these cases, the lighting data is +estimated by averaging data from nearby blocks. This is not an ideal +solution, but it produces acceptable results in almost all cases. + +Smooth Lighting +--------------- + +In the smooth-lighting rendermode, solid blocks are lit per-vertex +instead of per-face. This is done by covering all three faces with a +quadralateral where each corner has a lighting value associated with +it. These lighting values are then smoothly interpolated across the +entire face. + +To calculate these values on each corner, we look at lighting data in +the 8 blocks surrounding the corner, and ignore the 4 blocks behind +the face the corner belongs to. We then calculate the lighting +coefficient for all 4 remaining blocks as normal, and average them to +obtain the coefficient for the corner. This is repeated for all 4 +corners on a given face, and for all visible faces. + +.. image:: lighting/smooth-average.png + :alt: An example face and vertex, with the 4 light sources. + +The `ambient occlusion`_ effect so strongly associated with smooth +lighting in-game is a side effect of this method. Since solid blocks +have both light values set to 0, the lighting coefficient is very +close to 0. For verticies in corners, at least 1 (or more) of the 4 +averaged lighting values is therefore 0, dragging the average down, +and creating the "dark corners" effect. + +.. _ambient occlusion: http://en.wikipedia.org/wiki/Ambient_occlusion + Cave Mode ========= diff --git a/docs/design/lighting/light-eqn.png b/docs/design/lighting/light-eqn.png new file mode 100644 index 0000000..8b8067f Binary files /dev/null and b/docs/design/lighting/light-eqn.png differ diff --git a/docs/design/lighting/lighting-process.png b/docs/design/lighting/lighting-process.png new file mode 100644 index 0000000..0c9c299 Binary files /dev/null and b/docs/design/lighting/lighting-process.png differ diff --git a/docs/design/lighting/lighting-process.svg b/docs/design/lighting/lighting-process.svg new file mode 100644 index 0000000..39ea908 --- /dev/null +++ b/docs/design/lighting/lighting-process.svg @@ -0,0 +1,597 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Solid Block + Transparent Block + Unlit + Light Data Used + Lit + + diff --git a/docs/design/lighting/smooth-average.png b/docs/design/lighting/smooth-average.png new file mode 100644 index 0000000..03dcd47 Binary files /dev/null and b/docs/design/lighting/smooth-average.png differ diff --git a/docs/design/lighting/smooth-average.svg b/docs/design/lighting/smooth-average.svg new file mode 100644 index 0000000..020e0a0 --- /dev/null +++ b/docs/design/lighting/smooth-average.svg @@ -0,0 +1,297 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/overviewer.py b/overviewer.py index 6f41d7e..ea95951 100755 --- a/overviewer.py +++ b/overviewer.py @@ -102,6 +102,24 @@ from overviewer_core.configParser import ConfigOptionParser from overviewer_core import optimizeimages, world, quadtree from overviewer_core import googlemap, rendernode +# definitions of built-in custom modes +# usually because what used to be a mode became an option +# for example, night mode +builtin_custom_rendermodes = { + 'night' : { + 'parent' : 'lighting', + 'label' : 'Night', + 'description' : 'like "lighting", except at night', + 'options' : {'night' : True} + }, + + 'smooth-night' : { + 'parent' : 'smooth-lighting', + 'label' : 'Smooth Night', + 'description' : 'like "lighting", except smooth and at night', + 'options' : {'night' : True} + }, +} helptext = """ %prog [OPTIONS] """ @@ -204,6 +222,8 @@ def main(): sys.exit(0) # setup c_overviewer rendermode customs / options + for mode in builtin_custom_rendermodes: + c_overviewer.add_custom_render_mode(mode, builtin_custom_rendermodes[mode]) for mode in options.custom_rendermodes: c_overviewer.add_custom_render_mode(mode, options.custom_rendermodes[mode]) for mode in options.rendermode_options: diff --git a/overviewer_core/data/web_assets/overviewer.js b/overviewer_core/data/web_assets/overviewer.js index 02fa8d9..2ab35d0 100644 --- a/overviewer_core/data/web_assets/overviewer.js +++ b/overviewer_core/data/web_assets/overviewer.js @@ -124,7 +124,11 @@ var overviewer = { for (i in overviewerConfig.mapTypes) { var view = overviewerConfig.mapTypes[i]; var imageFormat = view.imgformat ? view.imgformat : 'png'; - mapOptions[view.label] = { + + if (view.shortname == null) + view.shortname = view.label.replace(/\s+/g, ""); + + mapOptions[view.shortname] = { 'getTileUrl': overviewer.gmap.getTileUrlGenerator(view.path, view.base, imageFormat), 'tileSize': new google.maps.Size( @@ -134,19 +138,20 @@ var overviewer = { 'minZoom': overviewerConfig.map.minZoom, 'isPng': imageFormat.toLowerCase() == 'png' } - overviewer.collections.mapTypes[view.label] = new google.maps.ImageMapType( - mapOptions[view.label]); - overviewer.collections.mapTypes[view.label].name = view.label; - overviewer.collections.mapTypes[view.label].alt = 'Minecraft ' + + overviewer.collections.mapTypes[view.shortname] = new google.maps.ImageMapType( + mapOptions[view.shortname]); + overviewer.collections.mapTypes[view.shortname].name = view.label; + overviewer.collections.mapTypes[view.shortname].shortname = view.shortname; + overviewer.collections.mapTypes[view.shortname].alt = 'Minecraft ' + view.label + ' Map'; - overviewer.collections.mapTypes[view.label].projection = + overviewer.collections.mapTypes[view.shortname].projection = new overviewer.classes.MapProjection(); if (view.overlay) { overviewer.collections.overlays.push( - overviewer.collections.mapTypes[view.label]); + overviewer.collections.mapTypes[view.shortname]); } else { overviewer.collections.mapTypeIds.push( - overviewerConfig.CONST.mapDivId + view.label); + overviewerConfig.CONST.mapDivId + view.shortname); } } }, @@ -245,7 +250,7 @@ var overviewer = { // Now attach the coordinate map type to the map's registry for (i in overviewer.collections.mapTypes) { overviewer.map.mapTypes.set(overviewerConfig.CONST.mapDivId + - overviewer.collections.mapTypes[i].name, + overviewer.collections.mapTypes[i].shortname, overviewer.collections.mapTypes[i]); } @@ -482,7 +487,7 @@ var overviewer = { 'getMapTypeBackgroundColor': function(mapTypeId) { for(i in overviewerConfig.mapTypes) { if( overviewerConfig.CONST.mapDivId + - overviewerConfig.mapTypes[i].label == mapTypeId ) { + overviewerConfig.mapTypes[i].shortname == mapTypeId ) { overviewer.util.debug('Found background color for: ' + overviewerConfig.mapTypes[i].bg_color); return overviewerConfig.mapTypes[i].bg_color; @@ -974,6 +979,11 @@ var overviewer = { } }, 'setHash': function(x, y, z, zoom, maptype) { + // remove the div prefix from the maptype (looks better) + if (maptype) + { + maptype = maptype.replace(overviewerConfig.CONST.mapDivId, ""); + } window.location.replace("#/" + Math.floor(x) + "/" + Math.floor(y) + "/" + Math.floor(z) + "/" + zoom + "/" + maptype); }, 'updateHash': function() { @@ -1023,7 +1033,20 @@ var overviewer = { // We can now set the map to use the 'coordinate' map type overviewer.map.setMapTypeId(overviewer.util.getDefaultMapTypeId()); } else { - overviewer.map.setMapTypeId(maptype); + // normalize the map type (this supports old-style, + // 'mcmapLabel' style map types, converts them to 'shortname' + if (maptype.lastIndexOf(overviewerConfig.CONST.mapDivId, 0) === 0) { + maptype = maptype.replace(overviewerConfig.CONST.mapDivId, ""); + for (i in overviewer.collections.mapTypes) { + var type = overviewer.collections.mapTypes[i]; + if (type.name == maptype) { + maptype = type.shortname; + break; + } + } + } + + overviewer.map.setMapTypeId(overviewerConfig.CONST.mapDivId + maptype); } overviewer.map.setCenter(latlngcoords); diff --git a/overviewer_core/data/web_assets/overviewerConfig.js b/overviewer_core/data/web_assets/overviewerConfig.js index e6889b4..5f3122f 100644 --- a/overviewer_core/data/web_assets/overviewerConfig.js +++ b/overviewer_core/data/web_assets/overviewerConfig.js @@ -172,6 +172,10 @@ var overviewerConfig = { * the js/html server. * imgformat : string. File extension used for these tiles. Defaults to png. * overlay : bool. If true, this tile set will be treated like an overlay + * bg_color : string. A #RRGGBB format background color. + * shortname : string. Used in the dynamic anchor link mechanism. If not + * present, label is used instead. + * * Example: * 'mapTypes': [ * {'label': 'Day', 'path': 'lighting/tiles'}, diff --git a/overviewer_core/googlemap.py b/overviewer_core/googlemap.py index b30fa05..abe3e18 100644 --- a/overviewer_core/googlemap.py +++ b/overviewer_core/googlemap.py @@ -139,6 +139,7 @@ class MapGen(object): # create generated map type data, from given quadtrees maptypedata = map(lambda q: {'label' : get_render_mode_label(q.rendermode), + 'shortname' : q.rendermode, 'path' : q.tiledir, 'bg_color': self.bg_color, 'overlay' : 'overlay' in get_render_mode_inheritance(q.rendermode), diff --git a/overviewer_core/src/composite.c b/overviewer_core/src/composite.c index d83701c..2a25706 100644 --- a/overviewer_core/src/composite.c +++ b/overviewer_core/src/composite.c @@ -358,3 +358,129 @@ tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, return dest; } + +/* draws a triangle on the destination image, multiplicatively! + * used for smooth lighting + * (excuse the ridiculous number of parameters!) + * + * Algorithm adapted from _Fundamentals_of_Computer_Graphics_ + * by Peter Shirley, Michael Ashikhmin + * (or at least, the version poorly reproduced here: + * http://www.gidforums.com/t-20838.html ) + */ +PyObject * +draw_triangle(PyObject *dest, int inclusive, + int x0, int y0, + unsigned char r0, unsigned char g0, unsigned char b0, + int x1, int y1, + unsigned char r1, unsigned char g1, unsigned char b1, + int x2, int y2, + unsigned char r2, unsigned char g2, unsigned char b2, + int tux, int tuy, int *touchups, unsigned int num_touchups) { + + /* destination image */ + Imaging imDest; + /* ranges of pixels that are affected */ + int xmin, xmax, ymin, ymax; + /* constant coefficients for alpha, beta, gamma */ + int a12, a20, a01; + int b12, b20, b01; + int c12, c20, c01; + /* constant normalizers for alpha, beta, gamma */ + float alpha_norm, beta_norm, gamma_norm; + /* temporary variables */ + int tmp; + /* iteration variables */ + int x, y; + + imDest = imaging_python_to_c(dest); + if (!imDest) + return NULL; + + /* check the various image modes, make sure they make sense */ + if (strcmp(imDest->mode, "RGBA") != 0) { + PyErr_SetString(PyExc_ValueError, + "given destination image does not have mode \"RGBA\""); + return NULL; + } + + /* set up draw ranges */ + xmin = MIN(x0, MIN(x1, x2)); + ymin = MIN(y0, MIN(y1, y2)); + xmax = MAX(x0, MAX(x1, x2)) + 1; + ymax = MAX(y0, MAX(y1, y2)) + 1; + + xmin = MAX(xmin, 0); + ymin = MAX(ymin, 0); + xmax = MIN(xmax, imDest->xsize); + ymax = MIN(ymax, imDest->ysize); + + /* setup coefficients */ + a12 = y1 - y2; b12 = x2 - x1; c12 = (x1 * y2) - (x2 * y1); + a20 = y2 - y0; b20 = x0 - x2; c20 = (x2 * y0) - (x0 * y2); + a01 = y0 - y1; b01 = x1 - x0; c01 = (x0 * y1) - (x1 * y0); + + /* setup normalizers */ + alpha_norm = 1.0f / ((a12 * x0) + (b12 * y0) + c12); + beta_norm = 1.0f / ((a20 * x1) + (b20 * y1) + c20); + gamma_norm = 1.0f / ((a01 * x2) + (b01 * y2) + c01); + + /* iterate over the destination rect */ + for (y = ymin; y < ymax; y++) { + UINT8 *out = (UINT8 *)imDest->image[y] + xmin * 4; + + for (x = xmin; x < xmax; x++) { + float alpha, beta, gamma; + alpha = alpha_norm * ((a12 * x) + (b12 * y) + c12); + beta = beta_norm * ((a20 * x) + (b20 * y) + c20); + gamma = gamma_norm * ((a01 * x) + (b01 * y) + c01); + + if (alpha >= 0 && beta >= 0 && gamma >= 0 && + (inclusive || (alpha * beta * gamma > 0))) { + unsigned int r = alpha * r0 + beta * r1 + gamma * r2; + unsigned int g = alpha * g0 + beta * g1 + gamma * g2; + unsigned int b = alpha * b0 + beta * b1 + gamma * b2; + + *out = MULDIV255(*out, r, tmp); out++; + *out = MULDIV255(*out, g, tmp); out++; + *out = MULDIV255(*out, b, tmp); out++; + + /* keep alpha the same */ + out++; + } else { + /* skip */ + out += 4; + } + } + } + + while (num_touchups > 0) { + float alpha, beta, gamma; + unsigned int r, g, b; + UINT8 *out; + + x = touchups[0] + tux; + y = touchups[1] + tuy; + touchups += 2; + num_touchups--; + + if (x < 0 || x >= imDest->xsize || y < 0 || y >= imDest->ysize) + continue; + + out = (UINT8 *)imDest->image[y] + x * 4; + + alpha = alpha_norm * ((a12 * x) + (b12 * y) + c12); + beta = beta_norm * ((a20 * x) + (b20 * y) + c20); + gamma = gamma_norm * ((a01 * x) + (b01 * y) + c01); + + r = alpha * r0 + beta * r1 + gamma * r2; + g = alpha * g0 + beta * g1 + gamma * g2; + b = alpha * b0 + beta * b1 + gamma * b2; + + *out = MULDIV255(*out, r, tmp); out++; + *out = MULDIV255(*out, g, tmp); out++; + *out = MULDIV255(*out, b, tmp); out++; + } + + return dest; +} diff --git a/overviewer_core/src/overviewer.h b/overviewer_core/src/overviewer.h index 934dae8..8d10d81 100644 --- a/overviewer_core/src/overviewer.h +++ b/overviewer_core/src/overviewer.h @@ -51,6 +51,14 @@ PyObject *alpha_over_wrap(PyObject *self, PyObject *args); PyObject *tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb, unsigned char sa, PyObject *mask, int dx, int dy, int xsize, int ysize); +PyObject *draw_triangle(PyObject *dest, int inclusive, + int x0, int y0, + unsigned char r0, unsigned char g0, unsigned char b0, + int x1, int y1, + unsigned char r1, unsigned char g1, unsigned char b1, + int x2, int y2, + unsigned char r2, unsigned char g2, unsigned char b2, + int tux, int tuy, int *touchups, unsigned int num_touchups); /* forward declaration of RenderMode object */ typedef struct _RenderMode RenderMode; diff --git a/overviewer_core/src/rendermode-lighting.c b/overviewer_core/src/rendermode-lighting.c index 395061a..6a984da 100644 --- a/overviewer_core/src/rendermode-lighting.c +++ b/overviewer_core/src/rendermode-lighting.c @@ -51,6 +51,39 @@ calculate_light_color_fancy(void *data, Py_DECREF(color); } +/* figures out the color from a given skylight and blocklight, used in + lighting calculations -- note this is *different* from the one above + (the "skylight - 11" part) +*/ +static void +calculate_light_color_night(void *data, + unsigned char skylight, unsigned char blocklight, + unsigned char *r, unsigned char *g, unsigned char *b) { + unsigned char v = 255 * powf(0.8f, 15.0 - MAX(blocklight, skylight - 11)); + *r = v; + *g = v; + *b = v; +} + +/* fancy night version that uses the colored light texture */ +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); + unsigned int index; + PyObject *color; + + index = skylight + blocklight * 16; + color = PySequence_GetItem(mode->lightcolor, index); + + *r = PyInt_AsLong(PyTuple_GET_ITEM(color, 0)); + *g = PyInt_AsLong(PyTuple_GET_ITEM(color, 1)); + *b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2)); + + Py_DECREF(color); +} + /* loads the appropriate light data for the given (possibly non-local) * coordinates, and returns a black_coeff this is exposed, so other (derived) * rendermodes can use it @@ -162,9 +195,9 @@ get_lighting_color(RenderModeLighting *self, RenderState *state, PyObject *blocklight = NULL; int local_x = x, local_y = y, local_z = z; unsigned char block, skylevel, blocklevel; - + /* find out what chunk we're in, and translate accordingly */ - if (x >= 0 && y < 16) { + if (x >= 0 && x < 16 && y >= 0 && y < 16) { blocks = state->blocks; skylight = self->skylight; blocklight = self->blocklight; @@ -178,13 +211,23 @@ get_lighting_color(RenderModeLighting *self, RenderState *state, blocks = state->right_blocks; skylight = self->right_skylight; blocklight = self->right_blocklight; + } else if (y < 0) { + local_y += 16; + blocks = state->up_left_blocks; + skylight = self->up_left_skylight; + blocklight = self->up_left_blocklight; + } else if (x >= 16) { + local_x -= 16; + blocks = state->up_right_blocks; + skylight = self->up_right_skylight; + blocklight = self->up_right_blocklight; } /* make sure we have correctly-ranged coordinates */ if (!(local_x >= 0 && local_x < 16 && local_y >= 0 && local_y < 16 && local_z >= 0 && local_z < 128)) { - + self->calculate_light_color(self, 15, 0, r, g, b); return; } @@ -193,7 +236,7 @@ get_lighting_color(RenderModeLighting *self, RenderState *state, if (blocks == Py_None || blocks == NULL || skylight == Py_None || skylight == NULL || blocklight == Py_None || blocklight == NULL) { - + self->calculate_light_color(self, 15, 0, r, g, b); return; } @@ -236,6 +279,39 @@ get_lighting_color(RenderModeLighting *self, RenderState *state, self->calculate_light_color(self, MIN(skylevel, 15), MIN(blocklevel, 15), r, g, b); } +/* 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) { + /* 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); + + if (!is_transparent(block) && !render_mode_hidden(state->rendermode, x, y, z)) { + /* this face isn't visible, so don't draw anything */ + return 1; + } + } else if (skip_sides && (x == -1) && (state->left_blocks != Py_None)) { + unsigned char block = getArrayByte3D(state->left_blocks, 15, state->y, state->z); + if (!is_transparent(block)) { + /* the same thing but for adjacent chunks, this solves an + ugly black doted line between chunks in night rendermode. + This wouldn't be necessary if the textures were truly + tessellate-able */ + return 1; + } + } else if (skip_sides && (y == 16) && (state->right_blocks != Py_None)) { + unsigned char block = getArrayByte3D(state->right_blocks, state->x, 0, state->z); + if (!is_transparent(block)) { + /* the same thing but for adjacent chunks, this solves an + ugly black doted line between chunks in night rendermode. + This wouldn't be necessary if the textures were truly + tessellate-able */ + return 1; + } + } + return 0; +} + /* shades the drawn block with the given facemask, based on the lighting results from (x, y, z) */ static inline void @@ -244,33 +320,9 @@ do_shading_with_mask(RenderModeLighting *self, RenderState *state, unsigned char r, g, b; float comp_shade_strength; - /* 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); - - if (!is_transparent(block) && !render_mode_hidden(state->rendermode, x, y, z)) { - /* this face isn't visible, so don't draw anything */ - return; - } - } else if (self->skip_sides && (x == -1) && (state->left_blocks != Py_None)) { - unsigned char block = getArrayByte3D(state->left_blocks, 15, state->y, state->z); - if (!is_transparent(block)) { - /* the same thing but for adjacent chunks, this solves an - ugly black doted line between chunks in night rendermode. - This wouldn't be necessary if the textures were truly - tessellate-able */ - return; - } - } else if (self->skip_sides && (y == 16) && (state->right_blocks != Py_None)) { - unsigned char block = getArrayByte3D(state->right_blocks, state->x, 0, state->z); - if (!is_transparent(block)) { - /* the same thing but for adjacent chunks, this solves an - ugly black doted line between chunks in night rendermode. - This wouldn't be necessary if the textures were truly - tessellate-able */ - return; - } - } + /* check occlusion */ + if (rendermode_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; @@ -300,6 +352,10 @@ rendermode_lighting_start(void *data, RenderState *state, PyObject *options) { if (!render_mode_parse_option(options, "shade_strength", "f", &(self->shade_strength))) return 1; + self->night = 0; + if (!render_mode_parse_option(options, "night", "i", &(self->night))) + return 1; + self->color_light = 0; if (!render_mode_parse_option(options, "color_light", "i", &(self->color_light))) return 1; @@ -316,8 +372,16 @@ rendermode_lighting_start(void *data, RenderState *state, PyObject *options) { self->left_blocklight = PyObject_GetAttrString(state->self, "left_blocklight"); self->right_skylight = PyObject_GetAttrString(state->self, "right_skylight"); self->right_blocklight = PyObject_GetAttrString(state->self, "right_blocklight"); + self->up_left_skylight = PyObject_GetAttrString(state->self, "up_left_skylight"); + self->up_left_blocklight = PyObject_GetAttrString(state->self, "up_left_blocklight"); + self->up_right_skylight = PyObject_GetAttrString(state->self, "up_right_skylight"); + self->up_right_blocklight = PyObject_GetAttrString(state->self, "up_right_blocklight"); - self->calculate_light_color = calculate_light_color; + if (self->night) { + self->calculate_light_color = calculate_light_color_night; + } else { + self->calculate_light_color = calculate_light_color; + } if (self->color_light) { self->lightcolor = PyObject_CallMethod(state->textures, "loadLightColor", ""); @@ -326,7 +390,11 @@ rendermode_lighting_start(void *data, RenderState *state, PyObject *options) { self->lightcolor = NULL; self->color_light = 0; } else { - self->calculate_light_color = calculate_light_color_fancy; + if (self->night) { + self->calculate_light_color = calculate_light_color_fancy_night; + } else { + self->calculate_light_color = calculate_light_color_fancy; + } } } else { self->lightcolor = NULL; @@ -347,6 +415,10 @@ rendermode_lighting_finish(void *data, RenderState *state) { Py_DECREF(self->left_blocklight); Py_DECREF(self->right_skylight); Py_DECREF(self->right_blocklight); + Py_DECREF(self->up_left_skylight); + 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); @@ -403,6 +475,7 @@ 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} }; diff --git a/overviewer_core/src/rendermode-night.c b/overviewer_core/src/rendermode-night.c deleted file mode 100644 index d4c1e6c..0000000 --- a/overviewer_core/src/rendermode-night.c +++ /dev/null @@ -1,106 +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 . - */ - -#include "overviewer.h" -#include - -/* figures out the color from a given skylight and blocklight, used in - lighting calculations -- note this is *different* from the one in - rendermode-lighting.c (the "skylight - 11" part) */ -static void -calculate_light_color(void *data, - unsigned char skylight, unsigned char blocklight, - unsigned char *r, unsigned char *g, unsigned char *b) { - unsigned char v = 255 * powf(0.8f, 15.0 - MAX(blocklight, skylight - 11)); - *r = v; - *g = v; - *b = v; -} - -/* fancy version that uses the colored light texture */ -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); - unsigned int index; - PyObject *color; - - index = skylight + blocklight * 16; - color = PySequence_GetItem(mode->lightcolor, index); - - *r = PyInt_AsLong(PyTuple_GET_ITEM(color, 0)); - *g = PyInt_AsLong(PyTuple_GET_ITEM(color, 1)); - *b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2)); - - Py_DECREF(color); -} - -static int -rendermode_night_start(void *data, RenderState *state, PyObject *options) { - RenderModeNight* self; - - /* first, chain up */ - int ret = rendermode_lighting.start(data, state, options); - if (ret != 0) - return ret; - - /* override the darkness function with our night version! */ - self = (RenderModeNight *)data; - self->parent.calculate_light_color = calculate_light_color; - if (self->parent.color_light) - self->parent.calculate_light_color = calculate_light_color_fancy; - - return 0; -} - -static void -rendermode_night_finish(void *data, RenderState *state) { - /* nothing special to do */ - rendermode_lighting.finish(data, state); -} - -static int -rendermode_night_occluded(void *data, RenderState *state, int x, int y, int z) { - /* no special occlusion here */ - return rendermode_lighting.occluded(data, state, x, y, z); -} - -static int -rendermode_night_hidden(void *data, RenderState *state, int x, int y, int z) { - /* no special hiding here */ - return rendermode_lighting.hidden(data, state, x, y, z); -} - -static void -rendermode_night_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { - /* nothing special to do */ - rendermode_lighting.draw(data, state, src, mask, mask_light); -} - -RenderModeInterface rendermode_night = { - "night", "Night", - "like \"lighting\", except at night", - NULL, - &rendermode_lighting, - sizeof(RenderModeNight), - rendermode_night_start, - rendermode_night_finish, - rendermode_night_occluded, - rendermode_night_hidden, - rendermode_night_draw, -}; diff --git a/overviewer_core/src/rendermode-smooth-lighting.c b/overviewer_core/src/rendermode-smooth-lighting.c new file mode 100644 index 0000000..28767ea --- /dev/null +++ b/overviewer_core/src/rendermode-smooth-lighting.c @@ -0,0 +1,271 @@ +/* + * 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 . + */ + +#include "overviewer.h" +#include + +/* structure representing one corner of a face (see below) */ +struct SmoothLightingCorner { + /* where this corner shows up on each block texture */ + int imgx, imgy; + + /* the two block offsets that (together) determine the 4 blocks to use */ + int dx1, dy1, dz1; + int dx2, dy2, dz2; +}; + +/* structure for rule table handling lighting */ +struct SmoothLightingFace { + /* offset from current coordinate to the block this face points towards + used for occlusion calculations, and as a base for later */ + int dx, dy, dz; + + /* the points that form the corners of this face */ + struct SmoothLightingCorner corners[4]; + + /* pairs of (x,y) in order, as touch-up points, or NULL for none */ + int *touch_up_points; + unsigned int num_touch_up_points; +}; + +/* top face touchups, pulled from textures.py (_build_block) */ +static int top_touchups[] = {3, 4, 7, 2, 11, 0}; + +/* the lighting face rule list! */ +static struct SmoothLightingFace lighting_rules[] = { + /* since this is getting a little insane, here's the general layout: + + {dx, dy, dz, { // direction this face is towards + // now, a list of 4 corners... + {imgx, imgy, // where the corner is on the block image + x1, y1, z1, // two vectors, describing the 4 (!!!) + x2, y2, z2}, // blocks neighboring this corner + // ... + }, + {x, y, x, y}, 2}, // touch-up points, and how many there are (may be NULL) + + // ... + + */ + + /* top */ + {0, 0, 1, { + {0, 6, + -1, 0, 0, + 0, -1, 0}, + {12, 0, + 1, 0, 0, + 0, -1, 0}, + {24, 6, + 1, 0, 0, + 0, 1, 0}, + {12, 12, + -1, 0, 0, + 0, 1, 0}, + }, + top_touchups, 3}, + + /* left */ + {-1, 0, 0, { + {0, 18, + 0, -1, 0, + 0, 0, -1}, + {0, 6, + 0, -1, 0, + 0, 0, 1}, + {12, 12, + 0, 1, 0, + 0, 0, 1}, + {12, 24, + 0, 1, 0, + 0, 0, -1}, + }, + NULL, 0}, + + /* right */ + {0, 1, 0, { + {24, 6, + 1, 0, 0, + 0, 0, 1}, + {12, 12, + -1, 0, 0, + 0, 0, 1}, + {12, 24, + -1, 0, 0, + 0, 0, -1}, + {24, 18, + 1, 0, 0, + 0, 0, -1}, + }, + NULL, 0}, +}; + +/* helpers for indexing the rule list */ +enum +{ + FACE_TOP = 0, + FACE_LEFT = 1, + FACE_RIGHT = 2, +}; + +static void +do_shading_with_rule(RenderModeSmoothLighting *self, RenderState *state, struct SmoothLightingFace face) { + int i; + RenderModeLighting *lighting = (RenderModeLighting *)self; + int x = state->imgx, y = state->imgy; + struct SmoothLightingCorner *pts = face.corners; + float comp_shade_strength = 1.0 - lighting->shade_strength; + unsigned char pts_r[4] = {0, 0, 0, 0}; + unsigned char pts_g[4] = {0, 0, 0, 0}; + unsigned char pts_b[4] = {0, 0, 0, 0}; + int cx = state->x + face.dx; + int cy = state->y + face.dy; + int cz = state->z + face.dz; + + /* first, check for occlusion if the block is in the local chunk */ + if (rendermode_lighting_is_face_occluded(state, 0, cx, cy, cz)) + return; + + /* calculate the lighting colors for each point */ + for (i = 0; i < 4; i++) + { + unsigned char r, g, b; + unsigned int rgather = 0, ggather = 0, bgather = 0; + + get_lighting_color(lighting, state, cx, cy, cz, + &r, &g, &b); + rgather += r; ggather += g; bgather += b; + + get_lighting_color(lighting, state, + cx+pts[i].dx1, cy+pts[i].dy1, cz+pts[i].dz1, + &r, &g, &b); + rgather += r; ggather += g; bgather += b; + + get_lighting_color(lighting, state, + cx+pts[i].dx2, cy+pts[i].dy2, cz+pts[i].dz2, + &r, &g, &b); + rgather += r; ggather += g; bgather += b; + + /* FIXME special far corner handling */ + get_lighting_color(lighting, state, + cx+pts[i].dx1+pts[i].dx2, cy+pts[i].dy1+pts[i].dy2, cz+pts[i].dz1+pts[i].dz2, + &r, &g, &b); + rgather += r; ggather += g; bgather += b; + + rgather += (255*4 - rgather) * comp_shade_strength; + ggather += (255*4 - ggather) * comp_shade_strength; + bgather += (255*4 - bgather) * comp_shade_strength; + + pts_r[i] = rgather / 4; + pts_g[i] = ggather / 4; + pts_b[i] = bgather / 4; + } + + /* draw the face */ + draw_triangle(state->img, 1, + x+pts[0].imgx, y+pts[0].imgy, pts_r[0], pts_g[0], pts_b[0], + x+pts[1].imgx, y+pts[1].imgy, pts_r[1], pts_g[1], pts_b[1], + x+pts[2].imgx, y+pts[2].imgy, pts_r[2], pts_g[2], pts_b[2], + x, y, face.touch_up_points, face.num_touch_up_points); + draw_triangle(state->img, 0, + x+pts[0].imgx, y+pts[0].imgy, pts_r[0], pts_g[0], pts_b[0], + x+pts[2].imgx, y+pts[2].imgy, pts_r[2], pts_g[2], pts_b[2], + x+pts[3].imgx, y+pts[3].imgy, pts_r[3], pts_g[3], pts_b[3], + x, y, NULL, 0); +} + +static int +rendermode_smooth_lighting_start(void *data, RenderState *state, PyObject *options) { + /* first, chain up */ + int ret = rendermode_lighting.start(data, state, options); + if (ret != 0) + return ret; + + return 0; +} + +static void +rendermode_smooth_lighting_finish(void *data, RenderState *state) { + /* nothing special to do */ + rendermode_lighting.finish(data, state); +} + +static int +rendermode_smooth_lighting_occluded(void *data, RenderState *state, int x, int y, int z) { + /* no special occlusion here */ + return rendermode_lighting.occluded(data, state, x, y, z); +} + +static int +rendermode_smooth_lighting_hidden(void *data, RenderState *state, int x, int y, int z) { + /* no special hiding here */ + return rendermode_lighting.hidden(data, state, x, y, z); +} + +static void +rendermode_smooth_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { + int light_top = 1; + int light_left = 1; + int light_right = 1; + RenderModeSmoothLighting *self = (RenderModeSmoothLighting *)data; + + /* special case for leaves, water 8, water 9 + -- these are also smooth-lit! */ + if (state->block != 18 && state->block != 8 && state->block != 9 && is_transparent(state->block)) + { + /* transparent blocks are rendered as usual, with flat lighting */ + rendermode_lighting.draw(data, state, src, mask, mask_light); + return; + } + + /* non-transparent blocks get the special smooth treatment */ + + /* nothing special to do, but we do want to avoid vanilla + * lighting mode draws */ + rendermode_normal.draw(data, state, src, mask, mask_light); + + /* special code for water */ + if (state->block == 9) + { + if (!(state->block_pdata & (1 << 4))) + light_top = 0; + if (!(state->block_pdata & (1 << 1))) + light_left = 0; + if (!(state->block_pdata & (1 << 2))) + light_right = 0; + } + + if (light_top) + do_shading_with_rule(self, state, lighting_rules[FACE_TOP]); + if (light_left) + do_shading_with_rule(self, state, lighting_rules[FACE_LEFT]); + if (light_right) + do_shading_with_rule(self, state, lighting_rules[FACE_RIGHT]); +} + +RenderModeInterface rendermode_smooth_lighting = { + "smooth-lighting", "Smooth Lighting", + "like \"lighting\", except smooth", + NULL, + &rendermode_lighting, + sizeof(RenderModeSmoothLighting), + rendermode_smooth_lighting_start, + rendermode_smooth_lighting_finish, + rendermode_smooth_lighting_occluded, + rendermode_smooth_lighting_hidden, + rendermode_smooth_lighting_draw, +}; diff --git a/overviewer_core/src/rendermodes.c b/overviewer_core/src/rendermodes.c index ff7858d..dd0848f 100644 --- a/overviewer_core/src/rendermodes.c +++ b/overviewer_core/src/rendermodes.c @@ -25,7 +25,7 @@ static RenderModeInterface *render_modes[] = { &rendermode_normal, &rendermode_lighting, - &rendermode_night, + &rendermode_smooth_lighting, &rendermode_spawn, &rendermode_cave, &rendermode_mineral, diff --git a/overviewer_core/src/rendermodes.h b/overviewer_core/src/rendermodes.h index 1a145cf..5084cd8 100644 --- a/overviewer_core/src/rendermodes.h +++ b/overviewer_core/src/rendermodes.h @@ -165,6 +165,8 @@ typedef struct { 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; @@ -180,15 +182,24 @@ typedef struct { float shade_strength; int color_light; + int night; } RenderModeLighting; extern RenderModeInterface rendermode_lighting; -/* NIGHT */ +/* 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); + +/* exposed so sub-modes can look at colors directly */ +void get_lighting_color(RenderModeLighting *self, RenderState *state, + int x, int y, int z, + unsigned char *r, unsigned char *g, unsigned char *b); + +/* SMOOTH LIGHTING */ typedef struct { /* inherits from lighting */ RenderModeLighting parent; -} RenderModeNight; -extern RenderModeInterface rendermode_night; +} RenderModeSmoothLighting; +extern RenderModeInterface rendermode_smooth_lighting; /* SPAWN */ typedef struct { diff --git a/setup.py b/setup.py index 2cfe65a..9516554 100755 --- a/setup.py +++ b/setup.py @@ -149,7 +149,7 @@ except Exception: # used to figure out what files to compile -render_modes = ['normal', 'overlay', 'lighting', 'night', 'spawn', 'cave', 'mineral'] +render_modes = ['normal', 'overlay', 'lighting', 'smooth-lighting', 'spawn', 'cave', 'mineral'] c_overviewer_files = ['main.c', 'composite.c', 'iterate.c', 'endian.c', 'rendermodes.c'] c_overviewer_files += map(lambda mode: 'rendermode-%s.c' % (mode,), render_modes)