diff --git a/chunk.py b/chunk.py index 1155f40..1076edb 100644 --- a/chunk.py +++ b/chunk.py @@ -398,108 +398,6 @@ class ChunkRenderer(object): return pseudo_data - def calculate_darkness(self, skylight, blocklight): - """Takes a raw blocklight and skylight, and returns a value - between 0.0 (fully lit) and 1.0 (fully black) that can be used as - an alpha value for a blend with a black source image. It mimics - Minecraft lighting calculations.""" - if not self.quadtree.night: - # Daytime - return 1.0 - pow(0.8, 15 - max(blocklight, skylight)) - else: - # Nighttime - return 1.0 - pow(0.8, 15 - max(blocklight, skylight - 11)) - - def get_lighting_coefficient(self, x, y, z, norecurse=False): - """Calculates the lighting coefficient for the given - coordinate, using default lighting and peeking into - neighboring chunks, if needed. A lighting coefficient of 1.0 - means fully black. - - Returns a tuple (coefficient, occluded), where occluded is - True if the given coordinate is filled with a solid block, and - therefore the returned coefficient is just the default.""" - - # placeholders for later data arrays, coordinates - blocks = None - skylight = None - blocklight = None - local_x = x - local_y = y - local_z = z - is_local_chunk = False - - # find out what chunk we're in, and translate accordingly - if x >= 0 and y < 16: - blocks = self.blocks - skylight = self.skylight - blocklight = self.blocklight - is_local_chunk = True - elif x < 0: - local_x += 16 - blocks = self.left_blocks - skylight = self.left_skylight - blocklight = self.left_blocklight - elif y >= 16: - local_y -= 16 - blocks = self.right_blocks - skylight = self.right_skylight - blocklight = self.right_blocklight - - # make sure we have a correctly-ranged coordinates and enough - # info about the chunk - if not (blocks is not None and skylight is not None and blocklight is not None and - local_x >= 0 and local_x < 16 and local_y >= 0 and local_y < 16 and - local_z >= 0 and local_z < 128): - # we have no useful info, return default - return (self.calculate_darkness(15, 0), False) - - blocktype = blocks[local_x, local_y, local_z] - - # special handling for half-blocks - # (don't recurse more than once!) - if blocktype == 44 and not norecurse: - # average gathering variables - averagegather = 0.0 - averagecount = 0 - - # how bright we need before we consider a side "lit" - threshold = self.calculate_darkness(0, 0) - # iterate through all the sides of the block - sides = [(x-1, y, z), (x+1, y, z), (x, y, z-1), (x, y, z+1), (x, y-1, z), (x, y+1, z)] - - for side in sides: - val, occ = self.get_lighting_coefficient(*side, norecurse=True) - if (not occ) and (val < threshold): - averagegather += val - averagecount += 1 - - # if at least one side was lit, return the average - if averagecount > 0: - return (averagegather / averagecount, False) - - # calculate the return... - occluded = not (blocktype in transparent_blocks) - - # only calculate the non-default coefficient if we're not occluded - if (blocktype == 10) or (blocktype == 11): - # lava blocks should always be lit! - coefficient = 0.0 - elif occluded: - coefficient = self.calculate_darkness(15, 0) - else: - coefficient = self.calculate_darkness(skylight[local_x, local_y, local_z], blocklight[local_x, local_y, local_z]) - - # only say we're occluded if the point is in the CURRENT - # chunk, so that we don't get obvious inter-chunk dependencies - # (we want this here so we still have the default coefficient - # for occluded blocks, even when we don't report them as - # occluded) - if not is_local_chunk: - occluded = False - - return (coefficient, occluded) - def chunk_render(self, img=None, xoff=0, yoff=0, cave=False): """Renders a chunk with the given parameters, and returns the image. If img is given, the chunk is rendered to that image object. Otherwise, diff --git a/src/overviewer.h b/src/overviewer.h index f85636e..5ae9079 100644 --- a/src/overviewer.h +++ b/src/overviewer.h @@ -32,6 +32,10 @@ /* macro for getting a value out of a 3D numpy byte array */ #define getArrayByte3D(array, x,y,z) (*(unsigned char *)(PyArray_GETPTR3((array), (x), (y), (z)))) +/* generally useful MAX / MIN macros */ +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + /* in composite.c */ Imaging imaging_python_to_c(PyObject *obj); PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask, int dx, diff --git a/src/rendermode-lighting.c b/src/rendermode-lighting.c index 5229cb7..b00c3aa 100644 --- a/src/rendermode-lighting.c +++ b/src/rendermode-lighting.c @@ -16,37 +16,94 @@ */ #include "overviewer.h" +#include + +/* figures out the black_coeff from a given skylight and blocklight, + used in lighting calculations */ +static float calculate_darkness(unsigned char skylight, unsigned char blocklight) { + return 1.0f - powf(0.8f, 15.0 - MAX(blocklight, skylight)); +} + +/* loads the appropriate light data for the given (possibly non-local) + coordinates, and returns a black_coeff */ +static inline float +get_lighting_coefficient(RenderModeLighting *self, RenderState *state, int x, int y, int z) { + /* 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; + + /* find out what chunk we're in, and translate accordingly */ + if (x >= 0 && y < 16) { + blocks = state->blocks; + skylight = self->skylight; + blocklight = self->blocklight; + } else if (x < 0) { + local_x += 16; + blocks = self->left_blocks; + skylight = self->left_skylight; + blocklight = self->left_blocklight; + } else if (y >= 16) { + local_y -= 16; + blocks = self->right_blocks; + skylight = self->right_skylight; + 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 self->calculate_darkness(15, 0); + } + + /* 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) { + + return self->calculate_darkness(15, 0); + } + + unsigned char block = getArrayByte3D(blocks, local_x, local_y, local_z); + + if (block == 44) { + /* TODO special handling for half-blocks! */ + } + + if (block == 10 || block == 11) { + /* lava blocks should always be lit! */ + return 0.0f; + } + + unsigned char skylevel = getArrayByte3D(skylight, local_x, local_y, local_z); + unsigned char blocklevel = getArrayByte3D(blocklight, local_x, local_y, local_z); + + return self->calculate_darkness(skylevel, blocklevel); +} /* shades the drawn block with the given facemask/black_color, based on the lighting results from (x, y, z) */ static inline void -do_shading_for_face(PyObject *chunk, int x, int y, int z, PyObject *facemask, PyObject *black_color, - PyObject *img, int imgx, int imgy) { - // returns new references - PyObject* light_tup = PyObject_CallMethod(chunk, "get_lighting_coefficient", "iii", x, y, z); - PyObject *black_coeff_py = PySequence_GetItem(light_tup, 0); - double black_coeff = PyFloat_AsDouble(black_coeff_py); - Py_DECREF(black_coeff_py); - - PyObject *face_occlude_py = PySequence_GetItem(light_tup, 1); - int face_occlude = PyInt_AsLong(face_occlude_py); - Py_DECREF(face_occlude_py); - - Py_DECREF(light_tup); - - - if (!face_occlude) { - //#composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) - - PyObject *mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref - //printf("black_coeff: %f\n", black_coeff); - brightness(mask, black_coeff); - //printf("done with brightness\n"); - alpha_over(img, black_color, mask, imgx, imgy, 0, 0); - //printf("done with alpha_over\n"); - Py_DECREF(mask); - +do_shading_for_face(RenderModeLighting *self, RenderState *state, + int x, int y, int z, PyObject *facemask) { + /* 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)) { + /* this face isn't visible, so don't draw anything */ + return; + } } + + float black_coeff = get_lighting_coefficient(self, state, x, y, z); + + PyObject *mask = PyObject_CallMethod(facemask, "copy", NULL); // new ref + brightness(mask, black_coeff); + alpha_over(state->img, self->black_color, mask, state->imgx, state->imgy, 0, 0); + Py_DECREF(mask); } static int @@ -65,6 +122,19 @@ rendermode_lighting_start(void *data, RenderState *state) { self->facemasks[1] = PyTuple_GetItem(self->facemasks_py, 1); self->facemasks[2] = PyTuple_GetItem(self->facemasks_py, 2); + self->skylight = PyObject_GetAttrString(state->self, "skylight"); + self->blocklight = PyObject_GetAttrString(state->self, "blocklight"); + + self->left_blocks = PyObject_GetAttrString(state->self, "left_blocks"); + self->left_skylight = PyObject_GetAttrString(state->self, "left_skylight"); + self->left_blocklight = PyObject_GetAttrString(state->self, "left_blocklight"); + + self->right_blocks = PyObject_GetAttrString(state->self, "right_blocks"); + self->right_skylight = PyObject_GetAttrString(state->self, "right_skylight"); + self->right_blocklight = PyObject_GetAttrString(state->self, "right_blocklight"); + + self->calculate_darkness = calculate_darkness; + return 0; } @@ -75,6 +145,17 @@ rendermode_lighting_finish(void *data, RenderState *state) { Py_DECREF(self->black_color); Py_DECREF(self->facemasks_py); + Py_DECREF(self->skylight); + Py_DECREF(self->blocklight); + + Py_DECREF(self->left_blocks); + Py_DECREF(self->left_skylight); + Py_DECREF(self->left_blocklight); + + Py_DECREF(self->right_blocks); + Py_DECREF(self->right_skylight); + Py_DECREF(self->right_blocklight); + /* now chain up */ rendermode_normal.finish(data, state); } @@ -91,20 +172,12 @@ rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject rendermode_normal.draw(data, state, src, mask); RenderModeLighting* self = (RenderModeLighting *)data; - - PyObject *chunk = state->self; int x = state->x, y = state->y, z = state->z; - PyObject **facemasks = self->facemasks; - PyObject *black_color = self->black_color, *img = state->img; - int imgx = state->imgx, imgy = state->imgy; - // FIXME whole-block shading for transparent blocks - do_shading_for_face(chunk, x, y, z+1, facemasks[0], black_color, - img, imgx, imgy); - do_shading_for_face(chunk, x-1, y, z, facemasks[1], black_color, - img, imgx, imgy); - do_shading_for_face(chunk, x, y+1, z, facemasks[2], black_color, - img, imgx, imgy); + // TODO whole-block shading for transparent blocks + do_shading_for_face(self, state, x, y, z+1, self->facemasks[0]); + do_shading_for_face(self, state, x-1, y, z, self->facemasks[1]); + do_shading_for_face(self, state, x, y+1, z, self->facemasks[2]); } RenderModeInterface rendermode_lighting = { diff --git a/src/rendermodes.h b/src/rendermodes.h index 89f12f1..1a693da 100644 --- a/src/rendermodes.h +++ b/src/rendermodes.h @@ -70,6 +70,15 @@ typedef struct { PyObject *black_color, *facemasks_py; PyObject *facemasks[3]; + + /* extra block data, loaded off the chunk class */ + PyObject *skylight, *blocklight; + PyObject *left_blocks, *left_skylight, *left_blocklight; + PyObject *right_blocks, *right_skylight, *right_blocklight; + + /* can be overridden in derived rendermodes to control lighting + arguments are skylight, blocklight */ + float (*calculate_darkness)(unsigned char, unsigned char); } RenderModeLighting; extern RenderModeInterface rendermode_lighting;