/* shades the drawn block with the given facemask/black_color, based on the
   lighting results from (x, y, z) */
static inline void
do_shading_with_mask(RenderModeLighting *self, RenderState *state,
                     int x, int y, int z, PyObject *mask) {
    float black_coeff;

    /* 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;
        }
    }
    
    black_coeff = get_lighting_coefficient(self, state, x, y, z, NULL);
    alpha_over_full(state->img, self->black_color, mask, black_coeff, state->imgx, state->imgy, 0, 0);
}
/* shades the drawn block with the given facemask/black_color, based on the
   lighting results from (x, y, z) */
static inline void
do_shading_with_mask(RenderModeLighting *self, RenderState *state,
                     int x, int y, int z, PyObject *mask) {
    float black_coeff;
    
    /* 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;
           }
    }
    
    black_coeff = get_lighting_coefficient(self, state, x, y, z);
    black_coeff *= self->shade_strength;
    alpha_over_full(state->img, self->black_color, mask, black_coeff, state->imgx, state->imgy, 0, 0);
}
/* 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 float
get_lighting_coefficient(RenderModeLighting *self, RenderState *state,
                         int x, int y, int z, int *authoratative) {
    
    /* 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;
    
    /* 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;
        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;
    }
    
    /* 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);
    }
    
    block = getArrayByte3D(blocks, local_x, local_y, local_z);
    
    /* if this block is opaque, use a fully-lit coeff instead
       to prevent stippled lines along chunk boundaries! */
    if (!is_transparent(block)) {
        return self->calculate_darkness(15, 0);
    }
    
    /* only do special half-step handling if no authoratative pointer was
       passed in, which is a sign that we're recursing */
    if (block == 44 && authoratative == NULL) {
        float average_gather = 0.0f;
        unsigned int average_count = 0;
        int auth;
        float coeff;
        
        /* iterate through all surrounding blocks to take an average */
        int dx, dy, dz;
        for (dx = -1; dx <= 1; dx += 2) {
            for (dy = -1; dy <= 1; dy += 2) {
                for (dz = -1; dz <= 1; dz += 2) {
                    coeff = get_lighting_coefficient(self, state, x+dx, y+dy, z+dz, &auth);
                    if (auth) {
                        average_gather += coeff;
                        average_count++;
                    }
                }
            }
        }
        
        /* only return the average if at least one was authoratative */
        if (average_count > 0)
            return average_gather / average_count;
    }
    
    if (block == 10 || block == 11) {
        /* lava blocks should always be lit! */
        return 0.0f;
    }
    
    skylevel = getArrayByte3D(skylight, local_x, local_y, local_z);
    blocklevel = getArrayByte3D(blocklight, local_x, local_y, local_z);
    
    /* no longer a guess */
    if (authoratative)
        *authoratative = 1;
    
    return self->calculate_darkness(skylevel, blocklevel);
}