0

converted lighting mode into a primitive

This commit is contained in:
Aaron Griffith
2012-01-08 22:31:41 -05:00
parent c93715ebfa
commit ae88b6e27b
4 changed files with 127 additions and 161 deletions

View File

@@ -0,0 +1,485 @@
/*
* This file is part of the Minecraft Overviewer.
*
* Minecraft Overviewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Minecraft Overviewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../overviewer.h"
#include <math.h>
typedef struct {
PyObject *facemasks_py;
PyObject *facemasks[3];
/* extra data, loaded off the chunk class */
PyObject *skylight, *blocklight;
PyObject *left_skylight, *left_blocklight;
PyObject *right_skylight, *right_blocklight;
PyObject *up_left_skylight, *up_left_blocklight;
PyObject *up_right_skylight, *up_right_blocklight;
/* light color image, loaded if color_light is True */
PyObject *lightcolor;
/* can be overridden in derived rendermodes to control lighting
arguments are data, skylight, blocklight, return RGB */
void (*calculate_light_color)(void *, unsigned char, unsigned char, unsigned char *, unsigned char *, unsigned char *);
/* can be set to 0 in derived modes to indicate that lighting the chunk
* sides is actually important. Right now, this is used in cave mode
*/
int skip_sides;
float strength;
int color;
int night;
} RenderPrimitiveLighting;
/* figures out the color from a given skylight and blocklight,
used in lighting calculations */
static void
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));
*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) {
RenderPrimitiveLighting *mode = (RenderPrimitiveLighting *)(data);
unsigned int index;
PyObject *color;
blocklight = MAX(blocklight, skylight);
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);
}
/* 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) {
RenderPrimitiveLighting *mode = (RenderPrimitiveLighting *)(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
*
* authoratative is a return slot for whether or not this lighting calculation
* is true, or a guess. If we guessed, *authoratative will be false, but if it
* was calculated correctly from available light data, it will be true. You
* may (and probably should) pass NULL.
*/
inline unsigned char
estimate_blocklevel(RenderPrimitiveLighting *self, RenderState *state,
int x, int y, int z, int *authoratative) {
/* placeholders for later data arrays, coordinates */
PyObject *blocks = NULL;
PyObject *blocklight = NULL;
int local_x = x, local_y = y, local_z = z;
unsigned char block, blocklevel;
unsigned int average_count = 0, average_gather = 0, coeff = 0;
/* defaults to "guess" until told otherwise */
if (authoratative)
*authoratative = 0;
/* find out what chunk we're in, and translate accordingly */
if (x >= 0 && y < 16) {
blocks = state->blocks;
blocklight = self->blocklight;
} else if (x < 0) {
local_x += 16;
blocks = state->left_blocks;
blocklight = self->left_blocklight;
} else if (y >= 16) {
local_y -= 16;
blocks = state->right_blocks;
blocklight = self->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)) {
return 0;
}
/* also, make sure we have enough info to correctly calculate lighting */
if (blocks == Py_None || blocks == NULL ||
blocklight == Py_None || blocklight == NULL) {
return 0;
}
block = getArrayByte3D(blocks, local_x, local_y, local_z);
if (authoratative == NULL) {
int auth;
/* iterate through all surrounding blocks to take an average */
int dx, dy, dz, local_block;
for (dx = -1; dx <= 1; dx += 2) {
for (dy = -1; dy <= 1; dy += 2) {
for (dz = -1; dz <= 1; dz += 2) {
/* skip if block is out of range */
if (x+dx < 0 || x+dx >= 16 ||
y+dy < 0 || y+dy >= 16 ||
z+dz < 0 || z+dz >= 128) {
continue;
}
coeff = estimate_blocklevel(self, state, x+dx, y+dy, z+dz, &auth);
local_block = getArrayByte3D(blocks, x+dx, y+dy, z+dz);
/* only add if the block is transparent, this seems to look better than
using every block */
if (auth && is_transparent(local_block)) {
average_gather += coeff;
average_count++;
}
}
}
}
}
/* only return the average if at least one was authoratative */
if (average_count > 0) {
return average_gather / average_count;
}
blocklevel = getArrayByte3D(blocklight, local_x, local_y, local_z);
/* no longer a guess */
if (!(block == 44 || block == 53 || block == 67 || block == 108 || block == 109) && authoratative) {
*authoratative = 1;
}
return blocklevel;
}
inline void
get_lighting_color(RenderPrimitiveLighting *self, RenderState *state,
int x, int y, int z,
unsigned char *r, unsigned char *g, unsigned char *b) {
/* placeholders for later data arrays, coordinates */
PyObject *blocks = NULL;
PyObject *skylight = NULL;
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 && x < 16 && y >= 0 && y < 16) {
blocks = state->blocks;
skylight = self->skylight;
blocklight = self->blocklight;
} else if (x < 0) {
local_x += 16;
blocks = state->left_blocks;
skylight = self->left_skylight;
blocklight = self->left_blocklight;
} else if (y >= 16) {
local_y -= 16;
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;
}
/* also, make sure we have enough info to correctly calculate lighting */
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;
}
block = getArrayByte3D(blocks, local_x, local_y, local_z);
skylevel = getArrayByte3D(skylight, local_x, local_y, local_z);
blocklevel = getArrayByte3D(blocklight, local_x, local_y, local_z);
/* special half-step handling */
if (block == 44 || block == 53 || block == 67 || block == 108 || block == 109) {
unsigned int upper_block;
/* stairs and half-blocks take the skylevel from the upper block if it's transparent */
if (local_z != 127) {
int upper_counter = 0;
/* but if the upper_block is one of these special half-steps, we need to look at *its* upper_block */
do {
upper_counter++;
upper_block = getArrayByte3D(blocks, local_x, local_y, local_z + upper_counter);
} while ((upper_block == 44 || upper_block == 53 || upper_block == 67 || upper_block == 108 || upper_block == 109) && local_z < 127);
if (is_transparent(upper_block)) {
skylevel = getArrayByte3D(skylight, local_x, local_y, local_z + upper_counter);
}
} else {
upper_block = 0;
skylevel = 15;
}
/* the block has a bad blocklevel, estimate it from neigborhood
* use given coordinates, no local ones! */
blocklevel = estimate_blocklevel(self, state, x, y, z, NULL);
}
if (block == 10 || block == 11) {
/* lava blocks should always be lit! */
*r = 255;
*g = 255;
*b = 255;
return;
}
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
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
do_shading_with_mask(RenderPrimitiveLighting *self, RenderState *state,
int x, int y, int z, PyObject *mask) {
unsigned char r, g, b;
float comp_strength;
/* check occlusion */
if (lighting_is_face_occluded(state, self->skip_sides, x, y, z))
return;
get_lighting_color(self, state, x, y, z, &r, &g, &b);
comp_strength = 1.0 - self->strength;
r += (255 - r) * comp_strength;
g += (255 - g) * comp_strength;
b += (255 - b) * comp_strength;
tint_with_mask(state->img, r, g, b, 255, mask, state->imgx, state->imgy, 0, 0);
}
static int
lighting_start(void *data, RenderState *state, PyObject *support) {
RenderPrimitiveLighting* self;
self = (RenderPrimitiveLighting *)data;
/* don't skip sides by default */
self->skip_sides = 0;
if (!render_mode_parse_option(support, "strength", "f", &(self->strength)))
return 1;
if (!render_mode_parse_option(support, "night", "i", &(self->night)))
return 1;
if (!render_mode_parse_option(support, "color", "i", &(self->color)))
return 1;
self->facemasks_py = PyObject_GetAttrString(support, "facemasks");
// borrowed references, don't need to be decref'd
self->facemasks[0] = PyTuple_GetItem(self->facemasks_py, 0);
self->facemasks[1] = PyTuple_GetItem(self->facemasks_py, 1);
self->facemasks[2] = PyTuple_GetItem(self->facemasks_py, 2);
self->skylight = get_chunk_data(state, CURRENT, SKYLIGHT);
self->blocklight = get_chunk_data(state, CURRENT, BLOCKLIGHT);
self->left_skylight = get_chunk_data(state, DOWN_LEFT, SKYLIGHT);
self->left_blocklight = get_chunk_data(state, DOWN_LEFT, BLOCKLIGHT);
self->right_skylight = get_chunk_data(state, DOWN_RIGHT, SKYLIGHT);
self->right_blocklight = get_chunk_data(state, DOWN_RIGHT, BLOCKLIGHT);
self->up_left_skylight = get_chunk_data(state, UP_LEFT, SKYLIGHT);
self->up_left_blocklight = get_chunk_data(state, UP_LEFT, BLOCKLIGHT);
self->up_right_skylight = get_chunk_data(state, UP_RIGHT, SKYLIGHT);
self->up_right_blocklight = get_chunk_data(state, UP_RIGHT, BLOCKLIGHT);
if (self->night) {
self->calculate_light_color = calculate_light_color_night;
} else {
self->calculate_light_color = calculate_light_color;
}
if (self->color) {
self->lightcolor = PyObject_CallMethod(state->textures, "load_light_color", "");
if (self->lightcolor == Py_None) {
Py_DECREF(self->lightcolor);
self->lightcolor = NULL;
self->color = 0;
} else {
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;
}
return 0;
}
static void
lighting_finish(void *data, RenderState *state) {
RenderPrimitiveLighting *self = (RenderPrimitiveLighting *)data;
Py_DECREF(self->facemasks_py);
Py_DECREF(self->skylight);
Py_DECREF(self->blocklight);
Py_DECREF(self->left_skylight);
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);
}
static void
lighting_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) {
RenderPrimitiveLighting* self;
int x, y, z;
self = (RenderPrimitiveLighting *)data;
x = state->x, y = state->y, z = state->z;
if ((state->block == 9) || (state->block == 79)) { /* special case for water and ice */
/* looks like we need a new case for lighting, there are
* blocks that are transparent for occlusion calculations and
* need per-face shading if the face is drawn. */
if ((state->block_pdata & 16) == 16) {
do_shading_with_mask(self, state, x, y, z+1, self->facemasks[0]);
}
if ((state->block_pdata & 2) == 2) { /* bottom left */
do_shading_with_mask(self, state, x-1, y, z, self->facemasks[1]);
}
if ((state->block_pdata & 4) == 4) { /* bottom right */
do_shading_with_mask(self, state, x, y+1, z, self->facemasks[2]);
}
/* leaves are transparent for occlusion calculations but they
* per face-shading to look as in game */
} else if (is_transparent(state->block) && (state->block != 18)) {
/* transparent: do shading on whole block */
do_shading_with_mask(self, state, x, y, z, mask_light);
} else {
/* opaque: do per-face shading */
do_shading_with_mask(self, state, x, y, z+1, self->facemasks[0]);
do_shading_with_mask(self, state, x-1, y, z, self->facemasks[1]);
do_shading_with_mask(self, state, x, y+1, z, self->facemasks[2]);
}
}
RenderPrimitiveInterface primitive_lighting = {
"lighting", sizeof(RenderPrimitiveLighting),
lighting_start,
lighting_finish,
NULL,
NULL,
lighting_draw,
};