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 @@
+
+
+
+
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 @@
+
+
+
+
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)