"""Copyright (c) 2010-2012 David Rio Vierra Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.""" """ renderer.py What is going on in this file? Here is an attempt to show the relationships between classes and their responsibilities MCRenderer: has "position", "origin", optionally "viewFrustum" Loads chunks near position+origin, draws chunks offset by origin Calls visible on viewFrustum to exclude chunks (+) ChunkRenderer Has "chunkPosition", "invalidLayers", "lists" One per chunk and detail level. Creates display lists from BlockRenderers (*) BlockRenderer Has "vertexArrays" One per block type, plus one for low detail and one for Entity """ from collections import defaultdict, deque from datetime import datetime, timedelta from depths import DepthOffset from glutils import gl, Texture import logging import numpy from OpenGL import GL import pymclevel import sys #import time def chunkMarkers(chunkSet): """ Returns a mapping { size: [position, ...] } for different powers of 2 as size. """ sizedChunks = defaultdict(list) size = 1 def all4(cx, cz): cx &= ~size cz &= ~size return [(cx, cz), (cx + size, cz), (cx + size, cz + size), (cx, cz + size)] # lastsize = 6 size = 1 while True: nextsize = size << 1 chunkSet = set(chunkSet) while len(chunkSet): cx, cz = chunkSet.pop() chunkSet.add((cx, cz)) o = all4(cx, cz) others = set(o).intersection(chunkSet) if len(others) == 4: sizedChunks[nextsize].append(o[0]) for c in others: chunkSet.discard(c) else: for c in others: sizedChunks[size].append(c) chunkSet.discard(c) if len(sizedChunks[nextsize]): chunkSet = set(sizedChunks[nextsize]) sizedChunks[nextsize] = [] size <<= 1 else: break return sizedChunks class ChunkRenderer(object): maxlod = 2 minlod = 0 def __init__(self, renderer, chunkPosition): self.renderer = renderer self.blockRenderers = [] self.detailLevel = 0 self.invalidLayers = set(Layer.AllLayers) self.chunkPosition = chunkPosition self.bufferSize = 0 self.renderstateLists = None @property def visibleLayers(self): return self.renderer.visibleLayers def forgetDisplayLists(self, states=None): if self.renderstateLists is not None: # print "Discarded {0}, gained {1} bytes".format(self.chunkPosition,self.bufferSize) for k in states or self.renderstateLists.iterkeys(): a = self.renderstateLists.get(k, []) # print a for i in a: gl.glDeleteLists(i, 1) if states: del self.renderstateLists[states] else: self.renderstateLists = None self.needsRedisplay = True self.renderer.discardMasterList() def debugDraw(self): for blockRenderer in self.blockRenderers: blockRenderer.drawArrays(self.chunkPosition, False) def makeDisplayLists(self): if not self.needsRedisplay: return self.forgetDisplayLists() if not self.blockRenderers: return lists = defaultdict(list) showRedraw = self.renderer.showRedraw if not (showRedraw and self.needsBlockRedraw): GL.glEnableClientState(GL.GL_COLOR_ARRAY) renderers = self.blockRenderers for blockRenderer in renderers: if self.detailLevel not in blockRenderer.detailLevels: continue if blockRenderer.layer not in self.visibleLayers: continue l = blockRenderer.makeArrayList(self.chunkPosition, self.needsBlockRedraw and showRedraw) lists[blockRenderer.renderstate].append(l) if not (showRedraw and self.needsBlockRedraw): GL.glDisableClientState(GL.GL_COLOR_ARRAY) self.needsRedisplay = False self.renderstateLists = lists @property def needsBlockRedraw(self): return Layer.Blocks in self.invalidLayers def invalidate(self, layers=None): if layers is None: layers = Layer.AllLayers if layers: layers = set(layers) self.invalidLayers.update(layers) blockRenderers = [br for br in self.blockRenderers if br.layer is Layer.Blocks or br.layer not in layers] if len(blockRenderers) < len(self.blockRenderers): self.forgetDisplayLists() self.blockRenderers = blockRenderers if self.renderer.showRedraw and Layer.Blocks in layers: self.needsRedisplay = True def calcFaces(self): minlod = self.renderer.detailLevelForChunk(self.chunkPosition) minlod = min(minlod, self.maxlod) if self.detailLevel != minlod: self.forgetDisplayLists() self.detailLevel = minlod self.invalidLayers.add(Layer.Blocks) # discard the standard detail renderers if minlod > 0: blockRenderers = [] for br in self.blockRenderers: if br.detailLevels != (0,): blockRenderers.append(br) self.blockRenderers = blockRenderers if self.renderer.chunkCalculator: for i in self.renderer.chunkCalculator.calcFacesForChunkRenderer(self): yield else: raise StopIteration yield def vertexArraysDone(self): bufferSize = 0 for br in self.blockRenderers: bufferSize += br.bufferSize() if self.renderer.alpha != 0xff: br.setAlpha(self.renderer.alpha) self.bufferSize = bufferSize self.invalidLayers = set() self.needsRedisplay = True self.renderer.invalidateMasterList() needsRedisplay = False @property def done(self): return len(self.invalidLayers) == 0 _XYZ = numpy.s_[..., 0:3] _ST = numpy.s_[..., 3:5] _XYZST = numpy.s_[..., :5] _RGBA = numpy.s_[..., 20:24] _RGB = numpy.s_[..., 20:23] _A = numpy.s_[..., 23] def makeVertexTemplates(xmin=0, ymin=0, zmin=0, xmax=1, ymax=1, zmax=1): return numpy.array([ # FaceXIncreasing: [[xmax, ymin, zmax, (zmin * 16), 16 - (ymin * 16), 0x0b], [xmax, ymin, zmin, (zmax * 16), 16 - (ymin * 16), 0x0b], [xmax, ymax, zmin, (zmax * 16), 16 - (ymax * 16), 0x0b], [xmax, ymax, zmax, (zmin * 16), 16 - (ymax * 16), 0x0b], ], # FaceXDecreasing: [[xmin, ymin, zmin, (zmin * 16), 16 - (ymin * 16), 0x0b], [xmin, ymin, zmax, (zmax * 16), 16 - (ymin * 16), 0x0b], [xmin, ymax, zmax, (zmax * 16), 16 - (ymax * 16), 0x0b], [xmin, ymax, zmin, (zmin * 16), 16 - (ymax * 16), 0x0b]], # FaceYIncreasing: [[xmin, ymax, zmin, xmin * 16, 16 - (zmax * 16), 0x11], # ne [xmin, ymax, zmax, xmin * 16, 16 - (zmin * 16), 0x11], # nw [xmax, ymax, zmax, xmax * 16, 16 - (zmin * 16), 0x11], # sw [xmax, ymax, zmin, xmax * 16, 16 - (zmax * 16), 0x11]], # se # FaceYDecreasing: [[xmin, ymin, zmin, xmin * 16, 16 - (zmax * 16), 0x08], [xmax, ymin, zmin, xmax * 16, 16 - (zmax * 16), 0x08], [xmax, ymin, zmax, xmax * 16, 16 - (zmin * 16), 0x08], [xmin, ymin, zmax, xmin * 16, 16 - (zmin * 16), 0x08]], # FaceZIncreasing: [[xmin, ymin, zmax, xmin * 16, 16 - (ymin * 16), 0x0d], [xmax, ymin, zmax, xmax * 16, 16 - (ymin * 16), 0x0d], [xmax, ymax, zmax, xmax * 16, 16 - (ymax * 16), 0x0d], [xmin, ymax, zmax, xmin * 16, 16 - (ymax * 16), 0x0d]], # FaceZDecreasing: [[xmax, ymin, zmin, xmin * 16, 16 - (ymin * 16), 0x0d], [xmin, ymin, zmin, xmax * 16, 16 - (ymin * 16), 0x0d], [xmin, ymax, zmin, xmax * 16, 16 - (ymax * 16), 0x0d], [xmax, ymax, zmin, xmin * 16, 16 - (ymax * 16), 0x0d], ], ]) elementByteLength = 24 def createPrecomputedVertices(): height = 16 precomputedVertices = [numpy.zeros(shape=(16, 16, height, 4, 6), # x,y,z,s,t,rg, ba dtype='float32') for d in faceVertexTemplates] xArray = numpy.arange(16)[:, numpy.newaxis, numpy.newaxis, numpy.newaxis] zArray = numpy.arange(16)[numpy.newaxis, :, numpy.newaxis, numpy.newaxis] yArray = numpy.arange(height)[numpy.newaxis, numpy.newaxis, :, numpy.newaxis] for dir in range(len(faceVertexTemplates)): precomputedVertices[dir][_XYZ][..., 0] = xArray precomputedVertices[dir][_XYZ][..., 1] = yArray precomputedVertices[dir][_XYZ][..., 2] = zArray precomputedVertices[dir][_XYZ] += faceVertexTemplates[dir][..., 0:3] # xyz precomputedVertices[dir][_ST] = faceVertexTemplates[dir][..., 3:5] # s precomputedVertices[dir].view('uint8')[_RGB] = faceVertexTemplates[dir][..., 5, numpy.newaxis] precomputedVertices[dir].view('uint8')[_A] = 0xff return precomputedVertices faceVertexTemplates = makeVertexTemplates() class ChunkCalculator (object): cachedTemplate = None cachedTemplateHeight = 0 whiteLight = numpy.array([[[15] * 16] * 16] * 16, numpy.uint8) precomputedVertices = createPrecomputedVertices() def __init__(self, level): self.makeRenderstates(level.materials) # del xArray, zArray, yArray self.nullVertices = numpy.zeros((0,) * len(self.precomputedVertices[0].shape), dtype=self.precomputedVertices[0].dtype) from leveleditor import Settings Settings.fastLeaves.addObserver(self) Settings.roughGraphics.addObserver(self) class renderstatePlain(object): @classmethod def bind(self): pass @classmethod def release(self): pass class renderstateLowDetail(object): @classmethod def bind(self): GL.glDisable(GL.GL_CULL_FACE) GL.glDisable(GL.GL_TEXTURE_2D) @classmethod def release(self): GL.glEnable(GL.GL_CULL_FACE) GL.glEnable(GL.GL_TEXTURE_2D) class renderstateAlphaTest(object): @classmethod def bind(self): GL.glEnable(GL.GL_ALPHA_TEST) @classmethod def release(self): GL.glDisable(GL.GL_ALPHA_TEST) class _renderstateAlphaBlend(object): @classmethod def bind(self): GL.glEnable(GL.GL_BLEND) @classmethod def release(self): GL.glDisable(GL.GL_BLEND) class renderstateWater(_renderstateAlphaBlend): pass class renderstateIce(_renderstateAlphaBlend): pass class renderstateEntity(object): @classmethod def bind(self): GL.glDisable(GL.GL_DEPTH_TEST) # GL.glDisable(GL.GL_CULL_FACE) GL.glDisable(GL.GL_TEXTURE_2D) GL.glEnable(GL.GL_BLEND) @classmethod def release(self): GL.glEnable(GL.GL_DEPTH_TEST) # GL.glEnable(GL.GL_CULL_FACE) GL.glEnable(GL.GL_TEXTURE_2D) GL.glDisable(GL.GL_BLEND) renderstates = ( renderstatePlain, renderstateLowDetail, renderstateAlphaTest, renderstateIce, renderstateWater, renderstateEntity, ) def makeRenderstates(self, materials): self.blockRendererClasses = [ GenericBlockRenderer, LeafBlockRenderer, PlantBlockRenderer, TorchBlockRenderer, WaterBlockRenderer, SlabBlockRenderer, ] if materials.name in ("Alpha", "Pocket"): self.blockRendererClasses += [ RailBlockRenderer, LadderBlockRenderer, SnowBlockRenderer, RedstoneBlockRenderer, IceBlockRenderer, FeatureBlockRenderer, StairBlockRenderer, # button, floor plate, door -> 1-cube features # lever, sign, wall sign, stairs -> 2-cube features # repeater # fence # bed # cake # portal ] self.materialMap = materialMap = numpy.zeros((256,), 'uint8') materialMap[1:] = 1 # generic blocks materialCount = 2 for br in self.blockRendererClasses[1:]: # skip generic blocks materialMap[br.getBlocktypes(materials)] = materialCount br.materialIndex = materialCount materialCount += 1 self.exposedMaterialMap = numpy.array(materialMap) self.addTransparentMaterials(self.exposedMaterialMap, materialCount) def addTransparentMaterials(self, mats, materialCount): transparentMaterials = [ pymclevel.materials.alphaMaterials.Glass, pymclevel.materials.alphaMaterials.GlassPane, pymclevel.materials.alphaMaterials.IronBars, pymclevel.materials.alphaMaterials.MonsterSpawner, pymclevel.materials.alphaMaterials.Vines, pymclevel.materials.alphaMaterials.Fire, ] for b in transparentMaterials: mats[b.ID] = materialCount materialCount += 1 hiddenOreMaterials = numpy.arange(256, dtype='uint8') hiddenOreMaterials[2] = 1 # don't show boundaries between dirt,grass,sand,gravel,stone hiddenOreMaterials[3] = 1 hiddenOreMaterials[12] = 1 hiddenOreMaterials[13] = 1 roughMaterials = numpy.ones((256,), dtype='uint8') roughMaterials[0] = 0 addTransparentMaterials(None, roughMaterials, 2) def calcFacesForChunkRenderer(self, cr): if 0 == len(cr.invalidLayers): # layers = set(br.layer for br in cr.blockRenderers) # assert set() == cr.visibleLayers.difference(layers) return lod = cr.detailLevel cx, cz = cr.chunkPosition level = cr.renderer.level try: chunk = level.getChunk(cx, cz) except Exception, e: logging.warn(u"Error reading chunk: %s", e) yield return yield brs = [] classes = [ TileEntityRenderer, MonsterRenderer, ItemRenderer, TileTicksRenderer, TerrainPopulatedRenderer, LowDetailBlockRenderer, OverheadBlockRenderer, ] existingBlockRenderers = dict(((type(b), b) for b in cr.blockRenderers)) for blockRendererClass in classes: if cr.detailLevel not in blockRendererClass.detailLevels: continue if blockRendererClass.layer not in cr.visibleLayers: continue if blockRendererClass.layer not in cr.invalidLayers: if blockRendererClass in existingBlockRenderers: brs.append(existingBlockRenderers[blockRendererClass]) continue br = blockRendererClass(self) br.detailLevel = cr.detailLevel for _ in br.makeChunkVertices(chunk): yield brs.append(br) blockRenderers = [] # Recalculate high detail blocks if needed, otherwise retain the high detail renderers if lod == 0 and Layer.Blocks in cr.invalidLayers: for _ in self.calcHighDetailFaces(cr, blockRenderers): yield else: blockRenderers.extend(br for br in cr.blockRenderers if type(br) not in classes) # Add the layer renderers blockRenderers.extend(brs) cr.blockRenderers = blockRenderers cr.vertexArraysDone() raise StopIteration def getNeighboringChunks(self, chunk): cx, cz = chunk.chunkPosition level = chunk.world neighboringChunks = {} for dir, dx, dz in ((pymclevel.faces.FaceXDecreasing, -1, 0), (pymclevel.faces.FaceXIncreasing, 1, 0), (pymclevel.faces.FaceZDecreasing, 0, -1), (pymclevel.faces.FaceZIncreasing, 0, 1)): if not level.containsChunk(cx + dx, cz + dz): neighboringChunks[dir] = pymclevel.infiniteworld.ZeroChunk(level.Height) else: # if not level.chunkIsLoaded(cx+dx,cz+dz): # raise StopIteration try: neighboringChunks[dir] = level.getChunk(cx + dx, cz + dz) except (pymclevel.mclevelbase.ChunkNotPresent, pymclevel.mclevelbase.ChunkMalformed): neighboringChunks[dir] = pymclevel.infiniteworld.ZeroChunk(level.Height) return neighboringChunks def getAreaBlocks(self, chunk, neighboringChunks): chunkWidth, chunkLength, chunkHeight = chunk.Blocks.shape areaBlocks = numpy.zeros((chunkWidth + 2, chunkLength + 2, chunkHeight + 2), numpy.uint8) areaBlocks[1:-1, 1:-1, 1:-1] = chunk.Blocks areaBlocks[:1, 1:-1, 1:-1] = neighboringChunks[pymclevel.faces.FaceXDecreasing].Blocks[-1:, :chunkLength, :chunkHeight] areaBlocks[-1:, 1:-1, 1:-1] = neighboringChunks[pymclevel.faces.FaceXIncreasing].Blocks[:1, :chunkLength, :chunkHeight] areaBlocks[1:-1, :1, 1:-1] = neighboringChunks[pymclevel.faces.FaceZDecreasing].Blocks[:chunkWidth, -1:, :chunkHeight] areaBlocks[1:-1, -1:, 1:-1] = neighboringChunks[pymclevel.faces.FaceZIncreasing].Blocks[:chunkWidth, :1, :chunkHeight] return areaBlocks def getFacingBlockIndices(self, areaBlocks, areaBlockMats): facingBlockIndices = [None] * 6 exposedFacesX = (areaBlockMats[:-1, 1:-1, 1:-1] != areaBlockMats[1:, 1:-1, 1:-1]) facingBlockIndices[pymclevel.faces.FaceXDecreasing] = exposedFacesX[:-1] facingBlockIndices[pymclevel.faces.FaceXIncreasing] = exposedFacesX[1:] exposedFacesZ = (areaBlockMats[1:-1, :-1, 1:-1] != areaBlockMats[1:-1, 1:, 1:-1]) facingBlockIndices[pymclevel.faces.FaceZDecreasing] = exposedFacesZ[:, :-1] facingBlockIndices[pymclevel.faces.FaceZIncreasing] = exposedFacesZ[:, 1:] exposedFacesY = (areaBlockMats[1:-1, 1:-1, :-1] != areaBlockMats[1:-1, 1:-1, 1:]) facingBlockIndices[pymclevel.faces.FaceYDecreasing] = exposedFacesY[:, :, :-1] facingBlockIndices[pymclevel.faces.FaceYIncreasing] = exposedFacesY[:, :, 1:] return facingBlockIndices def getAreaBlockLights(self, chunk, neighboringChunks): chunkWidth, chunkLength, chunkHeight = chunk.Blocks.shape lights = chunk.BlockLight skyLight = chunk.SkyLight finalLight = self.whiteLight if lights != None: finalLight = lights if skyLight != None: finalLight = numpy.maximum(skyLight, lights) areaBlockLights = numpy.ones((chunkWidth + 2, chunkLength + 2, chunkHeight + 2), numpy.uint8) areaBlockLights[:] = 15 areaBlockLights[1:-1, 1:-1, 1:-1] = finalLight nc = neighboringChunks[pymclevel.faces.FaceXDecreasing] numpy.maximum(nc.SkyLight[-1:, :chunkLength, :chunkHeight], nc.BlockLight[-1:, :chunkLength, :chunkHeight], areaBlockLights[0:1, 1:-1, 1:-1]) nc = neighboringChunks[pymclevel.faces.FaceXIncreasing] numpy.maximum(nc.SkyLight[:1, :chunkLength, :chunkHeight], nc.BlockLight[:1, :chunkLength, :chunkHeight], areaBlockLights[-1:, 1:-1, 1:-1]) nc = neighboringChunks[pymclevel.faces.FaceZDecreasing] numpy.maximum(nc.SkyLight[:chunkWidth, -1:, :chunkHeight], nc.BlockLight[:chunkWidth, -1:, :chunkHeight], areaBlockLights[1:-1, 0:1, 1:-1]) nc = neighboringChunks[pymclevel.faces.FaceZIncreasing] numpy.maximum(nc.SkyLight[:chunkWidth, :1, :chunkHeight], nc.BlockLight[:chunkWidth, :1, :chunkHeight], areaBlockLights[1:-1, -1:, 1:-1]) minimumLight = 4 # areaBlockLights[areaBlockLights= len(materialCounts) or materialCounts[mi] == 0: continue blockRenderer = blockRendererClass(self) blockRenderer.y = y blockRenderer.materials = materials for _ in blockRenderer.makeVertices(facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): yield blockRenderers.append(blockRenderer) yield def makeTemplate(self, direction, blockIndices): return self.precomputedVertices[direction][blockIndices] class Layer: Blocks = "Blocks" Entities = "Entities" Monsters = "Monsters" Items = "Items" TileEntities = "TileEntities" TileTicks = "TileTicks" TerrainPopulated = "TerrainPopulated" AllLayers = (Blocks, Entities, Monsters, Items, TileEntities, TileTicks, TerrainPopulated) class BlockRenderer(object): # vertexArrays = None detailLevels = (0,) layer = Layer.Blocks directionOffsets = { pymclevel.faces.FaceXDecreasing: numpy.s_[:-2, 1:-1, 1:-1], pymclevel.faces.FaceXIncreasing: numpy.s_[2:, 1:-1, 1:-1], pymclevel.faces.FaceYDecreasing: numpy.s_[1:-1, 1:-1, :-2], pymclevel.faces.FaceYIncreasing: numpy.s_[1:-1, 1:-1, 2:], pymclevel.faces.FaceZDecreasing: numpy.s_[1:-1, :-2, 1:-1], pymclevel.faces.FaceZIncreasing: numpy.s_[1:-1, 2:, 1:-1], } renderstate = ChunkCalculator.renderstateAlphaTest def __init__(self, cc): self.makeTemplate = cc.makeTemplate self.chunkCalculator = cc self.vertexArrays = [] pass @classmethod def getBlocktypes(cls, mats): return cls.blocktypes def setAlpha(self, alpha): "alpha is an unsigned byte value" for a in self.vertexArrays: a.view('uint8')[_RGBA][..., 3] = alpha def bufferSize(self): return sum(a.size for a in self.vertexArrays) * 4 def getMaterialIndices(self, blockMaterials): return blockMaterials == self.materialIndex def makeVertices(self, facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): arrays = [] materialIndices = self.getMaterialIndices(blockMaterials) yield blockLight = areaBlockLights[1:-1, 1:-1, 1:-1] for (direction, exposedFaceIndices) in enumerate(facingBlockIndices): facingBlockLight = areaBlockLights[self.directionOffsets[direction]] vertexArray = self.makeFaceVertices(direction, materialIndices, exposedFaceIndices, blocks, blockData, blockLight, facingBlockLight, texMap) yield if len(vertexArray): arrays.append(vertexArray) self.vertexArrays = arrays def makeArrayList(self, chunkPosition, showRedraw): l = gl.glGenLists(1) GL.glNewList(l, GL.GL_COMPILE) self.drawArrays(chunkPosition, showRedraw) GL.glEndList() return l def drawArrays(self, chunkPosition, showRedraw): cx, cz = chunkPosition y = 0 if hasattr(self, 'y'): y = self.y with gl.glPushMatrix(GL.GL_MODELVIEW): GL.glTranslate(cx << 4, y, cz << 4) if showRedraw: GL.glColor(1.0, 0.25, 0.25, 1.0) self.drawVertices() def drawVertices(self): if self.vertexArrays: for buf in self.vertexArrays: self.drawFaceVertices(buf) def drawFaceVertices(self, buf): if 0 == len(buf): return stride = elementByteLength GL.glVertexPointer(3, GL.GL_FLOAT, stride, (buf.ravel())) GL.glTexCoordPointer(2, GL.GL_FLOAT, stride, (buf.ravel()[3:])) GL.glColorPointer(4, GL.GL_UNSIGNED_BYTE, stride, (buf.view(dtype=numpy.uint8).ravel()[20:])) GL.glDrawArrays(GL.GL_QUADS, 0, len(buf) * 4) class EntityRendererGeneric(BlockRenderer): renderstate = ChunkCalculator.renderstateEntity detailLevels = (0, 1, 2) def drawFaceVertices(self, buf): if 0 == len(buf): return stride = elementByteLength GL.glVertexPointer(3, GL.GL_FLOAT, stride, (buf.ravel())) GL.glTexCoordPointer(2, GL.GL_FLOAT, stride, (buf.ravel()[3:])) GL.glColorPointer(4, GL.GL_UNSIGNED_BYTE, stride, (buf.view(dtype=numpy.uint8).ravel()[20:])) GL.glDepthMask(False) GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE) GL.glLineWidth(2.0) GL.glDrawArrays(GL.GL_QUADS, 0, len(buf) * 4) GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL) GL.glPolygonOffset(DepthOffset.TerrainWire, DepthOffset.TerrainWire) with gl.glEnable(GL.GL_POLYGON_OFFSET_FILL, GL.GL_DEPTH_TEST): GL.glDrawArrays(GL.GL_QUADS, 0, len(buf) * 4) GL.glDepthMask(True) def _computeVertices(self, positions, colors, offset=False, chunkPosition=(0, 0)): cx, cz = chunkPosition x = cx << 4 z = cz << 4 vertexArray = numpy.zeros(shape=(len(positions), 6, 4, 6), dtype='float32') if len(positions): positions = numpy.array(positions) positions[:, (0, 2)] -= (x, z) if offset: positions -= 0.5 vertexArray.view('uint8')[_RGBA] = colors vertexArray[_XYZ] = positions[:, numpy.newaxis, numpy.newaxis, :] vertexArray[_XYZ] += faceVertexTemplates[_XYZ] vertexArray.shape = (len(positions) * 6, 4, 6) return vertexArray class TileEntityRenderer(EntityRendererGeneric): layer = Layer.TileEntities def makeChunkVertices(self, chunk): tilePositions = [] for i, ent in enumerate(chunk.TileEntities): if i % 10 == 0: yield if not 'x' in ent: continue tilePositions.append(pymclevel.TileEntity.pos(ent)) tiles = self._computeVertices(tilePositions, (0xff, 0xff, 0x33, 0x44), chunkPosition=chunk.chunkPosition) yield self.vertexArrays = [tiles] class BaseEntityRenderer(EntityRendererGeneric): pass class MonsterRenderer(BaseEntityRenderer): layer = Layer.Entities # xxx Monsters notMonsters = set(["Item", "XPOrb", "Painting"]) def makeChunkVertices(self, chunk): monsterPositions = [] for i, ent in enumerate(chunk.Entities): if i % 10 == 0: yield id = ent["id"].value if id in self.notMonsters: continue monsterPositions.append(pymclevel.Entity.pos(ent)) monsters = self._computeVertices(monsterPositions, (0xff, 0x22, 0x22, 0x44), offset=True, chunkPosition=chunk.chunkPosition) yield self.vertexArrays = [monsters] class EntityRenderer(BaseEntityRenderer): def makeChunkVertices(self, chunk): yield # entityPositions = [] # for i, ent in enumerate(chunk.Entities): # if i % 10 == 0: # yield # entityPositions.append(pymclevel.Entity.pos(ent)) # # entities = self._computeVertices(entityPositions, (0x88, 0x00, 0x00, 0x66), offset=True, chunkPosition=chunk.chunkPosition) # yield # self.vertexArrays = [entities] class ItemRenderer(BaseEntityRenderer): layer = Layer.Items def makeChunkVertices(self, chunk): entityPositions = [] entityColors = [] colorMap = { "Item": (0x22, 0xff, 0x22, 0x5f), "XPOrb": (0x88, 0xff, 0x88, 0x5f), "Painting": (134, 96, 67, 0x5f), } for i, ent in enumerate(chunk.Entities): if i % 10 == 0: yield color = colorMap.get(ent["id"].value) if color is None: continue entityPositions.append(pymclevel.Entity.pos(ent)) entityColors.append(color) entities = self._computeVertices(entityPositions, numpy.array(entityColors, dtype='uint8')[:, numpy.newaxis, numpy.newaxis], offset=True, chunkPosition=chunk.chunkPosition) yield self.vertexArrays = [entities] class TileTicksRenderer(EntityRendererGeneric): layer = Layer.TileTicks def makeChunkVertices(self, chunk): if "Level" in chunk.root_tag and "TileTicks" in chunk.root_tag["Level"]: ticks = chunk.root_tag["Level"]["TileTicks"] if len(ticks): self.vertexArrays.append(self._computeVertices([[t[i].value for i in "xyz"] for t in ticks], (0xff, 0xff, 0xff, 0x44), chunkPosition=chunk.chunkPosition)) yield class TerrainPopulatedRenderer(EntityRendererGeneric): layer = Layer.TerrainPopulated vertexTemplate = numpy.zeros((6, 4, 6), 'float32') vertexTemplate[_XYZ] = faceVertexTemplates[_XYZ] vertexTemplate[_XYZ] *= (16, 128, 16) color = (255, 200, 155) vertexTemplate.view('uint8')[_RGBA] = color + (72,) def drawFaceVertices(self, buf): if 0 == len(buf): return stride = elementByteLength GL.glVertexPointer(3, GL.GL_FLOAT, stride, (buf.ravel())) GL.glTexCoordPointer(2, GL.GL_FLOAT, stride, (buf.ravel()[3:])) GL.glColorPointer(4, GL.GL_UNSIGNED_BYTE, stride, (buf.view(dtype=numpy.uint8).ravel()[20:])) GL.glDepthMask(False) # GL.glDrawArrays(GL.GL_QUADS, 0, len(buf) * 4) GL.glDisable(GL.GL_CULL_FACE) with gl.glEnable(GL.GL_DEPTH_TEST): GL.glDrawArrays(GL.GL_QUADS, 0, len(buf) * 4) GL.glEnable(GL.GL_CULL_FACE) GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE) GL.glLineWidth(1.0) GL.glDrawArrays(GL.GL_QUADS, 0, len(buf) * 4) GL.glLineWidth(2.0) with gl.glEnable(GL.GL_DEPTH_TEST): GL.glDrawArrays(GL.GL_QUADS, 0, len(buf) * 4) GL.glLineWidth(1.0) GL.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL) GL.glDepthMask(True) # GL.glPolygonOffset(DepthOffset.TerrainWire, DepthOffset.TerrainWire) # with gl.glEnable(GL.GL_POLYGON_OFFSET_FILL, GL.GL_DEPTH_TEST): # GL.glDrawArrays(GL.GL_QUADS, 0, len(buf) * 4) # def makeChunkVertices(self, chunk): neighbors = self.chunkCalculator.getNeighboringChunks(chunk) def getpop(ch): return getattr(ch, "TerrainPopulated", True) pop = getpop(chunk) yield if pop: return visibleFaces = [ getpop(neighbors[pymclevel.faces.FaceXIncreasing]), getpop(neighbors[pymclevel.faces.FaceXDecreasing]), True, True, getpop(neighbors[pymclevel.faces.FaceZIncreasing]), getpop(neighbors[pymclevel.faces.FaceZDecreasing]), ] visibleFaces = numpy.array(visibleFaces, dtype='bool') verts = self.vertexTemplate[visibleFaces] self.vertexArrays.append(verts) yield class LowDetailBlockRenderer(BlockRenderer): renderstate = ChunkCalculator.renderstateLowDetail detailLevels = (1,) def drawFaceVertices(self, buf): if not len(buf): return stride = 16 GL.glVertexPointer(3, GL.GL_FLOAT, stride, numpy.ravel(buf.ravel())) GL.glColorPointer(4, GL.GL_UNSIGNED_BYTE, stride, (buf.view(dtype='uint8').ravel()[12:])) GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY) GL.glDrawArrays(GL.GL_QUADS, 0, len(buf) * 4) GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) def makeChunkVertices(self, ch): step = 1 level = ch.world vertexArrays = [] blocks = ch.Blocks heightMap = ch.HeightMap heightMap = heightMap[::step, ::step] blocks = blocks[::step, ::step] if 0 in blocks.shape: return chunkWidth, chunkLength, chunkHeight = blocks.shape blockIndices = numpy.zeros((chunkWidth, chunkLength, chunkHeight), bool) gridaxes = list(numpy.indices((chunkWidth, chunkLength))) h = numpy.swapaxes(heightMap - 1, 0, 1)[:chunkWidth, :chunkLength] numpy.clip(h, 0, chunkHeight - 1, out=h) gridaxes = [gridaxes[0], gridaxes[1], h] depths = numpy.zeros((chunkWidth, chunkLength), dtype='uint16') depths[1:-1, 1:-1] = reduce(numpy.minimum, (h[1:-1, :-2], h[1:-1, 2:], h[:-2, 1:-1]), h[2:, 1:-1]) yield try: topBlocks = blocks[gridaxes] nonAirBlocks = (topBlocks != 0) blockIndices[gridaxes] = nonAirBlocks h += 1 numpy.clip(h, 0, chunkHeight - 1, out=h) overblocks = blocks[gridaxes][nonAirBlocks].ravel() except ValueError, e: raise ValueError(str(e.args) + "Chunk shape: {0}".format(blockIndices.shape), sys.exc_info()[-1]) if nonAirBlocks.any(): blockTypes = blocks[blockIndices] flatcolors = level.materials.flatColors[blockTypes, ch.Data[blockIndices] & 0xf][:, numpy.newaxis, :] # flatcolors[:,:,:3] *= (0.6 + (h * (0.4 / float(chunkHeight-1)))) [topBlocks != 0][:, numpy.newaxis, numpy.newaxis] x, z, y = blockIndices.nonzero() yield vertexArray = numpy.zeros((len(x), 4, 4), dtype='float32') vertexArray[_XYZ][..., 0] = x[:, numpy.newaxis] vertexArray[_XYZ][..., 1] = y[:, numpy.newaxis] vertexArray[_XYZ][..., 2] = z[:, numpy.newaxis] va0 = numpy.array(vertexArray) va0[..., :3] += faceVertexTemplates[pymclevel.faces.FaceYIncreasing, ..., :3] overmask = overblocks > 0 flatcolors[overmask] = level.materials.flatColors[:, 0][overblocks[overmask]][:, numpy.newaxis] if self.detailLevel == 2: heightfactor = (y / float(2.0 * ch.world.Height)) + 0.5 flatcolors[..., :3] *= heightfactor[:, numpy.newaxis, numpy.newaxis] _RGBA = numpy.s_[..., 12:16] va0.view('uint8')[_RGBA] = flatcolors va0[_XYZ][:, :, 0] *= step va0[_XYZ][:, :, 2] *= step yield if self.detailLevel == 2: self.vertexArrays = [va0] return va1 = numpy.array(vertexArray) va1[..., :3] += faceVertexTemplates[pymclevel.faces.FaceXIncreasing, ..., :3] va1[_XYZ][:, (0, 1), 1] = depths[nonAirBlocks].ravel()[:, numpy.newaxis] # stretch to floor va1[_XYZ][:, (1, 2), 0] -= 1.0 # turn diagonally va1[_XYZ][:, (2, 3), 1] -= 0.5 # drop down to prevent intersection pixels va1[_XYZ][:, :, 0] *= step va1[_XYZ][:, :, 2] *= step flatcolors *= 0.8 va1.view('uint8')[_RGBA] = flatcolors grassmask = topBlocks[nonAirBlocks] == 2 # color grass sides with dirt's color va1.view('uint8')[_RGBA][grassmask] = level.materials.flatColors[:, 0][[3]][:, numpy.newaxis] va2 = numpy.array(va1) va2[_XYZ][:, (1, 2), 0] += step va2[_XYZ][:, (0, 3), 0] -= step vertexArrays = [va1, va2, va0] self.vertexArrays = vertexArrays class OverheadBlockRenderer(LowDetailBlockRenderer): detailLevels = (2,) class GenericBlockRenderer(BlockRenderer): renderstate = ChunkCalculator.renderstateAlphaTest materialIndex = 1 def makeGenericVertices(self, facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): vertexArrays = [] materialIndices = self.getMaterialIndices(blockMaterials) yield for (direction, exposedFaceIndices) in enumerate(facingBlockIndices): facingBlockLight = areaBlockLights[self.directionOffsets[direction]] blockIndices = materialIndices & exposedFaceIndices theseBlocks = blocks[blockIndices] bdata = blockData[blockIndices] vertexArray = self.makeTemplate(direction, blockIndices) if not len(vertexArray): continue def setTexture(): vertexArray[_ST] += texMap(theseBlocks, bdata, direction)[:, numpy.newaxis, 0:2] setTexture() def setGrassColors(): grass = theseBlocks == pymclevel.materials.alphaMaterials.Grass.ID vertexArray.view('uint8')[_RGB][grass] *= self.grassColor def getBlockLight(): return facingBlockLight[blockIndices] def setColors(): vertexArray.view('uint8')[_RGB] *= getBlockLight()[..., numpy.newaxis, numpy.newaxis] if self.materials.name in ("Alpha", "Pocket"): if direction == pymclevel.faces.FaceYIncreasing: setGrassColors() # leaves = theseBlocks == pymclevel.materials.alphaMaterials.Leaves.ID # vertexArray.view('uint8')[_RGBA][leaves] *= [0.15, 0.88, 0.15, 1.0] # snow = theseBlocks == pymclevel.materials.alphaMaterials.SnowLayer.ID # if direction == pymclevel.faces.FaceYIncreasing: # vertexArray[_XYZ][snow, ...,1] -= 0.875 # # if direction != pymclevel.faces.FaceYIncreasing and direction != pymclevel.faces.FaceYDecreasing: # vertexArray[_XYZ][snow, ...,2:4,1] -= 0.875 # vertexArray[_ST][snow, ...,2:4,1] += 14 # setColors() yield vertexArrays.append(vertexArray) self.vertexArrays = vertexArrays grassColor = grassColorDefault = [0.39, 0.77, 0.23] # 62C743 makeVertices = makeGenericVertices class LeafBlockRenderer(BlockRenderer): blocktypes = [18] @property def renderstate(self): if self.chunkCalculator.fastLeaves: return ChunkCalculator.renderstatePlain else: return ChunkCalculator.renderstateAlphaTest def makeLeafVertices(self, facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): arrays = [] materialIndices = self.getMaterialIndices(blockMaterials) yield if self.materials.name in ("Alpha", "Pocket"): if not self.chunkCalculator.fastLeaves: blockIndices = materialIndices data = blockData[blockIndices] data &= 0x3 # ignore decay states leaves = (data == 0) | (data == 3) pines = (data == pymclevel.materials.alphaMaterials.PineLeaves.blockData) birches = (data == pymclevel.materials.alphaMaterials.BirchLeaves.blockData) texes = texMap(18, data, 0) else: blockIndices = materialIndices texes = texMap(18, [0], 0) for (direction, exposedFaceIndices) in enumerate(facingBlockIndices): if self.materials.name in ("Alpha", "Pocket"): if self.chunkCalculator.fastLeaves: blockIndices = materialIndices & exposedFaceIndices data = blockData[blockIndices] data &= 0x3 # ignore decay states leaves = (data == 0) pines = (data == pymclevel.materials.alphaMaterials.PineLeaves.blockData) birches = (data == pymclevel.materials.alphaMaterials.BirchLeaves.blockData) type3 = (data == 3) leaves |= type3 texes = texMap(18, data, 0) facingBlockLight = areaBlockLights[self.directionOffsets[direction]] vertexArray = self.makeTemplate(direction, blockIndices) if not len(vertexArray): continue vertexArray[_ST] += texes[:, numpy.newaxis] if not self.chunkCalculator.fastLeaves: vertexArray[_ST] -= (0x10, 0x0) vertexArray.view('uint8')[_RGB] *= facingBlockLight[blockIndices][..., numpy.newaxis, numpy.newaxis] if self.materials.name in ("Alpha", "Pocket"): vertexArray.view('uint8')[_RGB][leaves] *= self.leafColor vertexArray.view('uint8')[_RGB][pines] *= self.pineLeafColor vertexArray.view('uint8')[_RGB][birches] *= self.birchLeafColor yield arrays.append(vertexArray) self.vertexArrays = arrays leafColor = leafColorDefault = [0x48 / 255., 0xb5 / 255., 0x18 / 255.] # 48b518 pineLeafColor = pineLeafColorDefault = [0x61 / 255., 0x99 / 255., 0x61 / 255.] # 0x619961 birchLeafColor = birchLeafColorDefault = [0x80 / 255., 0xa7 / 255., 0x55 / 255.] # 0x80a755 makeVertices = makeLeafVertices class PlantBlockRenderer(BlockRenderer): @classmethod def getBlocktypes(cls, mats): # blocktypes = [6, 37, 38, 39, 40, 59, 83] # if mats.name != "Classic": blocktypes += [31, 32] # shrubs, tall grass # if mats.name == "Alpha": blocktypes += [115] # nether wart blocktypes = [b.ID for b in mats if b.type in ("DECORATION_CROSS", "NETHER_WART", "CROPS", "STEM")] return blocktypes renderstate = ChunkCalculator.renderstateAlphaTest def makePlantVertices(self, facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): arrays = [] blockIndices = self.getMaterialIndices(blockMaterials) yield theseBlocks = blocks[blockIndices] bdata = blockData[blockIndices] bdata[theseBlocks == 6] &= 0x3 # xxx saplings only texes = texMap(blocks[blockIndices], bdata, 0) blockLight = areaBlockLights[1:-1, 1:-1, 1:-1] lights = blockLight[blockIndices][..., numpy.newaxis, numpy.newaxis] colorize = None if self.materials.name == "Alpha": colorize = (theseBlocks == pymclevel.materials.alphaMaterials.TallGrass.ID) & (bdata != 0) for direction in (pymclevel.faces.FaceXIncreasing, pymclevel.faces.FaceXDecreasing, pymclevel.faces.FaceZIncreasing, pymclevel.faces.FaceZDecreasing): vertexArray = self.makeTemplate(direction, blockIndices) if not len(vertexArray): return if direction == pymclevel.faces.FaceXIncreasing: vertexArray[_XYZ][..., 1:3, 0] -= 1 if direction == pymclevel.faces.FaceXDecreasing: vertexArray[_XYZ][..., 1:3, 0] += 1 if direction == pymclevel.faces.FaceZIncreasing: vertexArray[_XYZ][..., 1:3, 2] -= 1 if direction == pymclevel.faces.FaceZDecreasing: vertexArray[_XYZ][..., 1:3, 2] += 1 vertexArray[_ST] += texes[:, numpy.newaxis, 0:2] vertexArray.view('uint8')[_RGBA] = 0xf # ignore precomputed directional light vertexArray.view('uint8')[_RGB] *= lights if colorize is not None: vertexArray.view('uint8')[_RGB][colorize] *= LeafBlockRenderer.leafColor arrays.append(vertexArray) yield self.vertexArrays = arrays makeVertices = makePlantVertices class TorchBlockRenderer(BlockRenderer): blocktypes = [50, 75, 76] renderstate = ChunkCalculator.renderstateAlphaTest torchOffsetsStraight = [ [ # FaceXIncreasing (-7 / 16., 0, 0), (-7 / 16., 0, 0), (-7 / 16., 0, 0), (-7 / 16., 0, 0), ], [ # FaceXDecreasing (7 / 16., 0, 0), (7 / 16., 0, 0), (7 / 16., 0, 0), (7 / 16., 0, 0), ], [ # FaceYIncreasing (7 / 16., -6 / 16., 7 / 16.), (7 / 16., -6 / 16., -7 / 16.), (-7 / 16., -6 / 16., -7 / 16.), (-7 / 16., -6 / 16., 7 / 16.), ], [ # FaceYDecreasing (7 / 16., 0., 7 / 16.), (-7 / 16., 0., 7 / 16.), (-7 / 16., 0., -7 / 16.), (7 / 16., 0., -7 / 16.), ], [ # FaceZIncreasing (0, 0, -7 / 16.), (0, 0, -7 / 16.), (0, 0, -7 / 16.), (0, 0, -7 / 16.) ], [ # FaceZDecreasing (0, 0, 7 / 16.), (0, 0, 7 / 16.), (0, 0, 7 / 16.), (0, 0, 7 / 16.) ], ] torchOffsetsSouth = [ [ # FaceXIncreasing (-7 / 16., 3 / 16., 0), (-7 / 16., 3 / 16., 0), (-7 / 16., 3 / 16., 0), (-7 / 16., 3 / 16., 0), ], [ # FaceXDecreasing (7 / 16., 3 / 16., 0), (7 / 16., 3 / 16., 0), (7 / 16., 3 / 16., 0), (7 / 16., 3 / 16., 0), ], [ # FaceYIncreasing (7 / 16., -3 / 16., 7 / 16.), (7 / 16., -3 / 16., -7 / 16.), (-7 / 16., -3 / 16., -7 / 16.), (-7 / 16., -3 / 16., 7 / 16.), ], [ # FaceYDecreasing (7 / 16., 3 / 16., 7 / 16.), (-7 / 16., 3 / 16., 7 / 16.), (-7 / 16., 3 / 16., -7 / 16.), (7 / 16., 3 / 16., -7 / 16.), ], [ # FaceZIncreasing (0, 3 / 16., -7 / 16.), (0, 3 / 16., -7 / 16.), (0, 3 / 16., -7 / 16.), (0, 3 / 16., -7 / 16.) ], [ # FaceZDecreasing (0, 3 / 16., 7 / 16.), (0, 3 / 16., 7 / 16.), (0, 3 / 16., 7 / 16.), (0, 3 / 16., 7 / 16.), ], ] torchOffsetsNorth = torchOffsetsWest = torchOffsetsEast = torchOffsetsSouth torchOffsets = [ torchOffsetsStraight, torchOffsetsSouth, torchOffsetsNorth, torchOffsetsWest, torchOffsetsEast, torchOffsetsStraight, ] + [torchOffsetsStraight] * 10 torchOffsets = numpy.array(torchOffsets, dtype='float32') torchOffsets[1][..., 3, :, 0] -= 0.5 torchOffsets[1][..., 0:2, 0:2, 0] -= 0.5 torchOffsets[1][..., 4:6, 0:2, 0] -= 0.5 torchOffsets[1][..., 0:2, 2:4, 0] -= 0.1 torchOffsets[1][..., 4:6, 2:4, 0] -= 0.1 torchOffsets[1][..., 2, :, 0] -= 0.25 torchOffsets[2][..., 3, :, 0] += 0.5 torchOffsets[2][..., 0:2, 0:2, 0] += 0.5 torchOffsets[2][..., 4:6, 0:2, 0] += 0.5 torchOffsets[2][..., 0:2, 2:4, 0] += 0.1 torchOffsets[2][..., 4:6, 2:4, 0] += 0.1 torchOffsets[2][..., 2, :, 0] += 0.25 torchOffsets[3][..., 3, :, 2] -= 0.5 torchOffsets[3][..., 0:2, 0:2, 2] -= 0.5 torchOffsets[3][..., 4:6, 0:2, 2] -= 0.5 torchOffsets[3][..., 0:2, 2:4, 2] -= 0.1 torchOffsets[3][..., 4:6, 2:4, 2] -= 0.1 torchOffsets[3][..., 2, :, 2] -= 0.25 torchOffsets[4][..., 3, :, 2] += 0.5 torchOffsets[4][..., 0:2, 0:2, 2] += 0.5 torchOffsets[4][..., 4:6, 0:2, 2] += 0.5 torchOffsets[4][..., 0:2, 2:4, 2] += 0.1 torchOffsets[4][..., 4:6, 2:4, 2] += 0.1 torchOffsets[4][..., 2, :, 2] += 0.25 upCoords = ((7, 6), (7, 8), (9, 8), (9, 6)) downCoords = ((7, 14), (7, 16), (9, 16), (9, 14)) def makeTorchVertices(self, facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): blockIndices = self.getMaterialIndices(blockMaterials) torchOffsets = self.torchOffsets[blockData[blockIndices]] texes = texMap(blocks[blockIndices], blockData[blockIndices]) yield arrays = [] for direction in range(6): vertexArray = self.makeTemplate(direction, blockIndices) if not len(vertexArray): return vertexArray.view('uint8')[_RGBA] = 0xff vertexArray[_XYZ] += torchOffsets[:, direction] if direction == pymclevel.faces.FaceYIncreasing: vertexArray[_ST] = self.upCoords if direction == pymclevel.faces.FaceYDecreasing: vertexArray[_ST] = self.downCoords vertexArray[_ST] += texes[:, numpy.newaxis, direction] arrays.append(vertexArray) yield self.vertexArrays = arrays makeVertices = makeTorchVertices class RailBlockRenderer(BlockRenderer): blocktypes = [pymclevel.materials.alphaMaterials.Rail.ID, pymclevel.materials.alphaMaterials.PoweredRail.ID, pymclevel.materials.alphaMaterials.DetectorRail.ID] renderstate = ChunkCalculator.renderstateAlphaTest railTextures = numpy.array([ [(0, 128), (0, 144), (16, 144), (16, 128)], # east-west [(0, 128), (16, 128), (16, 144), (0, 144)], # north-south [(0, 128), (16, 128), (16, 144), (0, 144)], # south-ascending [(0, 128), (16, 128), (16, 144), (0, 144)], # north-ascending [(0, 128), (0, 144), (16, 144), (16, 128)], # east-ascending [(0, 128), (0, 144), (16, 144), (16, 128)], # west-ascending [(0, 112), (0, 128), (16, 128), (16, 112)], # northeast corner [(0, 128), (16, 128), (16, 112), (0, 112)], # southeast corner [(16, 128), (16, 112), (0, 112), (0, 128)], # southwest corner [(16, 112), (0, 112), (0, 128), (16, 128)], # northwest corner [(0, 192), (0, 208), (16, 208), (16, 192)], # unknown [(0, 192), (0, 208), (16, 208), (16, 192)], # unknown [(0, 192), (0, 208), (16, 208), (16, 192)], # unknown [(0, 192), (0, 208), (16, 208), (16, 192)], # unknown [(0, 192), (0, 208), (16, 208), (16, 192)], # unknown [(0, 192), (0, 208), (16, 208), (16, 192)], # unknown ], dtype='float32') railTextures -= pymclevel.materials.alphaMaterials.blockTextures[pymclevel.materials.alphaMaterials.Rail.ID, 0, 0] railOffsets = numpy.array([ [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 1], # south-ascending [1, 1, 0, 0], # north-ascending [1, 0, 0, 1], # east-ascending [0, 1, 1, 0], # west-ascending [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], ], dtype='float32') def makeRailVertices(self, facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): direction = pymclevel.faces.FaceYIncreasing blockIndices = self.getMaterialIndices(blockMaterials) yield bdata = blockData[blockIndices] railBlocks = blocks[blockIndices] tex = texMap(railBlocks, bdata, pymclevel.faces.FaceYIncreasing)[:, numpy.newaxis, :] # disable 'powered' or 'pressed' bit for powered and detector rails bdata[railBlocks != pymclevel.materials.alphaMaterials.Rail.ID] &= ~0x8 vertexArray = self.makeTemplate(direction, blockIndices) if not len(vertexArray): return vertexArray[_ST] = self.railTextures[bdata] vertexArray[_ST] += tex vertexArray[_XYZ][..., 1] -= 0.9 vertexArray[_XYZ][..., 1] += self.railOffsets[bdata] blockLight = areaBlockLights[1:-1, 1:-1, 1:-1] vertexArray.view('uint8')[_RGB] *= blockLight[blockIndices][..., numpy.newaxis, numpy.newaxis] yield self.vertexArrays = [vertexArray] makeVertices = makeRailVertices class LadderBlockRenderer(BlockRenderer): blocktypes = [pymclevel.materials.alphaMaterials.Ladder.ID] ladderOffsets = numpy.array([ [(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0)], [(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0)], [(0, -1, 0.9), (0, 0, -0.1), (0, 0, -0.1), (0, -1, 0.9)], # facing east [(0, 0, 0.1), (0, -1, -.9), (0, -1, -.9), (0, 0, 0.1)], # facing west [(.9, -1, 0), (.9, -1, 0), (-.1, 0, 0), (-.1, 0, 0)], # north [(0.1, 0, 0), (0.1, 0, 0), (-.9, -1, 0), (-.9, -1, 0)], # south ] + [[(0, 0, 0), (0, 0, 0), (0, 0, 0), (0, 0, 0)]] * 10, dtype='float32') ladderTextures = numpy.array([ [(0, 192), (0, 208), (16, 208), (16, 192)], # unknown [(0, 192), (0, 208), (16, 208), (16, 192)], # unknown [(64, 96), (64, 80), (48, 80), (48, 96), ], # e [(48, 80), (48, 96), (64, 96), (64, 80), ], # w [(48, 96), (64, 96), (64, 80), (48, 80), ], # n [(64, 80), (48, 80), (48, 96), (64, 96), ], # s ] + [[(0, 192), (0, 208), (16, 208), (16, 192)]] * 10, dtype='float32') def ladderVertices(self, facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): blockIndices = self.getMaterialIndices(blockMaterials) blockLight = areaBlockLights[1:-1, 1:-1, 1:-1] yield bdata = blockData[blockIndices] vertexArray = self.makeTemplate(pymclevel.faces.FaceYIncreasing, blockIndices) if not len(vertexArray): return vertexArray[_ST] = self.ladderTextures[bdata] vertexArray[_XYZ] += self.ladderOffsets[bdata] vertexArray.view('uint8')[_RGB] *= blockLight[blockIndices][..., numpy.newaxis, numpy.newaxis] yield self.vertexArrays = [vertexArray] makeVertices = ladderVertices class SnowBlockRenderer(BlockRenderer): snowID = 78 blocktypes = [snowID] def makeSnowVertices(self, facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): snowIndices = self.getMaterialIndices(blockMaterials) arrays = [] yield for direction, exposedFaceIndices in enumerate(facingBlockIndices): # def makeFaceVertices(self, direction, blockIndices, exposedFaceIndices, blocks, blockData, blockLight, facingBlockLight, texMap): # return [] if direction != pymclevel.faces.FaceYIncreasing: blockIndices = snowIndices & exposedFaceIndices else: blockIndices = snowIndices facingBlockLight = areaBlockLights[self.directionOffsets[direction]] lights = facingBlockLight[blockIndices][..., numpy.newaxis, numpy.newaxis] vertexArray = self.makeTemplate(direction, blockIndices) if not len(vertexArray): continue vertexArray[_ST] += texMap([self.snowID], 0, 0)[:, numpy.newaxis, 0:2] vertexArray.view('uint8')[_RGB] *= lights if direction == pymclevel.faces.FaceYIncreasing: vertexArray[_XYZ][..., 1] -= 0.875 if direction != pymclevel.faces.FaceYIncreasing and direction != pymclevel.faces.FaceYDecreasing: vertexArray[_XYZ][..., 2:4, 1] -= 0.875 vertexArray[_ST][..., 2:4, 1] += 14 arrays.append(vertexArray) yield self.vertexArrays = arrays makeVertices = makeSnowVertices class RedstoneBlockRenderer(BlockRenderer): blocktypes = [55] def redstoneVertices(self, facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): blockIndices = self.getMaterialIndices(blockMaterials) yield vertexArray = self.makeTemplate(pymclevel.faces.FaceYIncreasing, blockIndices) if not len(vertexArray): return vertexArray[_ST] += pymclevel.materials.alphaMaterials.blockTextures[55, 0, 0] vertexArray[_XYZ][..., 1] -= 0.9 bdata = blockData[blockIndices] bdata <<= 3 # bdata &= 0xe0 bdata[bdata > 0] |= 0x80 vertexArray.view('uint8')[_RGBA][..., 0] = bdata[..., numpy.newaxis] vertexArray.view('uint8')[_RGBA][..., 0:3] *= [1, 0, 0] yield self.vertexArrays = [vertexArray] makeVertices = redstoneVertices # button, floor plate, door -> 1-cube features class FeatureBlockRenderer(BlockRenderer): # blocktypes = [pymclevel.materials.alphaMaterials.Button.ID, # pymclevel.materials.alphaMaterials.StoneFloorPlate.ID, # pymclevel.materials.alphaMaterials.WoodFloorPlate.ID, # pymclevel.materials.alphaMaterials.WoodenDoor.ID, # pymclevel.materials.alphaMaterials.IronDoor.ID, # ] # blocktypes = [pymclevel.materials.alphaMaterials.Fence.ID] buttonOffsets = [ [[-14 / 16., 6 / 16., -5 / 16.], [-14 / 16., 6 / 16., 5 / 16.], [-14 / 16., -7 / 16., 5 / 16.], [-14 / 16., -7 / 16., -5 / 16.], ], [[0 / 16., 6 / 16., 5 / 16.], [0 / 16., 6 / 16., -5 / 16.], [0 / 16., -7 / 16., -5 / 16.], [0 / 16., -7 / 16., 5 / 16.], ], [[0 / 16., -7 / 16., 5 / 16.], [0 / 16., -7 / 16., -5 / 16.], [-14 / 16., -7 / 16., -5 / 16.], [-14 / 16., -7 / 16., 5 / 16.], ], [[0 / 16., 6 / 16., 5 / 16.], [-14 / 16., 6 / 16., 5 / 16.], [-14 / 16., 6 / 16., -5 / 16.], [0 / 16., 6 / 16., -5 / 16.], ], [[0 / 16., 6 / 16., -5 / 16.], [-14 / 16., 6 / 16., -5 / 16.], [-14 / 16., -7 / 16., -5 / 16.], [0 / 16., -7 / 16., -5 / 16.], ], [[-14 / 16., 6 / 16., 5 / 16.], [0 / 16., 6 / 16., 5 / 16.], [0 / 16., -7 / 16., 5 / 16.], [-14 / 16., -7 / 16., 5 / 16.], ], ] buttonOffsets = numpy.array(buttonOffsets) buttonOffsets[buttonOffsets < 0] += 1.0 dirIndexes = ((3, 2), (-3, 2), (1, 3), (1, 3), (-1, 2), (1, 2)) def buttonVertices(self, facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): blockIndices = blocks == pymclevel.materials.alphaMaterials.Button.ID axes = blockIndices.nonzero() vertexArray = numpy.zeros((len(axes[0]), 6, 4, 6), dtype=numpy.float32) vertexArray[_XYZ][..., 0] = axes[0][..., numpy.newaxis, numpy.newaxis] vertexArray[_XYZ][..., 1] = axes[2][..., numpy.newaxis, numpy.newaxis] vertexArray[_XYZ][..., 2] = axes[1][..., numpy.newaxis, numpy.newaxis] vertexArray[_XYZ] += self.buttonOffsets vertexArray[_ST] = [[0, 0], [0, 16], [16, 16], [16, 0]] vertexArray[_ST] += texMap(pymclevel.materials.alphaMaterials.Stone.ID, 0)[numpy.newaxis, :, numpy.newaxis] # if direction == 0: # for i, j in enumerate(self.dirIndexes[direction]): # if j < 0: # j = -j # j -= 1 # offs = self.buttonOffsets[direction, ..., j] * 16 # offs = 16 - offs # # else: # j -= 1 # offs =self.buttonOffsets[direction, ..., j] * 16 # # # if i == 1: # # # # vertexArray[_ST][...,i] -= offs # # else: # vertexArray[_ST][...,i] -= offs # vertexArray.view('uint8')[_RGB] = 255 vertexArray.shape = (len(axes[0]) * 6, 4, 6) self.vertexArrays = [vertexArray] fenceTemplates = makeVertexTemplates(3 / 8., 0, 3 / 8., 5 / 8., 1, 5 / 8.) def fenceVertices(self, facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): fenceMask = blocks == pymclevel.materials.alphaMaterials.Fence.ID fenceIndices = fenceMask.nonzero() yield vertexArray = numpy.zeros((len(fenceIndices[0]), 6, 4, 6), dtype='float32') for i in range(3): j = (0, 2, 1)[i] vertexArray[..., i] = fenceIndices[j][:, numpy.newaxis, numpy.newaxis] # xxx swap z with y using ^ vertexArray[..., 0:5] += self.fenceTemplates[..., 0:5] vertexArray[_ST] += pymclevel.materials.alphaMaterials.blockTextures[pymclevel.materials.alphaMaterials.WoodPlanks.ID, 0, 0] vertexArray.view('uint8')[_RGBA] = self.fenceTemplates[..., 5][..., numpy.newaxis] vertexArray.view('uint8')[_RGB] *= areaBlockLights[1:-1, 1:-1, 1:-1][fenceIndices][..., numpy.newaxis, numpy.newaxis, numpy.newaxis] vertexArray.shape = (vertexArray.shape[0] * 6, 4, 6) yield self.vertexArrays = [vertexArray] makeVertices = fenceVertices class StairBlockRenderer(BlockRenderer): @classmethod def getBlocktypes(cls, mats): return [a.ID for a in mats.AllStairs] # South - FaceXIncreasing # North - FaceXDecreasing # West - FaceZIncreasing # East - FaceZDecreasing stairTemplates = numpy.array([makeVertexTemplates(**kw) for kw in [ # South - FaceXIncreasing {"xmin":0.5}, # North - FaceXDecreasing {"xmax":0.5}, # West - FaceZIncreasing {"zmin":0.5}, # East - FaceZDecreasing {"zmax":0.5}, # Slabtype {"ymax":0.5}, ] ]) def stairVertices(self, facingBlockIndices, blocks, blockMaterials, blockData, areaBlockLights, texMap): arrays = [] materialIndices = self.getMaterialIndices(blockMaterials) yield stairBlocks = blocks[materialIndices] stairData = blockData[materialIndices] & 0x3 blockLight = areaBlockLights[1:-1, 1:-1, 1:-1] x, z, y = materialIndices.nonzero() for _ in ("slab", "step"): vertexArray = numpy.zeros((len(x), 6, 4, 6), dtype='float32') for i in range(3): vertexArray[_XYZ][..., i] = (x, y, z)[i][:, numpy.newaxis, numpy.newaxis] if _ == "step": vertexArray[_XYZST] += self.stairTemplates[4][..., :5] else: vertexArray[_XYZST] += self.stairTemplates[stairData][..., :5] vertexArray[_ST] += texMap(stairBlocks, 0)[..., numpy.newaxis, :] vertexArray.view('uint8')[_RGB] = self.stairTemplates[4][numpy.newaxis, ..., 5, numpy.newaxis] vertexArray.view('uint8')[_RGB] *= 0xf vertexArray.view('uint8')[_A] = 0xff vertexArray.shape = (len(x) * 6, 4, 6) yield arrays.append(vertexArray) self.vertexArrays = arrays makeVertices = stairVertices class SlabBlockRenderer(BlockRenderer): slabID = 44 blocktypes = [slabID] def slabFaceVertices(self, direction, blockIndices, exposedFaceIndices, blocks, blockData, blockLight, facingBlockLight, texMap): if direction != pymclevel.faces.FaceYIncreasing: blockIndices = blockIndices & exposedFaceIndices lights = facingBlockLight[blockIndices][..., numpy.newaxis, numpy.newaxis] bdata = blockData[blockIndices] vertexArray = self.makeTemplate(direction, blockIndices) if not len(vertexArray): return vertexArray vertexArray[_ST] += texMap(self.slabID, bdata, direction)[:, numpy.newaxis, 0:2] vertexArray.view('uint8')[_RGB] *= lights if direction == pymclevel.faces.FaceYIncreasing: vertexArray[_XYZ][..., 1] -= 0.5 if direction != pymclevel.faces.FaceYIncreasing and direction != pymclevel.faces.FaceYDecreasing: vertexArray[_XYZ][..., 2:4, 1] -= 0.5 vertexArray[_ST][..., 2:4, 1] += 8 return vertexArray makeFaceVertices = slabFaceVertices class WaterBlockRenderer(BlockRenderer): waterID = 9 blocktypes = [8, waterID] renderstate = ChunkCalculator.renderstateWater def waterFaceVertices(self, direction, blockIndices, exposedFaceIndices, blocks, blockData, blockLight, facingBlockLight, texMap): blockIndices = blockIndices & exposedFaceIndices vertexArray = self.makeTemplate(direction, blockIndices) vertexArray[_ST] += texMap(self.waterID, 0, 0)[numpy.newaxis, numpy.newaxis] vertexArray.view('uint8')[_RGB] *= facingBlockLight[blockIndices][..., numpy.newaxis, numpy.newaxis] return vertexArray makeFaceVertices = waterFaceVertices class IceBlockRenderer(BlockRenderer): iceID = 79 blocktypes = [iceID] renderstate = ChunkCalculator.renderstateIce def iceFaceVertices(self, direction, blockIndices, exposedFaceIndices, blocks, blockData, blockLight, facingBlockLight, texMap): blockIndices = blockIndices & exposedFaceIndices vertexArray = self.makeTemplate(direction, blockIndices) vertexArray[_ST] += texMap(self.iceID, 0, 0)[numpy.newaxis, numpy.newaxis] vertexArray.view('uint8')[_RGB] *= facingBlockLight[blockIndices][..., numpy.newaxis, numpy.newaxis] return vertexArray makeFaceVertices = iceFaceVertices from glutils import DisplayList class MCRenderer(object): isPreviewer = False def __init__(self, level=None, alpha=1.0): self.render = True self.origin = (0, 0, 0) self.rotation = 0 self.bufferUsage = 0 self.invalidChunkQueue = deque() self._chunkWorker = None self.chunkRenderers = {} self.loadableChunkMarkers = DisplayList() self.visibleLayers = set(Layer.AllLayers) self.masterLists = None alpha = alpha * 255 self.alpha = (int(alpha) & 0xff) self.chunkStartTime = datetime.now() self.oldChunkStartTime = self.chunkStartTime self.oldPosition = None self.chunkSamples = [timedelta(0, 0, 0)] * 15 self.chunkIterator = None import leveleditor Settings = leveleditor.Settings Settings.fastLeaves.addObserver(self) Settings.roughGraphics.addObserver(self) Settings.showHiddenOres.addObserver(self) Settings.vertexBufferLimit.addObserver(self) Settings.drawEntities.addObserver(self) Settings.drawTileEntities.addObserver(self) Settings.drawTileTicks.addObserver(self) Settings.drawUnpopulatedChunks.addObserver(self, "drawTerrainPopulated") Settings.drawMonsters.addObserver(self) Settings.drawItems.addObserver(self) Settings.showChunkRedraw.addObserver(self, "showRedraw") Settings.spaceHeight.addObserver(self) Settings.targetFPS.addObserver(self, "targetFPS") self.level = level chunkClass = ChunkRenderer calculatorClass = ChunkCalculator minViewDistance = 2 maxViewDistance = 24 _viewDistance = 8 needsRedraw = True def toggleLayer(self, val, layer): if val: self.visibleLayers.add(layer) else: self.visibleLayers.discard(layer) for cr in self.chunkRenderers.itervalues(): cr.invalidLayers.add(layer) self.loadNearbyChunks() def layerProperty(layer, default=True): # @NoSelf attr = intern("_draw" + layer) def _get(self): return getattr(self, attr, default) def _set(self, val): if val != _get(self): setattr(self, attr, val) self.toggleLayer(val, layer) return property(_get, _set) drawEntities = layerProperty(Layer.Entities) drawTileEntities = layerProperty(Layer.TileEntities) drawTileTicks = layerProperty(Layer.TileTicks) drawMonsters = layerProperty(Layer.Monsters) drawItems = layerProperty(Layer.Items) drawTerrainPopulated = layerProperty(Layer.TerrainPopulated) def inSpace(self): if self.level is None: return True h = self.position[1] return ((h > self.level.Height + self.spaceHeight) or (h <= -self.spaceHeight)) def chunkDistance(self, cpos): camx, camy, camz = self.position # if the renderer is offset into the world somewhere, adjust for that ox, oy, oz = self.origin camx -= ox camz -= oz camcx = int(numpy.floor(camx)) >> 4 camcz = int(numpy.floor(camz)) >> 4 cx, cz = cpos return max(abs(cx - camcx), abs(cz - camcz)) overheadMode = False def detailLevelForChunk(self, cpos): if self.overheadMode: return 2 if self.isPreviewer: w, l, h = self.level.bounds.size if w + l < 256: return 0 distance = self.chunkDistance(cpos) - self.viewDistance if distance > 0 or self.inSpace(): return 1 return 0 def getViewDistance(self): return self._viewDistance def setViewDistance(self, vd): vd = int(vd) & 0xfffe vd = min(max(vd, self.minViewDistance), self.maxViewDistance) if vd != self._viewDistance: self._viewDistance = vd self.viewDistanceChanged() # self.invalidateChunkMarkers() viewDistance = property(getViewDistance, setViewDistance, None, "View Distance") @property def effectiveViewDistance(self): if self.inSpace(): return self.viewDistance * 4 else: return self.viewDistance * 2 def viewDistanceChanged(self): self.oldPosition = None # xxx self.discardMasterList() self.loadNearbyChunks() self.discardChunksOutsideViewDistance() maxWorkFactor = 64 minWorkFactor = 1 workFactor = 2 chunkCalculator = None _level = None @property def level(self): return self._level @level.setter def level(self, level): """ this probably warrants creating a new renderer """ self.stopWork() self._level = level self.oldPosition = None self.position = (0, 0, 0) self.chunkCalculator = None self.invalidChunkQueue = deque() self.discardAllChunks() self.loadableChunkMarkers.invalidate() if level: self.chunkCalculator = self.calculatorClass(self.level) self.oldPosition = None level.allChunks self.loadNearbyChunks() position = (0, 0, 0) def loadChunksStartingFrom(self, wx, wz, distance=None): # world position if None is self.level: return cx = wx >> 4 cz = wz >> 4 if distance is None: d = self.effectiveViewDistance else: d = distance self.chunkIterator = self.iterateChunks(wx, wz, d * 2) def iterateChunks(self, x, z, d): cx = x >> 4 cz = z >> 4 yield (cx, cz) step = dir = 1 while True: for i in range(step): cx += dir yield (cx, cz) for i in range(step): cz += dir yield (cx, cz) step += 1 if step > d and not self.overheadMode: raise StopIteration dir = -dir chunkIterator = None @property def chunkWorker(self): if self._chunkWorker is None: self._chunkWorker = self.makeWorkIterator() return self._chunkWorker def stopWork(self): self._chunkWorker = None def discardAllChunks(self): self.bufferUsage = 0 self.forgetAllDisplayLists() self.chunkRenderers = {} self.oldPosition = None # xxx force reload def discardChunksInBox(self, box): self.discardChunks(box.chunkPositions) def discardChunksOutsideViewDistance(self): if self.overheadMode: return # print "discardChunksOutsideViewDistance" d = self.effectiveViewDistance cx = (self.position[0] - self.origin[0]) / 16 cz = (self.position[2] - self.origin[2]) / 16 origin = (cx - d, cz - d) size = d * 2 if not len(self.chunkRenderers): return (ox, oz) = origin bytes = 0 # chunks = numpy.fromiter(self.chunkRenderers.iterkeys(), dtype='int32', count=len(self.chunkRenderers)) chunks = numpy.fromiter(self.chunkRenderers.iterkeys(), dtype='i,i', count=len(self.chunkRenderers)) chunks.dtype = 'int32' chunks.shape = len(self.chunkRenderers), 2 if size: outsideChunks = chunks[:, 0] < ox - 1 outsideChunks |= chunks[:, 0] > ox + size outsideChunks |= chunks[:, 1] < oz - 1 outsideChunks |= chunks[:, 1] > oz + size chunks = chunks[outsideChunks] self.discardChunks(chunks) def discardChunks(self, chunks): for cx, cz in chunks: self.discardChunk(cx, cz) self.oldPosition = None # xxx force reload def discardChunk(self, cx, cz): " discards the chunk renderer for this chunk and compresses the chunk " if (cx, cz) in self.chunkRenderers: self.bufferUsage -= self.chunkRenderers[cx, cz].bufferSize self.chunkRenderers[cx, cz].forgetDisplayLists() del self.chunkRenderers[cx, cz] _fastLeaves = False @property def fastLeaves(self): return self._fastLeaves @fastLeaves.setter def fastLeaves(self, val): if self._fastLeaves != bool(val): self.discardAllChunks() self._fastLeaves = bool(val) _roughGraphics = False @property def roughGraphics(self): return self._roughGraphics @roughGraphics.setter def roughGraphics(self, val): if self._roughGraphics != bool(val): self.discardAllChunks() self._roughGraphics = bool(val) _showHiddenOres = False @property def showHiddenOres(self): return self._showHiddenOres @showHiddenOres.setter def showHiddenOres(self, val): if self._showHiddenOres != bool(val): self.discardAllChunks() self._showHiddenOres = bool(val) def invalidateChunk(self, cx, cz, layers=None): " marks the chunk for regenerating vertex data and display lists " if (cx, cz) in self.chunkRenderers: # self.chunkRenderers[(cx,cz)].invalidate() # self.bufferUsage -= self.chunkRenderers[(cx, cz)].bufferSize self.chunkRenderers[(cx, cz)].invalidate(layers) # self.bufferUsage += self.chunkRenderers[(cx, cz)].bufferSize self.invalidChunkQueue.append((cx, cz)) # xxx encapsulate def invalidateChunksInBox(self, box, layers=None): # If the box is at the edge of any chunks, expanding by 1 makes sure the neighboring chunk gets redrawn. box = box.expand(1) self.invalidateChunks(box.chunkPositions, layers) def invalidateEntitiesInBox(self, box): self.invalidateChunks(box.chunkPositions, [Layer.Entities]) def invalidateChunks(self, chunks, layers=None): for c in chunks: cx, cz = c self.invalidateChunk(cx, cz, layers) self.stopWork() self.discardMasterList() self.loadNearbyChunks() def invalidateAllChunks(self, layers=None): self.invalidateChunks(self.chunkRenderers.iterkeys(), layers) def forgetAllDisplayLists(self): for cr in self.chunkRenderers.itervalues(): cr.forgetDisplayLists() def invalidateMasterList(self): self.discardMasterList() shouldRecreateMasterList = True def discardMasterList(self): self.shouldRecreateMasterList = True @property def shouldDrawAll(self): box = self.level.bounds return self.isPreviewer and box.width + box.length < 256 distanceToChunkReload = 32.0 def cameraMovedFarEnough(self): if self.shouldDrawAll: return False if self.oldPosition is None: return True cPos = self.position oldPos = self.oldPosition cameraDelta = self.distanceToChunkReload return any([abs(x - y) > cameraDelta for x, y in zip(cPos, oldPos)]) def loadVisibleChunks(self): """ loads nearby chunks if the camera has moved beyond a certain distance """ # print "loadVisibleChunks" if self.cameraMovedFarEnough(): if datetime.now() - self.lastVisibleLoad > timedelta(0, 0.5): self.discardChunksOutsideViewDistance() self.loadNearbyChunks() self.oldPosition = self.position self.lastVisibleLoad = datetime.now() lastVisibleLoad = datetime.now() def loadNearbyChunks(self): if None is self.level: return # print "loadNearbyChunks" cameraPos = self.position if self.shouldDrawAll: self.loadAllChunks() else: # subtract self.origin to load nearby chunks correctly for preview renderers self.loadChunksStartingFrom(int(cameraPos[0]) - self.origin[0], int(cameraPos[2]) - self.origin[2]) def loadAllChunks(self): box = self.level.bounds self.loadChunksStartingFrom(box.origin[0] + box.width / 2, box.origin[2] + box.length / 2, max(box.width, box.length)) _floorTexture = None @property def floorTexture(self): if self._floorTexture is None: self._floorTexture = Texture(self.makeFloorTex) return self._floorTexture def makeFloorTex(self): color0 = (0xff, 0xff, 0xff, 0x22) color1 = (0xff, 0xff, 0xff, 0x44) img = numpy.array([color0, color1, color1, color0], dtype='uint8') GL.glTexParameter(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST) GL.glTexParameter(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST) GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGBA, 2, 2, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, img) def invalidateChunkMarkers(self): self.loadableChunkMarkers.invalidate() def _drawLoadableChunkMarkers(self): if self.level.chunkCount: chunkSet = set(self.level.allChunks) sizedChunks = chunkMarkers(chunkSet) GL.glPushAttrib(GL.GL_FOG_BIT) GL.glDisable(GL.GL_FOG) GL.glEnable(GL.GL_BLEND) GL.glEnable(GL.GL_POLYGON_OFFSET_FILL) GL.glPolygonOffset(DepthOffset.ChunkMarkers, DepthOffset.ChunkMarkers) GL.glEnable(GL.GL_DEPTH_TEST) GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) GL.glEnable(GL.GL_TEXTURE_2D) GL.glColor(1.0, 1.0, 1.0, 1.0) self.floorTexture.bind() # chunkColor = numpy.zeros(shape=(chunks.shape[0], 4, 4), dtype='float32') # chunkColor[:]= (1, 1, 1, 0.15) # # cc = numpy.array(chunks[:,0] + chunks[:,1], dtype='int32') # cc &= 1 # coloredChunks = cc > 0 # chunkColor[coloredChunks] = (1, 1, 1, 0.28) # chunkColor *= 255 # chunkColor = numpy.array(chunkColor, dtype='uint8') # # GL.glColorPointer(4, GL.GL_UNSIGNED_BYTE, 0, chunkColor) for size, chunks in sizedChunks.iteritems(): if not len(chunks): continue chunks = numpy.array(chunks, dtype='float32') chunkPosition = numpy.zeros(shape=(chunks.shape[0], 4, 3), dtype='float32') chunkPosition[:, :, (0, 2)] = numpy.array(((0, 0), (0, 1), (1, 1), (1, 0)), dtype='float32') chunkPosition[:, :, (0, 2)] *= size chunkPosition[:, :, (0, 2)] += chunks[:, numpy.newaxis, :] chunkPosition *= 16 GL.glVertexPointer(3, GL.GL_FLOAT, 0, chunkPosition.ravel()) # chunkPosition *= 8 GL.glTexCoordPointer(2, GL.GL_FLOAT, 0, (chunkPosition[..., (0, 2)] * 8).ravel()) GL.glDrawArrays(GL.GL_QUADS, 0, len(chunkPosition) * 4) GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY) GL.glDisable(GL.GL_TEXTURE_2D) GL.glDisable(GL.GL_BLEND) GL.glDisable(GL.GL_DEPTH_TEST) GL.glDisable(GL.GL_POLYGON_OFFSET_FILL) GL.glPopAttrib() def drawLoadableChunkMarkers(self): if not self.isPreviewer or isinstance(self.level, pymclevel.MCInfdevOldLevel): self.loadableChunkMarkers.call(self._drawLoadableChunkMarkers) # self.drawCompressedChunkMarkers() needsImmediateRedraw = False viewingFrustum = None if "-debuglists" in sys.argv: def createMasterLists(self): pass def callMasterLists(self): for cr in self.chunkRenderers.itervalues(): cr.debugDraw() else: def createMasterLists(self): if self.shouldRecreateMasterList: lists = {} chunkLists = defaultdict(list) chunksPerFrame = 80 shouldRecreateAgain = False for ch in self.chunkRenderers.itervalues(): if chunksPerFrame: if ch.needsRedisplay: chunksPerFrame -= 1 ch.makeDisplayLists() else: shouldRecreateAgain = True if ch.renderstateLists: for rs in ch.renderstateLists: chunkLists[rs] += ch.renderstateLists[rs] for rs in chunkLists: if len(chunkLists[rs]): lists[rs] = numpy.array(chunkLists[rs], dtype='uint32').ravel() # lists = lists[lists.nonzero()] self.masterLists = lists self.shouldRecreateMasterList = shouldRecreateAgain self.needsImmediateRedraw = shouldRecreateAgain def callMasterLists(self): for renderstate in self.chunkCalculator.renderstates: if renderstate not in self.masterLists: continue if self.alpha != 0xff and renderstate is not ChunkCalculator.renderstateLowDetail: GL.glEnable(GL.GL_BLEND) renderstate.bind() GL.glCallLists(self.masterLists[renderstate]) renderstate.release() if self.alpha != 0xff and renderstate is not ChunkCalculator.renderstateLowDetail: GL.glDisable(GL.GL_BLEND) errorLimit = 10 def draw(self): self.needsRedraw = False if not self.level: return if not self.chunkCalculator: return if not self.render: return chunksDrawn = 0 with gl.glPushMatrix(GL.GL_MODELVIEW): dx, dy, dz = self.origin GL.glTranslate(dx, dy, dz) GL.glEnable(GL.GL_CULL_FACE) GL.glEnable(GL.GL_DEPTH_TEST) self.level.materials.terrainTexture.bind() GL.glEnable(GL.GL_TEXTURE_2D) GL.glEnableClientState(GL.GL_TEXTURE_COORD_ARRAY) offset = DepthOffset.PreviewRenderer if self.isPreviewer else DepthOffset.Renderer GL.glPolygonOffset(offset, offset) GL.glEnable(GL.GL_POLYGON_OFFSET_FILL) self.createMasterLists() try: self.callMasterLists() except GL.GLError, e: if self.errorLimit: self.errorLimit -= 1 traceback.print_exc() print e GL.glDisable(GL.GL_POLYGON_OFFSET_FILL) GL.glDisable(GL.GL_CULL_FACE) GL.glDisable(GL.GL_DEPTH_TEST) GL.glDisable(GL.GL_TEXTURE_2D) GL.glDisableClientState(GL.GL_TEXTURE_COORD_ARRAY) # if self.drawLighting: self.drawLoadableChunkMarkers() renderErrorHandled = False def addDebugInfo(self, addDebugString): addDebugString("BU: {0} MB, ".format( self.bufferUsage / 1000000, )) addDebugString("WQ: {0}, ".format(len(self.invalidChunkQueue))) if self.chunkIterator: addDebugString("[LR], ") addDebugString("CR: {0}, ".format(len(self.chunkRenderers),)) def next(self): self.chunkWorker.next() def makeWorkIterator(self): ''' does chunk face and vertex calculation work. returns a generator that can be iterated over for smaller work units.''' try: while True: if self.level is None: raise StopIteration if len(self.invalidChunkQueue) > 1024: self.invalidChunkQueue.clear() if len(self.invalidChunkQueue): c = self.invalidChunkQueue[0] for i in self.workOnChunk(c): yield self.invalidChunkQueue.popleft() elif self.chunkIterator is None: raise StopIteration else: c = self.chunkIterator.next() if self.vertexBufferLimit: while self.bufferUsage > (0.9 * (self.vertexBufferLimit << 20)): deadChunk = None deadDistance = self.chunkDistance(c) for cr in self.chunkRenderers.itervalues(): dist = self.chunkDistance(cr.chunkPosition) if dist > deadDistance: deadChunk = cr deadDistance = dist if deadChunk is not None: self.discardChunk(*deadChunk.chunkPosition) else: break else: for i in self.workOnChunk(c): yield else: for i in self.workOnChunk(c): yield yield finally: self._chunkWorker = None if self.chunkIterator: self.chunkIterator = None vertexBufferLimit = 384 def getChunkRenderer(self, c): if not (c in self.chunkRenderers): cr = self.chunkClass(self, c) else: cr = self.chunkRenderers[c] return cr def calcFacesForChunkRenderer(self, cr): self.bufferUsage -= cr.bufferSize calc = cr.calcFaces() work = 0 for i in calc: yield work += 1 self.chunkDone(cr, work) def workOnChunk(self, c): work = 0 if self.level.containsChunk(*c): cr = self.getChunkRenderer(c) if self.viewingFrustum: # if not self.viewingFrustum.visible(numpy.array([[c[0] * 16 + 8, 64, c[1] * 16 + 8, 1.0]]), 64).any(): if not self.viewingFrustum.visible1([c[0] * 16 + 8, self.level.Height / 2, c[1] * 16 + 8, 1.0], self.level.Height / 2): raise StopIteration yield faceInfoCalculator = self.calcFacesForChunkRenderer(cr) try: for result in faceInfoCalculator: work += 1 if (work % MCRenderer.workFactor) == 0: yield self.invalidateMasterList() except Exception, e: traceback.print_exc() fn = c logging.info(u"Skipped chunk {f}: {e}".format(e=e, f=fn)) redrawChunks = 0 def chunkDone(self, chunkRenderer, work): self.chunkRenderers[chunkRenderer.chunkPosition] = chunkRenderer self.bufferUsage += chunkRenderer.bufferSize # print "Chunk {0} used {1} work units".format(chunkRenderer.chunkPosition, work) if not self.needsRedraw: if self.redrawChunks: self.redrawChunks -= 1 if not self.redrawChunks: self.needsRedraw = True else: self.redrawChunks = 2 if work > 0: self.oldChunkStartTime = self.chunkStartTime self.chunkStartTime = datetime.now() self.chunkSamples.pop(0) self.chunkSamples.append(self.chunkStartTime - self.oldChunkStartTime) cx, cz = chunkRenderer.chunkPosition class PreviewRenderer(MCRenderer): isPreviewer = True def rendermain(): renderer = MCRenderer() renderer.level = pymclevel.mclevel.loadWorld("World1") renderer.viewDistance = 6 renderer.detailLevelForChunk = lambda * x: 0 start = datetime.now() renderer.loadVisibleChunks() try: while True: # for i in range(100): renderer.next() except StopIteration: pass except Exception, e: traceback.print_exc() print repr(e) duration = datetime.now() - start perchunk = duration / len(renderer.chunkRenderers) print "Duration: {0} ({1} chunks per second, {2} per chunk, {3} chunks)".format(duration, 1000000.0 / perchunk.microseconds, perchunk, len(renderer.chunkRenderers)) # display.init( (640, 480), OPENGL | DOUBLEBUF ) from mcedit import GLDisplayContext from OpenGL import GLU cxt = GLDisplayContext() import pygame # distance = 4000 GL.glMatrixMode(GL.GL_PROJECTION) GL.glLoadIdentity() GLU.gluPerspective(35, 640.0 / 480.0, 0.5, 4000.0) h = 366 pos = (0, h, 0) look = (0.0001, h - 1, 0.0001) up = (0, 1, 0) GL.glMatrixMode(GL.GL_MODELVIEW) GL.glLoadIdentity() GLU.gluLookAt(pos[0], pos[1], pos[2], look[0], look[1], look[2], up[0], up[1], up[2]) GL.glClearColor(0.0, 0.0, 0.0, 1.0) framestart = datetime.now() frames = 200 for i in range(frames): GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT) renderer.draw() pygame.display.flip() delta = datetime.now() - framestart seconds = delta.seconds + delta.microseconds / 1000000.0 print "{0} frames in {1} ({2} per frame, {3} FPS)".format(frames, delta, delta / frames, frames / seconds) while True: evt = pygame.event.poll() if evt.type == pygame.MOUSEBUTTONDOWN: break # time.sleep(3.0) import traceback import cProfile if __name__ == "__main__": cProfile.run("rendermain()", "mcedit.profile")