"""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 deque

from numpy import *
import itertools

from pymclevel import *
del sum
del any
del all  # xxx use builtin all, fix the wild imports later

from datetime import datetime, timedelta
from time import sleep
from threading import Thread
from collections import defaultdict

from depths import DepthOffset

import OpenGL

from OpenGL import GL
from OpenGL.GL import *
from OpenGL.GLU import *
from OpenGL.GLUT import *
from OpenGL.GL.ARB.vertex_buffer_object import *
from OpenGL.GL.EXT.framebuffer_object import *

from pymclevel.faces import *
import config
from mceutils import reportCrash
from glutils import gl, Texture


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):
            glEnableClientState(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):
            glDisableClientState(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 = s_[..., 0:3]
_ST = s_[..., 3:5]
_XYZST = s_[..., :5]
_RGBA = s_[..., 20:24]
_RGB = s_[..., 20:23]
_A = s_[..., 23]


def makeVertexTemplates(xmin=0, ymin=0, zmin=0, xmax=1, ymax=1, zmax=1):
        return 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 = [zeros(shape=(16, 16, height, 4, 6),  # x,y,z,s,t,rg, ba
                                  dtype='float32') for d in faceVertexTemplates]

    xArray = arange(16)[:, newaxis, newaxis, newaxis]
    zArray = arange(16)[newaxis, :, newaxis, newaxis]
    yArray = arange(height)[newaxis, newaxis, :, 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, newaxis]
        precomputedVertices[dir].view('uint8')[_A] = 0xff

    return precomputedVertices

faceVertexTemplates = makeVertexTemplates()


class ChunkCalculator (object):
    cachedTemplate = None
    cachedTemplateHeight = 0

    whiteLight = array([[[15] * 16] * 16] * 16, uint8)
    precomputedVertices = createPrecomputedVertices()

    def __init__(self, level):
        self.makeRenderstates(level.materials)

            # del xArray, zArray, yArray
        self.nullVertices = 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):
            glDisable(GL_CULL_FACE)
            glDisable(GL_TEXTURE_2D)

        @classmethod
        def release(self):
            glEnable(GL_CULL_FACE)
            glEnable(GL_TEXTURE_2D)

    class renderstateAlphaTest(object):
        @classmethod
        def bind(self):
            glEnable(GL_ALPHA_TEST)

        @classmethod
        def release(self):
            glDisable(GL_ALPHA_TEST)

    class _renderstateAlphaBlend(object):
        @classmethod
        def bind(self):
            glEnable(GL_BLEND)

        @classmethod
        def release(self):
            glDisable(GL_BLEND)

    class renderstateWater(_renderstateAlphaBlend):
        pass

    class renderstateIce(_renderstateAlphaBlend):
        pass

    class renderstateEntity(object):
        @classmethod
        def bind(self):
            glDisable(GL_DEPTH_TEST)
            # glDisable(GL_CULL_FACE)
            glDisable(GL_TEXTURE_2D)
            glEnable(GL_BLEND)

        @classmethod
        def release(self):
            glEnable(GL_DEPTH_TEST)
            # glEnable(GL_CULL_FACE)
            glEnable(GL_TEXTURE_2D)
            glDisable(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 = 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 = array(materialMap)
        self.addTransparentMaterials(self.exposedMaterialMap, materialCount)

    def addTransparentMaterials(self, mats, materialCount):
        transparentMaterials = [
            alphaMaterials.Glass,
            alphaMaterials.GlassPane,
            alphaMaterials.IronBars,
            alphaMaterials.MonsterSpawner,
            alphaMaterials.Vines,
            alphaMaterials.Fire,
        ]
        for b in transparentMaterials:
            mats[b.ID] = materialCount
            materialCount += 1

    hiddenOreMaterials = 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 = 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:
            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 ((FaceXDecreasing, -1, 0),
                           (FaceXIncreasing, 1, 0),
                           (FaceZDecreasing, 0, -1),
                           (FaceZIncreasing, 0, 1)):
            if not level.containsChunk(cx + dx, cz + dz):
                neighboringChunks[dir] = ZeroChunk(level.Height)
            else:
                # if not level.chunkIsLoaded(cx+dx,cz+dz):
                #    raise StopIteration
                try:
                    neighboringChunks[dir] = level.getChunk(cx + dx, cz + dz)
                except (ChunkNotPresent, ChunkMalformed):
                    neighboringChunks[dir] = ZeroChunk(level.Height)
        return neighboringChunks

    def getAreaBlocks(self, chunk, neighboringChunks):
        chunkWidth, chunkLength, chunkHeight = chunk.Blocks.shape

        areaBlocks = zeros((chunkWidth + 2, chunkLength + 2, chunkHeight + 2), uint8)
        areaBlocks[1:-1, 1:-1, 1:-1] = chunk.Blocks
        areaBlocks[:1, 1:-1, 1:-1] = neighboringChunks[FaceXDecreasing].Blocks[-1:, :chunkLength, :chunkHeight]
        areaBlocks[-1:, 1:-1, 1:-1] = neighboringChunks[FaceXIncreasing].Blocks[:1, :chunkLength, :chunkHeight]
        areaBlocks[1:-1, :1, 1:-1] = neighboringChunks[FaceZDecreasing].Blocks[:chunkWidth, -1:, :chunkHeight]
        areaBlocks[1:-1, -1:, 1:-1] = neighboringChunks[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[FaceXDecreasing] = exposedFacesX[:-1]
        facingBlockIndices[FaceXIncreasing] = exposedFacesX[1:]

        exposedFacesZ = (areaBlockMats[1:-1, :-1, 1:-1] != areaBlockMats[1:-1, 1:, 1:-1])

        facingBlockIndices[FaceZDecreasing] = exposedFacesZ[:, :-1]
        facingBlockIndices[FaceZIncreasing] = exposedFacesZ[:, 1:]

        exposedFacesY = (areaBlockMats[1:-1, 1:-1, :-1] != areaBlockMats[1:-1, 1:-1, 1:])

        facingBlockIndices[FaceYDecreasing] = exposedFacesY[:, :, :-1]
        facingBlockIndices[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 = maximum(skyLight, lights)

        areaBlockLights = ones((chunkWidth + 2, chunkLength + 2, chunkHeight + 2), uint8)
        areaBlockLights[:] = 15

        areaBlockLights[1:-1, 1:-1, 1:-1] = finalLight

        nc = neighboringChunks[FaceXDecreasing]
        maximum(nc.SkyLight[-1:, :chunkLength, :chunkHeight],
                nc.BlockLight[-1:, :chunkLength, :chunkHeight],
                areaBlockLights[0:1, 1:-1, 1:-1])

        nc = neighboringChunks[FaceXIncreasing]
        maximum(nc.SkyLight[:1, :chunkLength, :chunkHeight],
                nc.BlockLight[:1, :chunkLength, :chunkHeight],
                areaBlockLights[-1:, 1:-1, 1:-1])

        nc = neighboringChunks[FaceZDecreasing]
        maximum(nc.SkyLight[:chunkWidth, -1:, :chunkHeight],
                nc.BlockLight[:chunkWidth, -1:, :chunkHeight],
                areaBlockLights[1:-1, 0:1, 1:-1])

        nc = neighboringChunks[FaceZIncreasing]
        maximum(nc.SkyLight[:chunkWidth, :1, :chunkHeight],
                nc.BlockLight[:chunkWidth, :1, :chunkHeight],
                areaBlockLights[1:-1, -1:, 1:-1])

        minimumLight = 4
        # areaBlockLights[areaBlockLights<minimumLight]=minimumLight
        clip(areaBlockLights, minimumLight, 16, areaBlockLights)

        return areaBlockLights

    def calcHighDetailFaces(self, cr, blockRenderers):  # ForChunk(self, chunkPosition = (0,0), level = None, alpha = 1.0):
        """ calculate the geometry for a chunk renderer from its blockMats, data,
        and lighting array. fills in the cr's blockRenderers with verts
        for each block facing and material"""

        # chunkBlocks and chunkLights shall be indexed [x,z,y] to follow infdev's convention
        cx, cz = cr.chunkPosition
        level = cr.renderer.level

        chunk = level.getChunk(cx, cz)
        neighboringChunks = self.getNeighboringChunks(chunk)

        areaBlocks = self.getAreaBlocks(chunk, neighboringChunks)
        yield

        areaBlockLights = self.getAreaBlockLights(chunk, neighboringChunks)
        yield

        slabs = areaBlocks == alphaMaterials.StoneSlab.ID
        if slabs.any():
            areaBlockLights[slabs] = areaBlockLights[:, :, 1:][slabs[:, :, :-1]]
        yield

        showHiddenOres = cr.renderer.showHiddenOres
        if showHiddenOres:
            facingMats = self.hiddenOreMaterials[areaBlocks]
        else:
            facingMats = self.exposedMaterialMap[areaBlocks]

        yield

        if self.roughGraphics:
            areaBlockMats = self.roughMaterials[areaBlocks]
        else:
            areaBlockMats = self.materialMap[areaBlocks]

        facingBlockIndices = self.getFacingBlockIndices(areaBlocks, facingMats)
        yield

        for i in self.computeGeometry(chunk, areaBlockMats, facingBlockIndices, areaBlockLights, cr, blockRenderers):
            yield

    def computeGeometry(self, chunk, areaBlockMats, facingBlockIndices, areaBlockLights, chunkRenderer, blockRenderers):
        blocks, blockData = chunk.Blocks, chunk.Data
        blockData = blockData & 0xf
        blockMaterials = areaBlockMats[1:-1, 1:-1, 1:-1]
        if self.roughGraphics:
            blockMaterials.clip(0, 1, blockMaterials)

        sx = sz = slice(0, 16)
        asx = asz = slice(0, 18)

        for y in range(0, chunk.world.Height, 16):
            sy = slice(y, y + 16)
            asy = slice(y, y + 18)

            for _i in self.computeCubeGeometry(
                    y,
                    blockRenderers,
                    blocks[sx, sz, sy],
                    blockData[sx, sz, sy],
                    chunk.materials,
                    blockMaterials[sx, sz, sy],
                    [f[sx, sz, sy] for f in facingBlockIndices],
                    areaBlockLights[asx, asz, asy],
                    chunkRenderer):
                yield

    def computeCubeGeometry(self, y, blockRenderers, blocks, blockData, materials, blockMaterials, facingBlockIndices, areaBlockLights, chunkRenderer):
        materialCounts = bincount(blockMaterials.ravel())

        def texMap(blocks, blockData=0, direction=slice(None)):
            return materials.blockTextures[blocks, blockData, direction]  # xxx slow

        for blockRendererClass in self.blockRendererClasses:
            mi = blockRendererClass.materialIndex
            if mi >= 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 = {
        FaceXDecreasing: s_[:-2, 1:-1, 1:-1],
        FaceXIncreasing: s_[2:, 1:-1, 1:-1],
        FaceYDecreasing: s_[1:-1, 1:-1, :-2],
        FaceYIncreasing: s_[1:-1, 1:-1, 2:],
        FaceZDecreasing: s_[1:-1, :-2, 1:-1],
        FaceZIncreasing: 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)
        glNewList(l, GL_COMPILE)
        self.drawArrays(chunkPosition, showRedraw)
        glEndList()
        return l

    def drawArrays(self, chunkPosition, showRedraw):
        cx, cz = chunkPosition
        y = 0
        if hasattr(self, 'y'):
            y = self.y
        with gl.glPushMatrix(GL_MODELVIEW):
            glTranslate(cx << 4, y, cz << 4)

            if showRedraw:
                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

        glVertexPointer(3, GL_FLOAT, stride, (buf.ravel()))
        glTexCoordPointer(2, GL_FLOAT, stride, (buf.ravel()[3:]))
        glColorPointer(4, GL_UNSIGNED_BYTE, stride, (buf.view(dtype=uint8).ravel()[20:]))

        glDrawArrays(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

        glVertexPointer(3, GL_FLOAT, stride, (buf.ravel()))
        glTexCoordPointer(2, GL_FLOAT, stride, (buf.ravel()[3:]))
        glColorPointer(4, GL_UNSIGNED_BYTE, stride, (buf.view(dtype=uint8).ravel()[20:]))

        glDepthMask(False)

        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)

        glLineWidth(2.0)
        glDrawArrays(GL_QUADS, 0, len(buf) * 4)

        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)

        glPolygonOffset(DepthOffset.TerrainWire, DepthOffset.TerrainWire)
        with gl.glEnable(GL_POLYGON_OFFSET_FILL, GL_DEPTH_TEST):
            glDrawArrays(GL_QUADS, 0, len(buf) * 4)
        glDepthMask(True)

    def _computeVertices(self, positions, colors, offset=False, chunkPosition=(0, 0)):
        cx, cz = chunkPosition
        x = cx << 4
        z = cz << 4

        vertexArray = zeros(shape=(len(positions), 6, 4, 6), dtype='float32')
        if len(positions):
            positions = array(positions)
            positions[:, (0, 2)] -= (x, z)
            if offset:
                positions -= 0.5

            vertexArray.view('uint8')[_RGBA] = colors
            vertexArray[_XYZ] = positions[:, newaxis, 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(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(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(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(Entity.pos(ent))
            entityColors.append(color)

        entities = self._computeVertices(entityPositions, array(entityColors, dtype='uint8')[:, newaxis, newaxis], offset=True, chunkPosition=chunk.chunkPosition)
        yield
        self.vertexArrays = [entities]


class TileTicksRenderer(EntityRendererGeneric):
    layer = Layer.TileTicks

    def makeChunkVertices(self, chunk):
        chunk.decompress()
        if chunk.root_tag and "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 = 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

        glVertexPointer(3, GL_FLOAT, stride, (buf.ravel()))
        glTexCoordPointer(2, GL_FLOAT, stride, (buf.ravel()[3:]))
        glColorPointer(4, GL_UNSIGNED_BYTE, stride, (buf.view(dtype=uint8).ravel()[20:]))

        glDepthMask(False)

        # glDrawArrays(GL_QUADS, 0, len(buf) * 4)
        glDisable(GL_CULL_FACE)

        with gl.glEnable(GL_DEPTH_TEST):
            glDrawArrays(GL_QUADS, 0, len(buf) * 4)

        glEnable(GL_CULL_FACE)

        glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)

        glLineWidth(1.0)
        glDrawArrays(GL_QUADS, 0, len(buf) * 4)
        glLineWidth(2.0)
        with gl.glEnable(GL_DEPTH_TEST):
            glDrawArrays(GL_QUADS, 0, len(buf) * 4)
        glLineWidth(1.0)

        glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
        glDepthMask(True)

#        glPolygonOffset(DepthOffset.TerrainWire, DepthOffset.TerrainWire)
#        with gl.glEnable(GL_POLYGON_OFFSET_FILL, GL_DEPTH_TEST):
#            glDrawArrays(GL_QUADS, 0, len(buf) * 4)
#

    def makeChunkVertices(self, chunk):
        chunk.decompress()
        neighbors = self.chunkCalculator.getNeighboringChunks(chunk)

        def getpop(ch):
            return getattr(ch, "TerrainPopulated", True)

        pop = getpop(chunk)
        yield
        if pop:
            return

        visibleFaces = [
            getpop(neighbors[FaceXIncreasing]),
            getpop(neighbors[FaceXDecreasing]),
            True,
            True,
            getpop(neighbors[FaceZIncreasing]),
            getpop(neighbors[FaceZDecreasing]),
        ]
        visibleFaces = 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

        glVertexPointer(3, GL_FLOAT, stride, ravel(buf.ravel()))
        glColorPointer(4, GL_UNSIGNED_BYTE, stride, (buf.view(dtype='uint8').ravel()[12:]))

        glDisableClientState(GL_TEXTURE_COORD_ARRAY)
        glDrawArrays(GL_QUADS, 0, len(buf) * 4)
        glEnableClientState(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 = zeros((chunkWidth, chunkLength, chunkHeight), bool)

        gridaxes = list(indices((chunkWidth, chunkLength)))
        h = swapaxes(heightMap - 1, 0, 1)[:chunkWidth, :chunkLength]
        clip(h, 0, chunkHeight - 1, out=h)

        gridaxes = [gridaxes[0], gridaxes[1], h]

        depths = zeros((chunkWidth, chunkLength), dtype='uint16')
        depths[1:-1, 1:-1] = reduce(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
            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][:, newaxis, :]
            # flatcolors[:,:,:3] *= (0.6 + (h * (0.4 / float(chunkHeight-1)))) [topBlocks != 0][:, newaxis, newaxis]
            x, z, y = blockIndices.nonzero()

            yield
            vertexArray = zeros((len(x), 4, 4), dtype='float32')
            vertexArray[_XYZ][..., 0] = x[:, newaxis]
            vertexArray[_XYZ][..., 1] = y[:, newaxis]
            vertexArray[_XYZ][..., 2] = z[:, newaxis]

            va0 = array(vertexArray)

            va0[..., :3] += faceVertexTemplates[FaceYIncreasing, ..., :3]

            overmask = overblocks > 0
            flatcolors[overmask] = level.materials.flatColors[:, 0][overblocks[overmask]][:, newaxis]

            if self.detailLevel == 2:
                heightfactor = (y / float(2.0 * ch.world.Height)) + 0.5
                flatcolors[..., :3] *= heightfactor[:, newaxis, newaxis]

            _RGBA = 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 = array(vertexArray)
            va1[..., :3] += faceVertexTemplates[FaceXIncreasing, ..., :3]

            va1[_XYZ][:, (0, 1), 1] = depths[nonAirBlocks].ravel()[:, 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]][:, newaxis]

            va2 = 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)[:, newaxis, 0:2]
            setTexture()

            def setGrassColors():
                grass = theseBlocks == alphaMaterials.Grass.ID
                vertexArray.view('uint8')[_RGB][grass] *= self.grassColor

            def getBlockLight():
                return facingBlockLight[blockIndices]

            def setColors():
                vertexArray.view('uint8')[_RGB] *= getBlockLight()[..., newaxis, newaxis]
                if self.materials.name in ("Alpha", "Pocket"):
                    if direction == FaceYIncreasing:
                        setGrassColors()
                # leaves = theseBlocks == alphaMaterials.Leaves.ID
                # vertexArray.view('uint8')[_RGBA][leaves] *= [0.15, 0.88, 0.15, 1.0]
#                snow = theseBlocks == alphaMaterials.SnowLayer.ID
#                if direction == FaceYIncreasing:
#                    vertexArray[_XYZ][snow, ...,1] -= 0.875
#
#                if direction != FaceYIncreasing and direction != 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 == alphaMaterials.PineLeaves.blockData)
                birches = (data == 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 == alphaMaterials.PineLeaves.blockData)
                    birches = (data == 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[:, newaxis]

            if not self.chunkCalculator.fastLeaves:
                vertexArray[_ST] -= (0x10, 0x0)

            vertexArray.view('uint8')[_RGB] *= facingBlockLight[blockIndices][..., newaxis, 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][..., newaxis, newaxis]

        colorize = None
        if self.materials.name == "Alpha":
            colorize = (theseBlocks == alphaMaterials.TallGrass.ID) & (bdata != 0)

        for direction in (FaceXIncreasing, FaceXDecreasing, FaceZIncreasing, FaceZDecreasing):
            vertexArray = self.makeTemplate(direction, blockIndices)
            if not len(vertexArray):
                return

            if direction == FaceXIncreasing:
                vertexArray[_XYZ][..., 1:3, 0] -= 1
            if direction == FaceXDecreasing:
                vertexArray[_XYZ][..., 1:3, 0] += 1
            if direction == FaceZIncreasing:
                vertexArray[_XYZ][..., 1:3, 2] -= 1
            if direction == FaceZDecreasing:
                vertexArray[_XYZ][..., 1:3, 2] += 1

            vertexArray[_ST] += texes[:, 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 = 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 == FaceYIncreasing:
                vertexArray[_ST] = self.upCoords
            if direction == FaceYDecreasing:
                vertexArray[_ST] = self.downCoords
            vertexArray[_ST] += texes[:, newaxis, direction]
            arrays.append(vertexArray)
            yield
        self.vertexArrays = arrays

    makeVertices = makeTorchVertices


class RailBlockRenderer(BlockRenderer):
    blocktypes = [alphaMaterials.Rail.ID, alphaMaterials.PoweredRail.ID, alphaMaterials.DetectorRail.ID]
    renderstate = ChunkCalculator.renderstateAlphaTest

    railTextures = 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 -= alphaMaterials.blockTextures[alphaMaterials.Rail.ID, 0, 0]

    railOffsets = 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 = FaceYIncreasing
        blockIndices = self.getMaterialIndices(blockMaterials)
        yield

        bdata = blockData[blockIndices]
        railBlocks = blocks[blockIndices]
        tex = texMap(railBlocks, bdata, FaceYIncreasing)[:, newaxis, :]

        # disable 'powered' or 'pressed' bit for powered and detector rails
        bdata[railBlocks != 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][..., newaxis, newaxis]
        yield
        self.vertexArrays = [vertexArray]

    makeVertices = makeRailVertices


class LadderBlockRenderer(BlockRenderer):
    blocktypes = [alphaMaterials.Ladder.ID]

    ladderOffsets = 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 = 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(FaceYIncreasing, blockIndices)
        if not len(vertexArray):
            return

        vertexArray[_ST] = self.ladderTextures[bdata]
        vertexArray[_XYZ] += self.ladderOffsets[bdata]
        vertexArray.view('uint8')[_RGB] *= blockLight[blockIndices][..., newaxis, 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 != FaceYIncreasing:
                blockIndices = snowIndices & exposedFaceIndices
            else:
                blockIndices = snowIndices

            facingBlockLight = areaBlockLights[self.directionOffsets[direction]]
            lights = facingBlockLight[blockIndices][..., newaxis, newaxis]

            vertexArray = self.makeTemplate(direction, blockIndices)
            if not len(vertexArray):
                continue

            vertexArray[_ST] += texMap([self.snowID], 0, 0)[:, newaxis, 0:2]
            vertexArray.view('uint8')[_RGB] *= lights

            if direction == FaceYIncreasing:
                vertexArray[_XYZ][..., 1] -= 0.875

            if direction != FaceYIncreasing and direction != 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(FaceYIncreasing, blockIndices)
        if not len(vertexArray):
            return

        vertexArray[_ST] += 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[..., 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 = [alphaMaterials.Button.ID,
#                  alphaMaterials.StoneFloorPlate.ID,
#                  alphaMaterials.WoodFloorPlate.ID,
#                  alphaMaterials.WoodenDoor.ID,
#                  alphaMaterials.IronDoor.ID,
#                  ]
#
    blocktypes = [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 = 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 == alphaMaterials.Button.ID
        axes = blockIndices.nonzero()

        vertexArray = zeros((len(axes[0]), 6, 4, 6), dtype=float32)
        vertexArray[_XYZ][..., 0] = axes[0][..., newaxis, newaxis]
        vertexArray[_XYZ][..., 1] = axes[2][..., newaxis, newaxis]
        vertexArray[_XYZ][..., 2] = axes[1][..., newaxis, newaxis]

        vertexArray[_XYZ] += self.buttonOffsets
        vertexArray[_ST] = [[0, 0], [0, 16], [16, 16], [16, 0]]
        vertexArray[_ST] += texMap(alphaMaterials.Stone.ID, 0)[newaxis, :, 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 == alphaMaterials.Fence.ID
        fenceIndices = fenceMask.nonzero()
        yield

        vertexArray = zeros((len(fenceIndices[0]), 6, 4, 6), dtype='float32')
        for i in range(3):
            j = (0, 2, 1)[i]

            vertexArray[..., i] = fenceIndices[j][:, newaxis, newaxis]  # xxx swap z with y using ^

        vertexArray[..., 0:5] += self.fenceTemplates[..., 0:5]
        vertexArray[_ST] += alphaMaterials.blockTextures[alphaMaterials.WoodPlanks.ID, 0, 0]

        vertexArray.view('uint8')[_RGBA] = self.fenceTemplates[..., 5][..., newaxis]

        vertexArray.view('uint8')[_RGB] *= areaBlockLights[1:-1, 1:-1, 1:-1][fenceIndices][..., newaxis, newaxis, 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 = 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 = zeros((len(x), 6, 4, 6), dtype='float32')
            for i in range(3):
                vertexArray[_XYZ][..., i] = (x, y, z)[i][:, newaxis, newaxis]

            if _ == "step":
                vertexArray[_XYZST] += self.stairTemplates[4][..., :5]

            else:
                vertexArray[_XYZST] += self.stairTemplates[stairData][..., :5]

            vertexArray[_ST] += texMap(stairBlocks, 0)[..., newaxis, :]

            vertexArray.view('uint8')[_RGB] = self.stairTemplates[4][newaxis, ..., 5, 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 != FaceYIncreasing:
            blockIndices = blockIndices & exposedFaceIndices

        lights = facingBlockLight[blockIndices][..., newaxis, newaxis]
        bdata = blockData[blockIndices]

        vertexArray = self.makeTemplate(direction, blockIndices)
        if not len(vertexArray):
            return vertexArray

        vertexArray[_ST] += texMap(self.slabID, bdata, direction)[:, newaxis, 0:2]
        vertexArray.view('uint8')[_RGB] *= lights

        if direction == FaceYIncreasing:
            vertexArray[_XYZ][..., 1] -= 0.5

        if direction != FaceYIncreasing and direction != 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)[newaxis, newaxis]
        vertexArray.view('uint8')[_RGB] *= facingBlockLight[blockIndices][..., newaxis, 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)[newaxis, newaxis]
        vertexArray.view('uint8')[_RGB] *= facingBlockLight[blockIndices][..., newaxis, 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(floor(camx)) >> 4
        camcz = int(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 = fromiter(self.chunkRenderers.iterkeys(), dtype='int32', count=len(self.chunkRenderers))
        chunks = 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]

        self.level.compressChunk(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):
        box = BoundingBox(box)
        if box.minx & 0xf == 0:
            box.minx -= 1
        if box.miny & 0xf == 0:
            box.miny -= 1
        if box.minz & 0xf == 0:
            box.minz -= 1

        if box.maxx & 0xf == 0:
            box.maxx += 1
        if box.maxy & 0xf == 0:
            box.maxy += 1
        if box.maxz & 0xf == 0:
            box.maxz += 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 = 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)

            glPushAttrib(GL_FOG_BIT)
            glDisable(GL_FOG)

            glEnable(GL_BLEND)
            glEnable(GL_POLYGON_OFFSET_FILL)
            glPolygonOffset(DepthOffset.ChunkMarkers, DepthOffset.ChunkMarkers)
            glEnable(GL_DEPTH_TEST)

            glEnableClientState(GL_TEXTURE_COORD_ARRAY)
            glEnable(GL_TEXTURE_2D)
            glColor(1.0, 1.0, 1.0, 1.0)

            self.floorTexture.bind()
            # chunkColor = zeros(shape=(chunks.shape[0], 4, 4), dtype='float32')
#            chunkColor[:]= (1, 1, 1, 0.15)
#
#            cc = array(chunks[:,0] + chunks[:,1], dtype='int32')
#            cc &= 1
#            coloredChunks = cc > 0
#            chunkColor[coloredChunks] = (1, 1, 1, 0.28)
#            chunkColor *= 255
#            chunkColor = array(chunkColor, dtype='uint8')
#
            # glColorPointer(4, GL_UNSIGNED_BYTE, 0, chunkColor)
            for size, chunks in sizedChunks.iteritems():
                if not len(chunks):
                    continue
                chunks = array(chunks, dtype='float32')

                chunkPosition = zeros(shape=(chunks.shape[0], 4, 3), dtype='float32')
                chunkPosition[:, :, (0, 2)] = array(((0, 0), (0, 1), (1, 1), (1, 0)), dtype='float32')
                chunkPosition[:, :, (0, 2)] *= size
                chunkPosition[:, :, (0, 2)] += chunks[:, newaxis, :]
                chunkPosition *= 16
                glVertexPointer(3, GL_FLOAT, 0, chunkPosition.ravel())
                # chunkPosition *= 8
                glTexCoordPointer(2, GL_FLOAT, 0, (chunkPosition[..., (0, 2)] * 8).ravel())
                glDrawArrays(GL_QUADS, 0, len(chunkPosition) * 4)

            glDisableClientState(GL_TEXTURE_COORD_ARRAY)
            glDisable(GL_TEXTURE_2D)
            glDisable(GL_BLEND)
            glDisable(GL_DEPTH_TEST)
            glDisable(GL_POLYGON_OFFSET_FILL)
            glPopAttrib()

    def drawLoadableChunkMarkers(self):
        if not self.isPreviewer or isinstance(self.level, MCInfdevOldLevel):
            self.loadableChunkMarkers.call(self._drawLoadableChunkMarkers)

        # self.drawCompressedChunkMarkers()

    def drawCompressedChunkMarkers(self):
        chunkPositions = self.chunkRenderers.keys()
        if 0 == len(chunkPositions):
            return

        chunkPositions = array(chunkPositions)

        chunkLoaded = [self.level.chunkIsLoaded(*c) for c in chunkPositions]
        chunkLoaded = array(chunkLoaded, dtype='bool')

        chunkCompressed = [self.level.chunkIsCompressed(*c) for c in chunkPositions]
        chunkCompressed = array(chunkCompressed, dtype='bool')

        chunkDirty = [self.level.chunkIsDirty(*c) for c in chunkPositions]
        chunkDirty = array(chunkDirty, dtype='bool')

        vertexBuffer = zeros((len(chunkPositions), 4, 3), dtype='float32')

        vertexBuffer[..., (0, 2)] = array(((0, 0), (0, 1), (1, 1), (1, 0)), dtype='float32')

        vertexBuffer[..., (0, 2)] += chunkPositions[:, newaxis]
        vertexBuffer[..., (0, 2)] *= 16

        vertexBuffer[..., 1] = 128

        colorBuffer = zeros((len(chunkCompressed), 4, 4), dtype='uint8')
        colorBuffer[:] = (0x00, 0x00, 0x00, 0x33)
        colorBuffer[chunkLoaded] = (0xff, 0xff, 0xff, 0x66)
        colorBuffer[chunkCompressed] = (0xff, 0xFF, 0x00, 0x66)
        colorBuffer[chunkDirty] = (0xff, 0x00, 0x00, 0x66)

        cc = array(chunkPositions[:, 0] + chunkPositions[:, 1], dtype='int32')
        cc &= 1
        coloredChunks = cc > 0
        colorBuffer[coloredChunks] *= 0.75

        glEnable(GL_BLEND)
        glEnable(GL_POLYGON_OFFSET_FILL)
        glPolygonOffset(DepthOffset.ChunkMarkers, DepthOffset.ChunkMarkers)
        glEnable(GL_DEPTH_TEST)
        glEnableClientState(GL_COLOR_ARRAY)

        glVertexPointer(3, GL_FLOAT, 0, vertexBuffer)
        glColorPointer(4, GL_UNSIGNED_BYTE, 0, colorBuffer)
        glDrawArrays(GL_QUADS, 0, len(vertexBuffer) * 4)

        glDisableClientState(GL_COLOR_ARRAY)

        glDisable(GL_POLYGON_OFFSET_FILL)
        glDisable(GL_DEPTH_TEST)

        glDisable(GL_BLEND)

    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] = 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:
                    glEnable(GL_BLEND)
                renderstate.bind()

                glCallLists(self.masterLists[renderstate])

                renderstate.release()
                if self.alpha != 0xff and renderstate is not ChunkCalculator.renderstateLowDetail:
                    glDisable(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_MODELVIEW):
            dx, dy, dz = self.origin
            glTranslate(dx, dy, dz)

            glEnable(GL_CULL_FACE)
            glEnable(GL_DEPTH_TEST)

            self.level.materials.terrainTexture.bind()
            glEnable(GL_TEXTURE_2D)
            glEnableClientState(GL_TEXTURE_COORD_ARRAY)

            offset = DepthOffset.PreviewRenderer if self.isPreviewer else DepthOffset.Renderer
            glPolygonOffset(offset, offset)
            glEnable(GL_POLYGON_OFFSET_FILL)

            self.createMasterLists()
            try:
                self.callMasterLists()

            except GLError, e:
                if self.errorLimit:
                    self.errorLimit -= 1
                    traceback.print_exc()
                    print e

            glDisable(GL_POLYGON_OFFSET_FILL)

            glDisable(GL_CULL_FACE)
            glDisable(GL_DEPTH_TEST)

            glDisable(GL_TEXTURE_2D)
            glDisableClientState(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(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()
                try:
                    fn = self.level.chunkFilename(*c)
                except Exception:
                    fn = c

                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

            for dx, dz in ((1, 0), (0, 1), (-1, 0), (0, -1), (0, 0)):
                self.tryCompressChunk(cx + dx, cz + dz, (cx, cz))

    def tryCompressChunk(self, cx, cz, donePos):
        neighborChunkPos = [(cx + dx, cz + dz) for dx, dz in ((1, 0), (0, 1), (-1, 0), (0, -1))]
        neighborChunks = [self.chunkRenderers.get(c) for c in neighborChunkPos]
        doneChunks = [cr and cr.done or not self.level.containsChunk(*cPos) or cPos == donePos for cPos, cr in zip(neighborChunkPos, neighborChunks)]

        if all(doneChunks):
            self.level.compressChunk(cx, cz)


class PreviewRenderer(MCRenderer):
    isPreviewer = True


def rendermain():
    renderer = MCRenderer()

    renderer.level = 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
    glMatrixMode(GL_PROJECTION)
    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_MODELVIEW)
    GL.glLoadIdentity()

    GLU.gluLookAt(pos[0], pos[1], pos[2],
                   look[0], look[1], look[2],
                   up[0], up[1], up[2])

    glClearColor(0.0, 0.0, 0.0, 1.0)

    framestart = datetime.now()
    frames = 200
    for i in range(frames):
        glClear(GL_COLOR_BUFFER_BIT | 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
    # sleep(3.0)


import traceback
import cProfile

if __name__ == "__main__":
    cProfile.run("rendermain()", "mcedit.profile")
