From 4f2e649ff82ad65d083bffb8d9e4100c83a6d09b Mon Sep 17 00:00:00 2001 From: David Vierra Date: Fri, 2 Jan 2015 11:43:55 -1000 Subject: [PATCH] Handle element rotation, texture rotation, variant rotation, correct texture alignment problems and shading values --- src/mcedit2/rendering/blockmodels.py | 265 +++++++++++++++++++++------ src/mceditlib/anvil/adapter.py | 29 +-- src/mceditlib/blocktypes/__init__.py | 1 + src/mceditlib/geometry.py | 8 +- 4 files changed, 230 insertions(+), 73 deletions(-) diff --git a/src/mcedit2/rendering/blockmodels.py b/src/mcedit2/rendering/blockmodels.py index 4f12df8..27fc7f5 100644 --- a/src/mcedit2/rendering/blockmodels.py +++ b/src/mcedit2/rendering/blockmodels.py @@ -4,9 +4,11 @@ from __future__ import absolute_import, division, print_function import json import logging +import math + import numpy from mceditlib import faces -from mceditlib.geometry import BoundingBox, Vector, FloatBox +from mceditlib.geometry import Vector, FloatBox log = logging.getLogger(__name__) @@ -56,6 +58,20 @@ class BlockModels(object): # model dict may also have optional keys 'x', 'y', 'z' with a value in degrees, to rotate the model # around that axis # another optional key is 'uvlock', which needs investigating + # variant dict for 'rail': + + # "variants": { + # "shape=north_south": { "model": "normal_rail_flat" }, + # "shape=east_west": { "model": "normal_rail_flat", "y": 90 }, + # "shape=ascending_east": { "model": "normal_rail_raised_ne", "y": 90 }, + # "shape=ascending_west": { "model": "normal_rail_raised_sw", "y": 90 }, + # "shape=ascending_north": { "model": "normal_rail_raised_ne" }, + # "shape=ascending_south": { "model": "normal_rail_raised_sw" }, + # "shape=south_east": { "model": "normal_rail_curved" }, + # "shape=south_west": { "model": "normal_rail_curved", "y": 90 }, + # "shape=north_west": { "model": "normal_rail_curved", "y": 180 }, + # "shape=north_east": { "model": "normal_rail_curved", "y": 270 } + # } variantBlockState = block.resourceVariant log.debug("Loading %s#%s for %s", block.resourcePath, block.resourceVariant, block) @@ -63,7 +79,14 @@ class BlockModels(object): if isinstance(variantDict, list): variantDict = variantDict[0] # do the random pick thing later, if at all modelName = variantDict['model'] - modelDict = self._getBlockModel("block/" + modelName) + try: + modelDict = self._getBlockModel("block/" + modelName) + except ValueError as e: + log.exception("Error parsing json for block/%s: %s", modelName, e) + continue + variantXrot = variantDict.get("x", 0) + variantYrot = variantDict.get("y", 0) + variantZrot = variantDict.get("z", 0) # model will either have an 'elements' key or a 'parent' key (maybe both). # 'parent' will be the name of a model @@ -106,7 +129,11 @@ class BlockModels(object): parentName = modelDict.get("parent") if parentName is None: break - modelDict = self._getBlockModel(parentName) + try: + modelDict = self._getBlockModel(parentName) + except ValueError as e: + log.exception("Error parsing json for block/%s: %s", parentName, e) + raise else: raise ValueError("Parent loop detected in block model %s" % modelName) @@ -121,17 +148,14 @@ class BlockModels(object): toPoint = Vector(*element["to"]) fromPoint /= 16. toPoint /= 16. - box = FloatBox(fromPoint, maximum=toPoint) + for face, info in element["faces"].iteritems(): + face = facesByCardinal[face] texture = info["texture"] + cullface = info.get("cullface") + uv = info.get("uv", [0, 0, 16, 16]) - textureRotation = info.get("rotation") - if textureRotation is not None: - textureRotation %= 360 - while textureRotation > 0: - uv = uv[3:] + uv[:3] - textureRotation -= 90 lastvar = texture @@ -149,15 +173,11 @@ class BlockModels(object): self.firstTextures.setdefault(name, texture) self._textureNames.add(texture) - allQuads.append((box, facesByCardinal[face], texture, uv, info.get("cullface"), shade)) + allQuads.append((box, face, + texture, uv, cullface, + shade, element.get("rotation"), info.get("rotation"), + variantXrot, variantYrot, variantZrot)) - rotation = element.get("rotation") - if rotation is not None: - origin = rotation["origin"] - axis = rotation["axis"] - angle = rotation["angle"] - rescale = rotation.get("rescale", False) - # construct rotation matrix, run quad vertices through it @@ -179,27 +199,147 @@ class BlockModels(object): cookedModels = {} for nameAndState, allQuads in self.modelQuads.iteritems(): cookedQuads = [] - for (box, face, texture, uv, cullface, shade) in allQuads: + for (box, face, texture, uv, cullface, shade, rotation, textureRotation, + variantXrot, variantYrot, variantZrot) in allQuads: + l, t, w, h = textureAtlas.texCoordsByName[texture] - u1, v1, u2, v2 = uv # xxxx (u2-u1) / w etc scale to texture width + u1, v1, u2, v2 = uv + uw = (u2 - u1) / 16 + vh = (v2 - v1) / 16 u1 += l - u2 += l - v1 += t - v2 += t + u2 = u1 + uw * w + + # flip v axis - texcoords origin is top left but model uv origin is from bottom left + v1 = t + h - v1 + v2 = v1 - vh * w + uv = (u1, v1, u2, v2) - xyzuvc = getBlockFaceVertices(box, face, uv) + xyzuvc = getBlockFaceVertices(box, face, uv, textureRotation) + xyzuvc.shape = 4, 6 + + if variantZrot: + face = rotateFace(face, 2, variantZrot) + if variantXrot: + face = rotateFace(face, 0, variantXrot) + if variantYrot: + face = rotateFace(face, 1, variantYrot) if cullface: cullface = facesByCardinal[cullface] + if variantZrot: + cullface = rotateFace(cullface, 2, variantZrot) + if variantXrot: + cullface = rotateFace(cullface, 0, variantXrot) + if variantYrot: + cullface = rotateFace(cullface, 1, variantYrot) + + + if rotation is not None: + origin = rotation["origin"] + axis = rotation["axis"] + angle = rotation["angle"] + rescale = rotation.get("rescale", False) + matrix = npRotate(axis, angle, rescale) + ox, oy, oz = origin + origin = ox/16., oy/16., oz/16. + + xyzuvc[:, :3] -= origin + xyz = xyzuvc[:, :3].transpose() + xyzuvc[:, :3] = (matrix[:3, :3] * xyz).transpose() + xyzuvc[:, :3] += origin + + rotate = variantXrot or variantYrot or variantZrot + if rotate: + matrix = numpy.matrix(numpy.identity(4)) + if variantYrot: + matrix *= npRotate("y", -variantYrot) + if variantXrot: + matrix *= npRotate("x", -variantXrot) + if variantZrot: + matrix *= npRotate("z", -variantZrot) + xyzuvc[:, :3] -= 0.5, 0.5, 0.5 + xyz = xyzuvc[:, :3].transpose() + xyzuvc[:, :3] = (matrix[:3, :3] * xyz).transpose() + xyzuvc[:, :3] += 0.5, 0.5, 0.5 + if shade: - xyzuvc.shape = 4, 6 xyzuvc.view('uint8')[:, 20:] = faceShades[face] + else: + xyzuvc.view('uint8')[:, 20:] = 0xff + + + cookedQuads.append((face, xyzuvc, cullface)) cookedModels[nameAndState] = cookedQuads self.cookedModels = cookedModels +faceRotations = ( + ( + faces.FaceYIncreasing, + faces.FaceZIncreasing, + faces.FaceYDecreasing, + faces.FaceZDecreasing, + ), + ( + faces.FaceXIncreasing, + faces.FaceZDecreasing, + faces.FaceXDecreasing, + faces.FaceZIncreasing, + ), + ( + faces.FaceXIncreasing, + faces.FaceYIncreasing, + faces.FaceXDecreasing, + faces.FaceYDecreasing, + ), + +) + +def rotateFace(face, axis, degrees): + rots = faceRotations[axis] + try: + idx = rots.index(face) + except ValueError: + return face + + while degrees > 0: + idx -= 1 + degrees -= 90 + idx %= 4 + return rots[idx] + + +def npRotate(axis, angle, rescale=False): + # ( xx(1-c)+c xy(1-c)-zs xz(1-c)+ys 0 ) + # | yx(1-c)+zs yy(1-c)+c yz(1-c)-xs 0 | + # | xz(1-c)-ys yz(1-c)+xs zz(1-c)+c 0 | + # ( 0 0 0 1 ) + # axis: + # "x": (1, 0, 0) + # "y": (0, 1, 0) + # "z": (0, 0, 1) + x = y = z = 0 + if axis == "x": + x = 1 + elif axis == "y": + y = 1 + elif axis == "z": + z = 1 + else: + raise ValueError("Unknown axis: %r" % axis) + + s = math.sin(math.radians(angle)) + c = math.cos(math.radians(angle)) + rotate = numpy.matrix([[x*x*(1-c)+c, x*y*(1-c)-z*s, x*z*(1-c)+y*s, 0], + [y*x*(1-c)+z*s, y*y*(1-c)+c, y*z*(1-c)-x*s, 0], + [x*z*(1-c)-y*s, y*z*(1-c)+x*s, z*z*(1-c)+c, 0], + [0, 0, 0, 1]]) + # xxx rescale + return rotate + + facesByCardinal = dict( north=faces.FaceNorth, south=faces.FaceSouth, @@ -211,68 +351,83 @@ facesByCardinal = dict( ) faceShades = { - faces.FaceNorth: 0xBB, - faces.FaceSouth: 0xBB, - faces.FaceEast: 0xDD, - faces.FaceWest: 0xDD, + faces.FaceNorth: 0x99, + faces.FaceSouth: 0x99, + faces.FaceEast: 0xCC, + faces.FaceWest: 0xCC, faces.FaceUp: 0xFF, faces.FaceDown: 0x77, } -# teutures = (u1, v1, u2, v1, u2, v2, u1, v2) -def getBlockFaceVertices(box, face, uv): +def getBlockFaceVertices(box, face, uv, textureRotation): x1, y1, z1, = box.origin x2, y2, z2 = box.maximum u1, v1, u2, v2 = uv + tc = [ + (u1, v1), + (u1, v2), + (u2, v2), + (u2, v1), + ] + if textureRotation: + while textureRotation > 0: + tc = tc[1:] + tc[:1] + textureRotation -= 90 + + tc = numpy.array(tc) + if face == faces.FaceXDecreasing: faceVertices = numpy.array( - (x1, y1, z1, u1, v1, 0.0, - x1, y2, z1, u1, v2, 0.0, - x1, y2, z2, u2, v2, 0.0, - x1, y1, z2, u2, v1, 0.0, + (x1, y2, z1, 0.0, 0.0, 0.0, + x1, y1, z1, 0.0, 0.0, 0.0, + x1, y1, z2, 0.0, 0.0, 0.0, + x1, y2, z2, 0.0, 0.0, 0.0, ), dtype='f4') elif face == faces.FaceXIncreasing: faceVertices = numpy.array( - (x2, y1, z1, u1, v1, 0.0, - x2, y2, z1, u1, v2, 0.0, - x2, y2, z2, u2, v2, 0.0, - x2, y1, z2, u2, v1, 0.0, + (x2, y2, z2, 0.0, 0.0, 0.0, + x2, y1, z2, 0.0, 0.0, 0.0, + x2, y1, z1, 0.0, 0.0, 0.0, + x2, y2, z1, 0.0, 0.0, 0.0, ), dtype='f4') elif face == faces.FaceYDecreasing: faceVertices = numpy.array( - (x2, y1, z2, u1, v1, 0.0, - x1, y1, z2, u1, v2, 0.0, - x1, y1, z1, u2, v2, 0.0, - x2, y1, z1, u2, v1, 0.0, + (x1, y1, z2, 0.0, 0.0, 0.0, + x1, y1, z1, 0.0, 0.0, 0.0, + x2, y1, z1, 0.0, 0.0, 0.0, + x2, y1, z2, 0.0, 0.0, 0.0, ), dtype='f4') elif face == faces.FaceYIncreasing: faceVertices = numpy.array( - (x2, y2, z1, u1, v1, 0.0, - x1, y2, z1, u1, v2, 0.0, - x1, y2, z2, u2, v2, 0.0, - x2, y2, z2, u2, v1, 0.0, + (x1, y2, z1, 0.0, 0.0, 0.0, + x1, y2, z2, 0.0, 0.0, 0.0, + x2, y2, z2, 0.0, 0.0, 0.0, + x2, y2, z1, 0.0, 0.0, 0.0, ), dtype='f4') elif face == faces.FaceZDecreasing: faceVertices = numpy.array( - (x1, y1, z1, u1, v1, 0.0, - x1, y2, z1, u1, v2, 0.0, - x2, y2, z1, u2, v2, 0.0, - x2, y1, z1, u2, v1, 0.0, + (x2, y2, z1, 0.0, 0.0, 0.0, + x2, y1, z1, 0.0, 0.0, 0.0, + x1, y1, z1, 0.0, 0.0, 0.0, + x1, y2, z1, 0.0, 0.0, 0.0, ), dtype='f4') elif face == faces.FaceZIncreasing: faceVertices = numpy.array( - (x2, y1, z2, u1, v1, 0.0, - x2, y2, z2, u1, v2, 0.0, - x1, y2, z2, u2, v2, 0.0, - x1, y1, z2, u2, v1, 0.0, + (x1, y2, z2, 0.0, 0.0, 0.0, + x1, y1, z2, 0.0, 0.0, 0.0, + x2, y1, z2, 0.0, 0.0, 0.0, + x2, y2, z2, 0.0, 0.0, 0.0, ), dtype='f4') else: raise ValueError("Unknown face %s" % face) + faceVertices.shape = 4, 6 + faceVertices[:, 3:5] = tc + return faceVertices diff --git a/src/mceditlib/anvil/adapter.py b/src/mceditlib/anvil/adapter.py index 76a6b34..1efe84c 100644 --- a/src/mceditlib/anvil/adapter.py +++ b/src/mceditlib/anvil/adapter.py @@ -82,20 +82,21 @@ def inflate(data): def sanitizeBlocks(section, blocktypes): - # change grass to dirt where needed so Minecraft doesn't flip out and die - grass = section.Blocks == blocktypes.Grass.ID - grass |= section.Blocks == blocktypes.Dirt.ID - badgrass = grass[1:, :, :] & grass[:-1, :, :] - - section.Blocks[:-1, :, :][badgrass] = blocktypes.Dirt.ID - - # remove any thin snow layers immediately above other thin snow layers. - # minecraft doesn't flip out, but it's almost never intended - if hasattr(blocktypes, "SnowLayer"): - snowlayer = section.Blocks == blocktypes.SnowLayer.ID - badsnow = snowlayer[:, :, 1:] & snowlayer[:, :, :-1] - - section.Blocks[:, :, 1:][badsnow] = 0 + return + # # change grass to dirt where needed so Minecraft doesn't flip out and die + # grass = section.Blocks == blocktypes.Grass.ID + # grass |= section.Blocks == blocktypes.Dirt.ID + # badgrass = grass[1:, :, :] & grass[:-1, :, :] + # + # section.Blocks[:-1, :, :][badgrass] = blocktypes.Dirt.ID + # + # # remove any thin snow layers immediately above other thin snow layers. + # # minecraft doesn't flip out, but it's almost never intended + # if hasattr(blocktypes, "SnowLayer"): + # snowlayer = section.Blocks == blocktypes.SnowLayer.ID + # badsnow = snowlayer[:, :, 1:] & snowlayer[:, :, :-1] + # + # section.Blocks[:, :, 1:][badsnow] = 0 # --- Sections and chunks --- diff --git a/src/mceditlib/blocktypes/__init__.py b/src/mceditlib/blocktypes/__init__.py index 098beab..6812b74 100644 --- a/src/mceditlib/blocktypes/__init__.py +++ b/src/mceditlib/blocktypes/__init__.py @@ -89,6 +89,7 @@ class BlockTypeSet(object): 'opacity': 15, 'brightness': 0, 'internalName': 'UNKNOWN_NAME', + 'blockState': '[UNKNOWN_STATE]', 'unlocalizedName': 'name.unknown', 'opaqueCube': True, 'renderType': -1, #xxx unknowns diff --git a/src/mceditlib/geometry.py b/src/mceditlib/geometry.py index 99966ac..1ae21fa 100644 --- a/src/mceditlib/geometry.py +++ b/src/mceditlib/geometry.py @@ -266,7 +266,7 @@ class BoundingBox(SelectionBox): self._size = Vector(*(self.type(a) for a in size)) def __repr__(self): - return "BoundingBox(origin={0}, size={1})".format(self.origin, self.size) + return "%s(origin={0}, size={1})".format(self.__class__.__name__, self.origin, self.size) def __iter__(self): return iter((self._origin, self._size)) @@ -375,7 +375,7 @@ class BoundingBox(SelectionBox): if any(s<=0 for s in size): return ZeroBox #print "Intersect of {0} and {1}: {2}".format(self, box, newbox) - return BoundingBox(origin, size) + return self.__class__(origin, size) def union(self, box): """ @@ -391,7 +391,7 @@ class BoundingBox(SelectionBox): max(self.maxy, box.maxy), max(self.maxz, box.maxz), ) - return BoundingBox(origin, maximum - origin) + return self.__class__(origin, maximum - origin) def expand(self, dx, dy=None, dz=None): """ @@ -406,7 +406,7 @@ class BoundingBox(SelectionBox): origin = self.origin - (dx, dy, dz) size = self.size + (dx * 2, dy * 2, dz * 2) - return BoundingBox(origin, size) + return self.__class__(origin, size) def __contains__(self, (x, y, z)): if x < self.minx or x >= self.maxx: