From 3a090f77f5653635455891dd6ab02a9b9571056e Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 11 Oct 2011 19:59:33 -0400 Subject: [PATCH 01/12] stub smooth-lighting mode (does nothing special, for now) --- .../src/rendermode-smooth-lighting.c | 68 +++++++++++++++++++ overviewer_core/src/rendermodes.c | 1 + overviewer_core/src/rendermodes.h | 7 ++ setup.py | 2 +- 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 overviewer_core/src/rendermode-smooth-lighting.c diff --git a/overviewer_core/src/rendermode-smooth-lighting.c b/overviewer_core/src/rendermode-smooth-lighting.c new file mode 100644 index 0000000..80570b0 --- /dev/null +++ b/overviewer_core/src/rendermode-smooth-lighting.c @@ -0,0 +1,68 @@ +/* + * 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 + +static int +rendermode_smooth_lighting_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; + + 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) { + /* nothing special to do */ + rendermode_lighting.draw(data, state, src, mask, mask_light); +} + +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..31acdb7 100644 --- a/overviewer_core/src/rendermodes.c +++ b/overviewer_core/src/rendermodes.c @@ -26,6 +26,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..25d774e 100644 --- a/overviewer_core/src/rendermodes.h +++ b/overviewer_core/src/rendermodes.h @@ -190,6 +190,13 @@ typedef struct { } RenderModeNight; extern RenderModeInterface rendermode_night; +/* SMOOTH LIGHTING */ +typedef struct { + /* inherits from lighting */ + RenderModeLighting parent; +} RenderModeSmoothLighting; +extern RenderModeInterface rendermode_smooth_lighting; + /* SPAWN */ typedef struct { /* inherits from overlay */ diff --git a/setup.py b/setup.py index cd1b7d0..a881f9c 100755 --- a/setup.py +++ b/setup.py @@ -149,7 +149,7 @@ except: # used to figure out what files to compile -render_modes = ['normal', 'overlay', 'lighting', 'night', 'spawn', 'cave', 'mineral'] +render_modes = ['normal', 'overlay', 'lighting', 'night', '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) From 80a3849a6cc0aa007335f6b7cd13f83bfba5ff04 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 11 Oct 2011 21:17:10 -0400 Subject: [PATCH 02/12] added a triangle drawing function, with interpolated color --- overviewer_core/src/composite.c | 95 ++++++++++++++++++++++++++++++++ overviewer_core/src/overviewer.h | 7 +++ 2 files changed, 102 insertions(+) diff --git a/overviewer_core/src/composite.c b/overviewer_core/src/composite.c index d83701c..1c4ba1d 100644 --- a/overviewer_core/src/composite.c +++ b/overviewer_core/src/composite.c @@ -358,3 +358,98 @@ 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 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) { + /* 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) { + 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; + } + } + } + + return dest; +} diff --git a/overviewer_core/src/overviewer.h b/overviewer_core/src/overviewer.h index 934dae8..8aecfa8 100644 --- a/overviewer_core/src/overviewer.h +++ b/overviewer_core/src/overviewer.h @@ -51,6 +51,13 @@ 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 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); /* forward declaration of RenderMode object */ typedef struct _RenderMode RenderMode; From b984185f0ce84b6ed39678f1e705d41cc77d40e5 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 11 Oct 2011 21:27:20 -0400 Subject: [PATCH 03/12] smooth lighting mode now covers every block with 6 triangles --- .../src/rendermode-smooth-lighting.c | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/overviewer_core/src/rendermode-smooth-lighting.c b/overviewer_core/src/rendermode-smooth-lighting.c index 80570b0..2384440 100644 --- a/overviewer_core/src/rendermode-smooth-lighting.c +++ b/overviewer_core/src/rendermode-smooth-lighting.c @@ -50,8 +50,50 @@ rendermode_smooth_lighting_hidden(void *data, RenderState *state, int x, int y, static void rendermode_smooth_lighting_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); + int x = state->imgx, y = state->imgy; + + if (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); + + /* draw a triangle on top of each block */ + draw_triangle(state->img, + x+12, y, 255, 0, 0, + x+24, y+6, 0, 255, 0, + x, y+6, 0, 0, 255); + draw_triangle(state->img, + x+24, y+6, 255, 0, 0, + x, y+6, 0, 255, 0, + x+12, y+12, 0, 0, 255); + + /* left side... */ + draw_triangle(state->img, + x, y+6, 255, 0, 0, + x+12, y+12, 0, 255, 0, + x+12, y+24, 0, 0, 255); + draw_triangle(state->img, + x+12, y+24, 255, 0, 0, + x, y+6, 0, 255, 0, + x, y+18, 0, 0, 255); + + /* right side... */ + draw_triangle(state->img, + x+24, y+6, 255, 0, 0, + x+12, y+12, 0, 255, 0, + x+12, y+24, 0, 0, 255); + draw_triangle(state->img, + x+12, y+24, 255, 0, 0, + x+24, y+6, 0, 255, 0, + x+24, y+18, 0, 0, 255); } RenderModeInterface rendermode_smooth_lighting = { From 2705a1efe874d50305486b22a08ea2af6d0ca2ae Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 16 Oct 2011 17:18:24 -0400 Subject: [PATCH 04/12] smooth-lighting triangles now pull lighting data correctly for each vertex --- overviewer_core/src/composite.c | 5 +- overviewer_core/src/overviewer.h | 2 +- overviewer_core/src/rendermode-lighting.c | 69 ++++--- .../src/rendermode-smooth-lighting.c | 175 ++++++++++++++---- overviewer_core/src/rendermodes.h | 8 + 5 files changed, 193 insertions(+), 66 deletions(-) diff --git a/overviewer_core/src/composite.c b/overviewer_core/src/composite.c index 1c4ba1d..271f558 100644 --- a/overviewer_core/src/composite.c +++ b/overviewer_core/src/composite.c @@ -369,7 +369,7 @@ tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, * http://www.gidforums.com/t-20838.html ) */ PyObject * -draw_triangle(PyObject *dest, +draw_triangle(PyObject *dest, int inclusive, int x0, int y0, unsigned char r0, unsigned char g0, unsigned char b0, int x1, int y1, @@ -433,7 +433,8 @@ draw_triangle(PyObject *dest, beta = beta_norm * ((a20 * x) + (b20 * y) + c20); gamma = gamma_norm * ((a01 * x) + (b01 * y) + c01); - if (alpha >= 0 && beta >= 0 && gamma >= 0) { + 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; diff --git a/overviewer_core/src/overviewer.h b/overviewer_core/src/overviewer.h index 8aecfa8..f08ecf6 100644 --- a/overviewer_core/src/overviewer.h +++ b/overviewer_core/src/overviewer.h @@ -51,7 +51,7 @@ 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, +PyObject *draw_triangle(PyObject *dest, int inclusive, int x0, int y0, unsigned char r0, unsigned char g0, unsigned char b0, int x1, int y1, diff --git a/overviewer_core/src/rendermode-lighting.c b/overviewer_core/src/rendermode-lighting.c index 395061a..e4a9254 100644 --- a/overviewer_core/src/rendermode-lighting.c +++ b/overviewer_core/src/rendermode-lighting.c @@ -162,7 +162,7 @@ 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) { blocks = state->blocks; @@ -184,7 +184,7 @@ get_lighting_color(RenderModeLighting *self, RenderState *state, 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 +193,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 +236,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 +277,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; diff --git a/overviewer_core/src/rendermode-smooth-lighting.c b/overviewer_core/src/rendermode-smooth-lighting.c index 2384440..38e500e 100644 --- a/overviewer_core/src/rendermode-smooth-lighting.c +++ b/overviewer_core/src/rendermode-smooth-lighting.c @@ -18,10 +18,144 @@ #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]; +}; + +/* the lighting face rule list! */ +static struct SmoothLightingFace lighting_rules[] = { + /* 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}, + }}, + /* left */ + {-1, 0, 0, { + {12, 24, + 0, 1, 0, + 0, 0, -1}, + {0, 18, + 0, -1, 0, + 0, 0, -1}, + {0, 6, + 0, -1, 0, + 0, 0, 1}, + {12, 12, + 0, 1, 0, + 0, 0, 1}, + }}, + /* right */ + {0, 1, 0, { + {12, 12, + -1, 0, 0, + 0, 0, 1}, + {12, 24, + -1, 0, 0, + 0, 0, -1}, + {24, 18, + 1, 0, 0, + 0, 0, -1}, + {24, 6, + 1, 0, 0, + 0, 0, 1}, + }}, +}; + +/* 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; + 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; + + 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]); + 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]); +} + static int rendermode_smooth_lighting_start(void *data, RenderState *state, PyObject *options) { - RenderModeNight* self; - /* first, chain up */ int ret = rendermode_lighting.start(data, state, options); if (ret != 0) @@ -50,9 +184,10 @@ rendermode_smooth_lighting_hidden(void *data, RenderState *state, int x, int y, static void rendermode_smooth_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { - int x = state->imgx, y = state->imgy; + RenderModeSmoothLighting *self = (RenderModeSmoothLighting *)data; - if (is_transparent(state->block)) + /* special case for leaves -- these are also smooth-lit! */ + if (state->block != 18 && is_transparent(state->block)) { /* transparent blocks are rendered as usual, with flat lighting */ rendermode_lighting.draw(data, state, src, mask, mask_light); @@ -65,35 +200,9 @@ rendermode_smooth_lighting_draw(void *data, RenderState *state, PyObject *src, P * lighting mode draws */ rendermode_normal.draw(data, state, src, mask, mask_light); - /* draw a triangle on top of each block */ - draw_triangle(state->img, - x+12, y, 255, 0, 0, - x+24, y+6, 0, 255, 0, - x, y+6, 0, 0, 255); - draw_triangle(state->img, - x+24, y+6, 255, 0, 0, - x, y+6, 0, 255, 0, - x+12, y+12, 0, 0, 255); - - /* left side... */ - draw_triangle(state->img, - x, y+6, 255, 0, 0, - x+12, y+12, 0, 255, 0, - x+12, y+24, 0, 0, 255); - draw_triangle(state->img, - x+12, y+24, 255, 0, 0, - x, y+6, 0, 255, 0, - x, y+18, 0, 0, 255); - - /* right side... */ - draw_triangle(state->img, - x+24, y+6, 255, 0, 0, - x+12, y+12, 0, 255, 0, - x+12, y+24, 0, 0, 255); - draw_triangle(state->img, - x+12, y+24, 255, 0, 0, - x+24, y+6, 0, 255, 0, - x+24, y+18, 0, 0, 255); + do_shading_with_rule(self, state, lighting_rules[FACE_TOP]); + do_shading_with_rule(self, state, lighting_rules[FACE_LEFT]); + do_shading_with_rule(self, state, lighting_rules[FACE_RIGHT]); } RenderModeInterface rendermode_smooth_lighting = { diff --git a/overviewer_core/src/rendermodes.h b/overviewer_core/src/rendermodes.h index 25d774e..a7aa47f 100644 --- a/overviewer_core/src/rendermodes.h +++ b/overviewer_core/src/rendermodes.h @@ -183,6 +183,14 @@ typedef struct { } 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); + +/* 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); + /* NIGHT */ typedef struct { /* inherits from lighting */ From 0fa734d0c5f79a23894667a79c19c406859992f8 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Mon, 17 Oct 2011 10:09:59 -0400 Subject: [PATCH 05/12] fixed smooth lighting errors on chunk boundaries --- overviewer_core/src/rendermode-lighting.c | 20 +++++++++++++++++++- overviewer_core/src/rendermodes.h | 2 ++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/overviewer_core/src/rendermode-lighting.c b/overviewer_core/src/rendermode-lighting.c index e4a9254..925b7d6 100644 --- a/overviewer_core/src/rendermode-lighting.c +++ b/overviewer_core/src/rendermode-lighting.c @@ -164,7 +164,7 @@ get_lighting_color(RenderModeLighting *self, RenderState *state, 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,6 +178,16 @@ 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 */ @@ -325,6 +335,10 @@ 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; @@ -356,6 +370,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); diff --git a/overviewer_core/src/rendermodes.h b/overviewer_core/src/rendermodes.h index a7aa47f..ca9a390 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; From 4bddf2c78af096196a7f594f578ffabf928739ab Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 18 Oct 2011 09:56:01 -0400 Subject: [PATCH 06/12] fixed top smooth lighting tesselation error --- overviewer_core/src/composite.c | 32 ++++++++++++++- overviewer_core/src/overviewer.h | 3 +- .../src/rendermode-smooth-lighting.c | 39 ++++++++++++++++--- 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/overviewer_core/src/composite.c b/overviewer_core/src/composite.c index 271f558..2a25706 100644 --- a/overviewer_core/src/composite.c +++ b/overviewer_core/src/composite.c @@ -375,7 +375,9 @@ draw_triangle(PyObject *dest, int inclusive, 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) { + 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 */ @@ -452,5 +454,33 @@ draw_triangle(PyObject *dest, int inclusive, } } + 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 f08ecf6..8d10d81 100644 --- a/overviewer_core/src/overviewer.h +++ b/overviewer_core/src/overviewer.h @@ -57,7 +57,8 @@ PyObject *draw_triangle(PyObject *dest, int inclusive, 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); + 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-smooth-lighting.c b/overviewer_core/src/rendermode-smooth-lighting.c index 38e500e..2af427c 100644 --- a/overviewer_core/src/rendermode-smooth-lighting.c +++ b/overviewer_core/src/rendermode-smooth-lighting.c @@ -36,10 +36,32 @@ struct SmoothLightingFace { /* 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, @@ -54,7 +76,9 @@ static struct SmoothLightingFace lighting_rules[] = { {12, 12, -1, 0, 0, 0, 1, 0}, - }}, + }, + top_touchups, 3}, + /* left */ {-1, 0, 0, { {12, 24, @@ -69,7 +93,9 @@ static struct SmoothLightingFace lighting_rules[] = { {12, 12, 0, 1, 0, 0, 0, 1}, - }}, + }, + NULL, 0}, + /* right */ {0, 1, 0, { {12, 12, @@ -84,7 +110,8 @@ static struct SmoothLightingFace lighting_rules[] = { {24, 6, 1, 0, 0, 0, 0, 1}, - }}, + }, + NULL, 0}, }; /* helpers for indexing the rule list */ @@ -147,11 +174,13 @@ do_shading_with_rule(RenderModeSmoothLighting *self, RenderState *state, struct 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+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+pts[3].imgx, y+pts[3].imgy, pts_r[3], pts_g[3], pts_b[3], + x, y, NULL, 0); } static int From 4fb0dac3fde73a1eb206b0d11e2efa86467fd328 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 26 Oct 2011 17:08:35 -0400 Subject: [PATCH 07/12] the position anchor tag now uses rendermode name, not label --- overviewer_core/data/web_assets/overviewer.js | 45 ++++++++++++++----- .../data/web_assets/overviewerConfig.js | 4 ++ overviewer_core/googlemap.py | 1 + 3 files changed, 39 insertions(+), 11 deletions(-) diff --git a/overviewer_core/data/web_assets/overviewer.js b/overviewer_core/data/web_assets/overviewer.js index 34fbfe8..eacf957 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]); } @@ -479,7 +484,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; @@ -971,6 +976,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() { @@ -1020,7 +1030,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), From 00f06e0499db8f53560af718ef5db2c42a0e7ad7 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 26 Oct 2011 21:50:23 -0400 Subject: [PATCH 08/12] last of the obvious smooth-lighting render issues fixed --- overviewer_core/src/rendermode-smooth-lighting.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/overviewer_core/src/rendermode-smooth-lighting.c b/overviewer_core/src/rendermode-smooth-lighting.c index 2af427c..d8f63a9 100644 --- a/overviewer_core/src/rendermode-smooth-lighting.c +++ b/overviewer_core/src/rendermode-smooth-lighting.c @@ -81,9 +81,6 @@ static struct SmoothLightingFace lighting_rules[] = { /* left */ {-1, 0, 0, { - {12, 24, - 0, 1, 0, - 0, 0, -1}, {0, 18, 0, -1, 0, 0, 0, -1}, @@ -93,11 +90,17 @@ static struct SmoothLightingFace lighting_rules[] = { {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}, @@ -107,9 +110,6 @@ static struct SmoothLightingFace lighting_rules[] = { {24, 18, 1, 0, 0, 0, 0, -1}, - {24, 6, - 1, 0, 0, - 0, 0, 1}, }, NULL, 0}, }; From 85da19b1a024d0cd1e6ccc32c04ee9c46bc654e8 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Wed, 26 Oct 2011 22:19:27 -0400 Subject: [PATCH 09/12] turned night into a lighting mode option, added 'night' builtin custom mode --- overviewer.py | 20 ++++ overviewer_core/src/rendermode-lighting.c | 50 +++++++++- overviewer_core/src/rendermode-night.c | 106 ---------------------- overviewer_core/src/rendermodes.c | 1 - overviewer_core/src/rendermodes.h | 8 +- setup.py | 2 +- 6 files changed, 70 insertions(+), 117 deletions(-) delete mode 100644 overviewer_core/src/rendermode-night.c diff --git a/overviewer.py b/overviewer.py index 3399424..9e1df17 100755 --- a/overviewer.py +++ b/overviewer.py @@ -87,6 +87,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] """ @@ -145,6 +163,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/src/rendermode-lighting.c b/overviewer_core/src/rendermode-lighting.c index 925b7d6..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 @@ -319,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; @@ -340,7 +377,11 @@ rendermode_lighting_start(void *data, RenderState *state, PyObject *options) { 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", ""); @@ -349,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; @@ -430,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/rendermodes.c b/overviewer_core/src/rendermodes.c index 31acdb7..dd0848f 100644 --- a/overviewer_core/src/rendermodes.c +++ b/overviewer_core/src/rendermodes.c @@ -25,7 +25,6 @@ static RenderModeInterface *render_modes[] = { &rendermode_normal, &rendermode_lighting, - &rendermode_night, &rendermode_smooth_lighting, &rendermode_spawn, &rendermode_cave, diff --git a/overviewer_core/src/rendermodes.h b/overviewer_core/src/rendermodes.h index ca9a390..5084cd8 100644 --- a/overviewer_core/src/rendermodes.h +++ b/overviewer_core/src/rendermodes.h @@ -182,6 +182,7 @@ typedef struct { float shade_strength; int color_light; + int night; } RenderModeLighting; extern RenderModeInterface rendermode_lighting; @@ -193,13 +194,6 @@ void get_lighting_color(RenderModeLighting *self, RenderState *state, int x, int y, int z, unsigned char *r, unsigned char *g, unsigned char *b); -/* NIGHT */ -typedef struct { - /* inherits from lighting */ - RenderModeLighting parent; -} RenderModeNight; -extern RenderModeInterface rendermode_night; - /* SMOOTH LIGHTING */ typedef struct { /* inherits from lighting */ diff --git a/setup.py b/setup.py index a881f9c..7714874 100755 --- a/setup.py +++ b/setup.py @@ -149,7 +149,7 @@ except: # used to figure out what files to compile -render_modes = ['normal', 'overlay', 'lighting', 'night', 'smooth-lighting', '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) From 670a458ec1d7fb81c315dca2fb73b4fcbabdfd98 Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Tue, 1 Nov 2011 15:59:03 -0400 Subject: [PATCH 10/12] made sure smooth-lighting honors the shade_strength option --- overviewer_core/src/rendermode-smooth-lighting.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/overviewer_core/src/rendermode-smooth-lighting.c b/overviewer_core/src/rendermode-smooth-lighting.c index d8f63a9..c9412ac 100644 --- a/overviewer_core/src/rendermode-smooth-lighting.c +++ b/overviewer_core/src/rendermode-smooth-lighting.c @@ -128,6 +128,7 @@ do_shading_with_rule(RenderModeSmoothLighting *self, RenderState *state, struct 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}; @@ -165,6 +166,10 @@ do_shading_with_rule(RenderModeSmoothLighting *self, RenderState *state, struct &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; From 53dbdc98c6b542103c46f60a72c785169e74670e Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sat, 5 Nov 2011 23:29:51 -0400 Subject: [PATCH 11/12] added special smooth-lighting exception for water --- .../src/rendermode-smooth-lighting.c | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/overviewer_core/src/rendermode-smooth-lighting.c b/overviewer_core/src/rendermode-smooth-lighting.c index c9412ac..28767ea 100644 --- a/overviewer_core/src/rendermode-smooth-lighting.c +++ b/overviewer_core/src/rendermode-smooth-lighting.c @@ -218,10 +218,14 @@ rendermode_smooth_lighting_hidden(void *data, RenderState *state, int x, int y, 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 -- these are also smooth-lit! */ - if (state->block != 18 && is_transparent(state->block)) + /* 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); @@ -234,9 +238,23 @@ rendermode_smooth_lighting_draw(void *data, RenderState *state, PyObject *src, P * lighting mode draws */ rendermode_normal.draw(data, state, src, mask, mask_light); - do_shading_with_rule(self, state, lighting_rules[FACE_TOP]); - do_shading_with_rule(self, state, lighting_rules[FACE_LEFT]); - do_shading_with_rule(self, state, lighting_rules[FACE_RIGHT]); + /* 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 = { From 4de9ba3b6e643ac08aa9b127995e593e3857f3df Mon Sep 17 00:00:00 2001 From: Aaron Griffith Date: Sun, 6 Nov 2011 20:34:06 -0500 Subject: [PATCH 12/12] added docs for lighting and smooth-lighting --- docs/design/designdoc.rst | 67 +++ docs/design/lighting/light-eqn.png | Bin 0 -> 1145 bytes docs/design/lighting/lighting-process.png | Bin 0 -> 38945 bytes docs/design/lighting/lighting-process.svg | 597 ++++++++++++++++++++++ docs/design/lighting/smooth-average.png | Bin 0 -> 13111 bytes docs/design/lighting/smooth-average.svg | 297 +++++++++++ 6 files changed, 961 insertions(+) create mode 100644 docs/design/lighting/light-eqn.png create mode 100644 docs/design/lighting/lighting-process.png create mode 100644 docs/design/lighting/lighting-process.svg create mode 100644 docs/design/lighting/smooth-average.png create mode 100644 docs/design/lighting/smooth-average.svg diff --git a/docs/design/designdoc.rst b/docs/design/designdoc.rst index e6a7ef7..90ac1b9 100644 --- a/docs/design/designdoc.rst +++ b/docs/design/designdoc.rst @@ -360,5 +360,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 0000000000000000000000000000000000000000..8b8067f3245187563d7b16aba216bfe58c5dc8d4 GIT binary patch literal 1145 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 dGXj6hzu4Ef=5~kWTm}XP22WQ%mvv4FO#ln}o! + + + + + + + + + + + + + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..03dcd4728fb9c4ad936a5f7c1455b412763c5adb GIT binary patch literal 13111 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

Du>bjg>y)U!+f=k3gd3_>gk< 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 + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +