From c86698650df0c2385c7dfb0a061a501624d682ff Mon Sep 17 00:00:00 2001 From: Jer Wilson Date: Sun, 15 Sep 2013 22:22:17 -0700 Subject: [PATCH] Refactored stairs rendering to support corners --- overviewer_core/src/iterate.c | 133 +++++++++++++++++++++- overviewer_core/textures.py | 204 +++++++++++++++------------------- 2 files changed, 217 insertions(+), 120 deletions(-) diff --git a/overviewer_core/src/iterate.c b/overviewer_core/src/iterate.c index ee88f69..08cf069 100644 --- a/overviewer_core/src/iterate.c +++ b/overviewer_core/src/iterate.c @@ -244,6 +244,27 @@ check_adjacent_blocks(RenderState *state, int x,int y,int z, unsigned short bloc } +static int +is_stairs(int block) { + /* + * Determines if a block is stairs of any material + */ + switch (block) { + case 53: /* oak wood stairs */ + case 67: /* cobblestone stairs */ + case 108: /* brick stairs */ + case 109: /* stone brick stairs */ + case 114: /* nether brick stairs */ + case 128: /* sandstone stairs */ + case 134: /* spruce wood stairs */ + case 135: /* birch wood stairs */ + case 136: /* jungle wood stairs */ + case 156: /* quartz stairs */ + return 1; + } + return 0; +} + unsigned char generate_pseudo_data(RenderState *state, unsigned char ancilData) { /* @@ -416,11 +437,115 @@ generate_pseudo_data(RenderState *state, unsigned char ancilData) { pr = pr * pr * 42317861 + pr * 11; rotation = 3 & (pr >> 16); return rotation; + } else if (is_stairs(state->block)) { /* stairs */ + /* 4 ancillary bits will be added to indicate which quarters of the block contain the + * upper step. Regular stairs will have 2 bits set & corner stairs will have 1 or 3. + * Southwest quarter is part of the upper step - 0x40 + * / Southeast " - 0x20 + * |/ Northeast " - 0x10 + * ||/ Northwest " - 0x8 + * |||/ flip upside down (Minecraft) + * ||||/ has North/South alignment (Minecraft) + * |||||/ ascends North or West, not South or East (Minecraft) + * ||||||/ + * 0b0011011 = Stair ascending north, upside up, with both north quarters filled + */ + + /* keep track of whether neighbors are stairs, and their data */ + unsigned char stairs_base[8]; + unsigned char neigh_base[8]; + unsigned char *stairs = stairs_base; + unsigned char *neigh = neigh_base; + + /* amount to rotate/roll to get to east, west, south, north */ + size_t rotations[] = {0,2,3,1}; + + /* masks for the filled (ridge) stair quarters: */ + /* Example: the ridge for an east-ascending stair are the two east quarters */ + /* ascending: east west south north */ + unsigned char ridge_mask[] = { 0x30, 0x48, 0x60, 0x18 }; + + /* masks for the open (trench) stair quarters: */ + unsigned char trench_mask[] = { 0x48, 0x30, 0x18, 0x60 }; + + /* boat analogy! up the stairs is toward the bow of the boat */ + /* masks for port and starboard, i.e. left and right sides while ascending: */ + unsigned char port_mask[] = { 0x18, 0x60, 0x30, 0x48 }; + unsigned char starboard_mask[] = { 0x60, 0x18, 0x48, 0x30 }; + + /* we may need to lock some quarters into place depending on neighbors */ + unsigned char lock_mask = 0; + + unsigned char repair_rot[] = { 0, 1, 2, 3, 2, 3, 1, 0, 1, 0, 3, 2, 3, 2, 0, 1 }; + + /* need to get northdirection of the render */ + /* TODO: get this just once? store in state? */ + PyObject *texrot; + int northdir; + texrot = PyObject_GetAttrString(state->textures, "rotation"); + northdir = PyInt_AsLong(texrot); + + /* fix the rotation value for different northdirections */ + #define FIX_ROT(x) (((x) & ~0x3) | repair_rot[((x) & 0x3) | (northdir << 2)]) + ancilData = FIX_ROT(ancilData); + + /* fill the ancillary bits assuming normal stairs with no corner yet */ + ancilData |= ridge_mask[ancilData & 0x3]; + + /* get block & data for neighbors in this order: east, north, west, south */ + /* so we can rotate things easily */ + stairs[0] = stairs[4] = is_stairs(get_data(state, BLOCKS, x+1, y, z)); + stairs[1] = stairs[5] = is_stairs(get_data(state, BLOCKS, x, y, z-1)); + stairs[2] = stairs[6] = is_stairs(get_data(state, BLOCKS, x-1, y, z)); + stairs[3] = stairs[7] = is_stairs(get_data(state, BLOCKS, x, y, z+1)); + neigh[0] = neigh[4] = FIX_ROT(get_data(state, DATA, x+1, y, z)); + neigh[1] = neigh[5] = FIX_ROT(get_data(state, DATA, x, y, z-1)); + neigh[2] = neigh[6] = FIX_ROT(get_data(state, DATA, x-1, y, z)); + neigh[3] = neigh[7] = FIX_ROT(get_data(state, DATA, x, y, z+1)); + + #undef FIX_ROT + + /* Rotate the neighbors so we only have to worry about one orientation + * No matter which way the boat is facing, the the neighbors will be: + * 0: bow + * 1: port + * 2: stern + * 3: starboard */ + stairs += rotations[ancilData & 0x3]; + neigh += rotations[ancilData & 0x3]; + + /* Matching neighbor stairs to the sides should prevent cornering on that side */ + /* If found, set bits in lock_mask to lock the current quarters as they are */ + if (stairs[1] && (neigh[1] & 0x7) == (ancilData & 0x7)) { + /* Neighbor on port side is stairs of the same orientation as me */ + /* Do NOT allow changing quarters on the port side */ + lock_mask |= port_mask[ancilData & 0x3]; + } + if (stairs[3] && (neigh[3] & 0x7) == (ancilData & 0x7)) { + /* Neighbor on starboard side is stairs of the same orientation as me */ + /* Do NOT allow changing quarters on the starboard side */ + lock_mask |= starboard_mask[ancilData & 0x3]; + } + + /* Make corner stairs -- prefer outside corners like Minecraft */ + if (stairs[0] && (neigh[0] & 0x4) == (ancilData & 0x4)) { + /* neighbor at bow is stairs with same flip */ + if ((neigh[0] & 0x2) != (ancilData & 0x2)) { + /* neighbor is perpendicular, cut a trench, but not where locked */ + ancilData &= ~trench_mask[neigh[0] & 0x3] | lock_mask; + } + } else if (stairs[2] && (neigh[2] & 0x4) == (ancilData & 0x4)) { + /* neighbor at stern is stairs with same flip */ + if ((neigh[2] & 0x2) != (ancilData & 0x2)) { + /* neighbor is perpendicular, add a ridge, but not where locked */ + ancilData |= ridge_mask[neigh[2] & 0x3] & ~lock_mask; + } + } + + return ancilData; } - return 0; - } @@ -556,7 +681,7 @@ chunk_render(PyObject *self, PyObject *args) { state.block_data = ancilData; /* block that need pseudo ancildata: * grass, water, glass, chest, restone wire, - * ice, fence, portal, iron bars, glass panes */ + * ice, fence, portal, iron bars, glass panes, stairs */ if ((state.block == 2) || (state.block == 9) || (state.block == 20) || (state.block == 54) || (state.block == 55) || (state.block == 64) || @@ -564,7 +689,7 @@ chunk_render(PyObject *self, PyObject *args) { (state.block == 85) || (state.block == 90) || (state.block == 101) || (state.block == 102) || (state.block == 111) || (state.block == 113) || - (state.block == 139)) { + (state.block == 139) || is_stairs(state.block)) { ancilData = generate_pseudo_data(&state, ancilData); state.block_pdata = ancilData; } else { diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index 88a8999..44b3c5a 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -1622,143 +1622,115 @@ def fire(self, blockid, data): block(blockid=52, top_image="assets/minecraft/textures/blocks/mob_spawner.png", transparent=True) # wooden, cobblestone, red brick, stone brick, netherbrick, sandstone, spruce, birch, jungle and quartz stairs. -@material(blockid=[53,67,108,109,114,128,134,135,136,156], data=range(8), transparent=True, solid=True, nospawn=True) +@material(blockid=[53,67,108,109,114,128,134,135,136,156], data=range(128), transparent=True, solid=True, nospawn=True) def stairs(self, blockid, data): - - # first, rotations # preserve the upside-down bit upside_down = data & 0x4 - data = data & 0x3 - if self.rotation == 1: - if data == 0: data = 2 - elif data == 1: data = 3 - elif data == 2: data = 1 - elif data == 3: data = 0 - elif self.rotation == 2: - if data == 0: data = 1 - elif data == 1: data = 0 - elif data == 2: data = 3 - elif data == 3: data = 2 - elif self.rotation == 3: - if data == 0: data = 3 - elif data == 1: data = 2 - elif data == 2: data = 0 - elif data == 3: data = 1 - data = data | upside_down + + # find solid quarters within the top or bottom half of the block + # NW NE SE SW + quarters = [data & 0x8, data & 0x10, data & 0x20, data & 0x40] + + # rotate the quarters so we can pretend northdirection is always upper-left + numpy.roll(quarters, [0,1,3,2][self.rotation]) + nw,ne,se,sw = quarters if blockid == 53: # wooden - texture = self.load_image_texture("assets/minecraft/textures/blocks/planks_oak.png") + texture = self.load_image_texture("assets/minecraft/textures/blocks/planks_oak.png").copy() elif blockid == 67: # cobblestone - texture = self.load_image_texture("assets/minecraft/textures/blocks/cobblestone.png") + texture = self.load_image_texture("assets/minecraft/textures/blocks/cobblestone.png").copy() elif blockid == 108: # red brick stairs - texture = self.load_image_texture("assets/minecraft/textures/blocks/brick.png") + texture = self.load_image_texture("assets/minecraft/textures/blocks/brick.png").copy() elif blockid == 109: # stone brick stairs - texture = self.load_image_texture("assets/minecraft/textures/blocks/stonebrick.png") + texture = self.load_image_texture("assets/minecraft/textures/blocks/stonebrick.png").copy() elif blockid == 114: # netherbrick stairs - texture = self.load_image_texture("assets/minecraft/textures/blocks/nether_brick.png") + texture = self.load_image_texture("assets/minecraft/textures/blocks/nether_brick.png").copy() elif blockid == 128: # sandstone stairs - texture = self.load_image_texture("assets/minecraft/textures/blocks/sandstone_normal.png") + texture = self.load_image_texture("assets/minecraft/textures/blocks/sandstone_normal.png").copy() elif blockid == 134: # spruce wood stairs - texture = self.load_image_texture("assets/minecraft/textures/blocks/planks_spruce.png") + texture = self.load_image_texture("assets/minecraft/textures/blocks/planks_spruce.png").copy() elif blockid == 135: # birch wood stairs - texture = self.load_image_texture("assets/minecraft/textures/blocks/planks_birch.png") + texture = self.load_image_texture("assets/minecraft/textures/blocks/planks_birch.png").copy() elif blockid == 136: # jungle good stairs - texture = self.load_image_texture("assets/minecraft/textures/blocks/planks_jungle.png") + texture = self.load_image_texture("assets/minecraft/textures/blocks/planks_jungle.png").copy() elif blockid == 156: # quartz block stairs - texture = self.load_image_texture("assets/minecraft/textures/blocks/quartz_block_side.png") + texture = self.load_image_texture("assets/minecraft/textures/blocks/quartz_block_side.png").copy() + outside_l = texture.copy() + outside_r = texture.copy() + inside_l = texture.copy() + inside_r = texture.copy() - side = texture.copy() - half_block_u = texture.copy() # up, down, left, right - half_block_d = texture.copy() - half_block_l = texture.copy() - half_block_r = texture.copy() - - # sandstone stairs have spcial top texture + # sandstone & quartz stairs have special top texture if blockid == 128: - half_block_u = self.load_image_texture("assets/minecraft/textures/blocks/sandstone_top.png").copy() - half_block_d = self.load_image_texture("assets/minecraft/textures/blocks/sandstone_top.png").copy() texture = self.load_image_texture("assets/minecraft/textures/blocks/sandstone_top.png").copy() - elif blockid == 156: # also quartz stairs - half_block_u = self.load_image_texture("assets/minecraft/textures/blocks/quartz_block_top.png").copy() - half_block_d = self.load_image_texture("assets/minecraft/textures/blocks/quartz_block_top.png").copy() + elif blockid == 156: texture = self.load_image_texture("assets/minecraft/textures/blocks/quartz_block_top.png").copy() - # generate needed geometries - ImageDraw.Draw(side).rectangle((0,0,7,6),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(half_block_u).rectangle((0,8,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(half_block_d).rectangle((0,0,15,6),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(half_block_l).rectangle((8,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(half_block_r).rectangle((0,0,7,15),outline=(0,0,0,0),fill=(0,0,0,0)) - - if data & 0x4 == 0x4: # upside doen stair - side = side.transpose(Image.FLIP_TOP_BOTTOM) - if data & 0x3 == 0: # ascending east - img = Image.new("RGBA", (24,24), self.bgcolor) # first paste the texture in the back - tmp = self.transform_image_side(half_block_d) - alpha_over(img, tmp, (6,3)) - alpha_over(img, self.build_full_block(texture, None, None, half_block_u, side.transpose(Image.FLIP_LEFT_RIGHT))) + slab_top = texture.copy() + + push = 8 if upside_down else 0 + + def rect(tex,coords): + ImageDraw.Draw(tex).rectangle(coords,outline=(0,0,0,0),fill=(0,0,0,0)) + + # cut out top or bottom half from inner surfaces + rect(inside_l, (0,8-push,15,15-push)) + rect(inside_r, (0,8-push,15,15-push)) + + # cut out missing or obstructed quarters from each surface + if not nw: + rect(outside_l, (0,push,7,7+push)) + rect(texture, (0,0,7,7)) + if not nw or sw: + rect(inside_r, (8,push,15,7+push)) # will be flipped + if not ne: + rect(texture, (8,0,15,7)) + if not ne or nw: + rect(inside_l, (0,push,7,7+push)) + if not ne or se: + rect(inside_r, (0,push,7,7+push)) # will be flipped + if not se: + rect(outside_r, (0,push,7,7+push)) # will be flipped + rect(texture, (8,8,15,15)) + if not se or sw: + rect(inside_l, (8,push,15,7+push)) + if not sw: + rect(outside_l, (8,push,15,7+push)) + rect(outside_r, (8,push,15,7+push)) # will be flipped + rect(texture, (0,8,7,15)) + + img = Image.new("RGBA", (24,24), self.bgcolor) + + if upside_down: + # top should have no cut-outs after all + texture = slab_top + else: + # render the slab-level surface + slab_top = self.transform_image_top(slab_top) + alpha_over(img, slab_top, (0,6)) + + # render inner left surface + inside_l = self.transform_image_side(inside_l) + # Darken the vertical part of the second step + sidealpha = inside_l.split()[3] + # darken it a bit more than usual, looks better + inside_l = ImageEnhance.Brightness(inside_l).enhance(0.8) + inside_l.putalpha(sidealpha) + alpha_over(img, inside_l, (6,3)) + + # render inner right surface + inside_r = self.transform_image_side(inside_r).transpose(Image.FLIP_LEFT_RIGHT) + # Darken the vertical part of the second step + sidealpha = inside_r.split()[3] + # darken it a bit more than usual, looks better + inside_r = ImageEnhance.Brightness(inside_r).enhance(0.7) + inside_r.putalpha(sidealpha) + alpha_over(img, inside_r, (6,3)) + + # render outer surfaces + alpha_over(img, self.build_full_block(texture, None, None, outside_l, outside_r)) - elif data & 0x3 == 0x1: # ascending west - img = self.build_full_block(texture, None, None, texture, side) - - elif data & 0x3 == 0x2: # ascending south - img = self.build_full_block(texture, None, None, side, texture) - - elif data & 0x3 == 0x3: # ascending north - img = Image.new("RGBA", (24,24), self.bgcolor) # first paste the texture in the back - tmp = self.transform_image_side(half_block_d).transpose(Image.FLIP_LEFT_RIGHT) - alpha_over(img, tmp, (6,3)) - alpha_over(img, self.build_full_block(texture, None, None, side.transpose(Image.FLIP_LEFT_RIGHT), half_block_u)) - - else: # normal stair - if data == 0: # ascending east - img = self.build_full_block(half_block_r, None, None, half_block_d, side.transpose(Image.FLIP_LEFT_RIGHT)) - tmp1 = self.transform_image_side(half_block_u) - - # Darken the vertical part of the second step - sidealpha = tmp1.split()[3] - # darken it a bit more than usual, looks better - tmp1 = ImageEnhance.Brightness(tmp1).enhance(0.8) - tmp1.putalpha(sidealpha) - - alpha_over(img, tmp1, (6,4)) #workaround, fixes a hole - alpha_over(img, tmp1, (6,3)) - tmp2 = self.transform_image_top(half_block_l) - alpha_over(img, tmp2, (0,6)) - - elif data == 1: # ascending west - img = Image.new("RGBA", (24,24), self.bgcolor) # first paste the texture in the back - tmp1 = self.transform_image_top(half_block_r) - alpha_over(img, tmp1, (0,6)) - tmp2 = self.build_full_block(half_block_l, None, None, texture, side) - alpha_over(img, tmp2) - - elif data == 2: # ascending south - img = Image.new("RGBA", (24,24), self.bgcolor) # first paste the texture in the back - tmp1 = self.transform_image_top(half_block_u) - alpha_over(img, tmp1, (0,6)) - tmp2 = self.build_full_block(half_block_d, None, None, side, texture) - alpha_over(img, tmp2) - - elif data == 3: # ascending north - img = self.build_full_block(half_block_u, None, None, side.transpose(Image.FLIP_LEFT_RIGHT), half_block_d) - tmp1 = self.transform_image_side(half_block_u).transpose(Image.FLIP_LEFT_RIGHT) - - # Darken the vertical part of the second step - sidealpha = tmp1.split()[3] - # darken it a bit more than usual, looks better - tmp1 = ImageEnhance.Brightness(tmp1).enhance(0.7) - tmp1.putalpha(sidealpha) - - alpha_over(img, tmp1, (6,4)) #workaround, fixes a hole - alpha_over(img, tmp1, (6,3)) - tmp2 = self.transform_image_top(half_block_d) - alpha_over(img, tmp2, (0,6)) - - # touch up a (horrible) pixel - img.putpixel((18,3),(0,0,0,0)) - return img # normal, locked (used in april's fool day), ender and trapped chest