Exemple #1
0
    def makeChunkVertices(self, chunk, limitBox):
        tilePositions = []
        defaultColor = (0xff, 0xff, 0x33, 0x44)
        for i, ref in enumerate(chunk.TileEntities):
            if i % 10 == 0:
                yield

            if limitBox and ref.Position not in limitBox:
                continue
            if ref.id == "Control":
                continue
            tilePositions.append(ref.Position)

        if not len(tilePositions):
            return

        tiles = self._computeVertices(tilePositions,
                                      defaultColor,
                                      chunkPosition=chunk.chunkPosition)

        vertexNode = VertexNode([tiles])
        polygonMode = PolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
        vertexNode.addState(polygonMode)
        lineWidth = LineWidth(2.0)
        vertexNode.addState(lineWidth)
        depthFunc = DepthFunc(GL.GL_ALWAYS)
        vertexNode.addState(depthFunc)

        self.sceneNode = Node("tileEntityLocations")
        self.sceneNode.addChild(vertexNode)

        vertexNode = VertexNode([tiles])
        self.sceneNode.addChild(vertexNode)
Exemple #2
0
    def makeChunkVertices(self, chunk, limitBox):
        monsterPositions = []
        for i, entityRef in enumerate(chunk.Entities):
            if i % 10 == 0:
                yield
            ID = entityRef.id

            if ID in self.notMonsters:
                continue
            pos = entityRef.Position
            if limitBox and pos not in limitBox:
                continue
            monsterPositions.append(pos)

        if not len(monsterPositions):
            return

        monsters = self._computeVertices(monsterPositions,
                                         (0xff, 0x22, 0x22, 0x44),
                                         offset=True,
                                         chunkPosition=chunk.chunkPosition)
        yield

        vertexNode = VertexNode(monsters)
        vertexNode.addState(PolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE))
        vertexNode.addState(LineWidth(2.0))
        vertexNode.addState(DepthFunc(GL.GL_ALWAYS))

        self.sceneNode = Node("monsterLocations")
        self.sceneNode.addChild(vertexNode)

        vertexNode = VertexNode(monsters)
        self.sceneNode.addChild(vertexNode)
Exemple #3
0
    def __init__(self, dimension, textureAtlas=None, geometryCache=None, bounds=None):
        super(WorldScene, self).__init__()

        self.dimension = dimension
        self.textureAtlas = textureAtlas
        self.depthOffset = DepthOffset(DepthOffsets.Renderer)
        self.addState(self.depthOffset)

        self.textureAtlasNode = Node()
        self.textureAtlasState = TextureAtlasState(textureAtlas)
        self.textureAtlasNode.addState(self.textureAtlasState)
        self.addChild(self.textureAtlasNode)

        self.renderstateNodes = {}
        for rsClass in renderstates.allRenderstates:
            rsNode = Node()
            rsNode.addState(rsClass())
            self.textureAtlasNode.addChild(rsNode)
            self.renderstateNodes[rsClass] = rsNode

        self.groupNodes = {}  # by renderstate
        self.chunkRenderInfo = {}
        self.visibleLayers = set(Layer.DefaultVisibleLayers)

        self.updateTask = SceneUpdateTask(self, textureAtlas)

        if geometryCache is None:
            geometryCache = GeometryCache()
        self.geometryCache = geometryCache

        self.showRedraw = False

        self.minlod = 0
        self.bounds = bounds
Exemple #4
0
    def createSceneGraph(self):
        sceneGraph = scenenode.Node("WorldView SceneGraph")
        self.worldScene = self.createWorldScene()
        self.worldScene.setVisibleLayers(
            self.layerToggleGroup.getVisibleLayers())

        clearNode = ClearNode()
        self.skyNode = sky.SkyNode()
        self.loadableChunksNode = loadablechunks.LoadableChunksNode(
            self.dimension)

        self.worldNode = Node("World Container")
        self.matrixState = MatrixState()
        self.worldNode.addState(self.matrixState)
        self._updateMatrices()

        self.worldNode.addChild(self.loadableChunksNode)
        self.worldNode.addChild(self.worldScene)
        self.worldNode.addChild(self.overlayNode)

        sceneGraph.addChild(clearNode)
        sceneGraph.addChild(self.skyNode)
        sceneGraph.addChild(self.worldNode)
        sceneGraph.addChild(self.compassNode)
        if self.cursorNode:
            self.worldNode.addChild(self.cursorNode)

        return sceneGraph
Exemple #5
0
    def makeChunkVertices(self, chunk, limitBox):
        monsterPositions = []
        for i, entityRef in enumerate(chunk.Entities):
            if i % 10 == 0:
                yield
            ID = entityRef.id

            if ID in self.notMonsters:
                continue
            pos = entityRef.Position
            if limitBox and pos not in limitBox:
                continue
            monsterPositions.append(pos)

        monsters = self._computeVertices(monsterPositions,
                                         (0xff, 0x22, 0x22, 0x44),
                                         offset=True,
                                         chunkPosition=chunk.chunkPosition)
        yield

        vertexNode = VertexNode(monsters)
        polyNode = PolygonModeNode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
        polyNode.addChild(vertexNode)
        lineNode = LineWidthNode(2.0)
        lineNode.addChild(polyNode)
        depthNode = DepthFuncNode(GL.GL_ALWAYS)
        depthNode.addChild(lineNode)

        self.sceneNode = Node()
        self.sceneNode.addChild(depthNode)

        vertexNode = VertexNode(monsters)
        self.sceneNode.addChild(vertexNode)
Exemple #6
0
class TileEntityLocationMesh(EntityMeshBase):
    layer = Layer.TileEntityLocations

    def makeChunkVertices(self, chunk, limitBox):
        tilePositions = []
        defaultColor = (0xff, 0xff, 0x33, 0x44)
        for i, ref in enumerate(chunk.TileEntities):
            if i % 10 == 0:
                yield

            if limitBox and ref.Position not in limitBox:
                continue
            if ref.id == "Control":
                continue
            tilePositions.append(ref.Position)

        tiles = self._computeVertices(tilePositions, defaultColor, chunkPosition=chunk.chunkPosition)

        vertexNode = VertexNode([tiles])
        polyNode = PolygonModeNode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
        polyNode.addChild(vertexNode)
        lineNode = LineWidthNode(2.0)
        lineNode.addChild(polyNode)
        depthNode = DepthFuncNode(GL.GL_ALWAYS)
        depthNode.addChild(lineNode)

        self.sceneNode = Node()
        self.sceneNode.addChild(depthNode)

        vertexNode = VertexNode([tiles])
        self.sceneNode.addChild(vertexNode)
Exemple #7
0
class TileEntityLocationMesh(EntityMeshBase):
    layer = Layer.TileEntityLocations

    def makeChunkVertices(self, chunk, limitBox):
        tilePositions = []
        defaultColor = (0xff, 0xff, 0x33, 0x44)
        for i, ref in enumerate(chunk.TileEntities):
            if i % 10 == 0:
                yield

            if limitBox and ref.Position not in limitBox:
                continue
            if ref.id == "Control":
                continue
            tilePositions.append(ref.Position)

        tiles = self._computeVertices(tilePositions,
                                      defaultColor,
                                      chunkPosition=chunk.chunkPosition)

        vertexNode = VertexNode([tiles])
        polyNode = PolygonModeNode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
        polyNode.addChild(vertexNode)
        lineNode = LineWidthNode(2.0)
        lineNode.addChild(polyNode)
        depthNode = DepthFuncNode(GL.GL_ALWAYS)
        depthNode.addChild(lineNode)

        self.sceneNode = Node()
        self.sceneNode.addChild(depthNode)

        vertexNode = VertexNode([tiles])
        self.sceneNode.addChild(vertexNode)
Exemple #8
0
class MonsterLocationRenderer(EntityMeshBase):
    layer = Layer.MonsterLocations
    notMonsters = {"Item", "XPOrb", "Painting", "ItemFrame"}

    def makeChunkVertices(self, chunk, limitBox):
        monsterPositions = []
        for i, entityRef in enumerate(chunk.Entities):
            if i % 10 == 0:
                yield
            ID = entityRef.id

            if ID in self.notMonsters:
                continue
            pos = entityRef.Position
            if limitBox and pos not in limitBox:
                continue
            monsterPositions.append(pos)

        monsters = self._computeVertices(monsterPositions,
                                         (0xff, 0x22, 0x22, 0x44),
                                         offset=True,
                                         chunkPosition=chunk.chunkPosition)
        yield

        vertexNode = VertexNode(monsters)
        vertexNode.addState(PolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE))
        vertexNode.addState(LineWidth(2.0))
        vertexNode.addState(DepthFunc(GL.GL_ALWAYS))

        self.sceneNode = Node()
        self.sceneNode.addChild(vertexNode)

        vertexNode = VertexNode(monsters)
        self.sceneNode.addChild(vertexNode)
Exemple #9
0
    def createSceneGraph(self):
        sceneGraph = scenenode.Node("WorldView SceneGraph")
        self.worldScene = self.createWorldScene()
        self.worldScene.setVisibleLayers(self.layerToggleGroup.getVisibleLayers())

        clearNode = ClearNode()
        self.skyNode = sky.SkyNode()
        self.loadableChunksNode = loadablechunks.LoadableChunksNode(self.dimension)

        self.worldNode = Node("World Container")
        self.matrixState = MatrixState()
        self.worldNode.addState(self.matrixState)
        self._updateMatrices()

        self.worldNode.addChild(self.loadableChunksNode)
        self.worldNode.addChild(self.worldScene)
        self.worldNode.addChild(self.overlayNode)

        sceneGraph.addChild(clearNode)
        sceneGraph.addChild(self.skyNode)
        sceneGraph.addChild(self.worldNode)
        sceneGraph.addChild(self.compassNode)
        if self.cursorNode:
            self.worldNode.addChild(self.cursorNode)

        return sceneGraph
Exemple #10
0
    def __init__(self, editorSession, *args, **kwargs):
        super(BrushTool, self).__init__(editorSession, *args, **kwargs)
        self.toolWidget = BrushToolWidget()
        self.brushMode = None
        self.brushLoader = None

        self.brushModesByName = {cls.name:cls() for cls in BrushModeClasses}
        modes = self.brushModesByName.values()
        modes.sort(key=lambda m: m.name)
        self.toolWidget.brushModeInput.setModes(modes)
        BrushModeSetting.connectAndCall(self.modeSettingChanged)

        self.cursorWorldScene = None
        self.cursorNode = Node("brushCursor")
        self.cursorTranslate = Translate()
        self.cursorNode.addState(self.cursorTranslate)

        self.toolWidget.xSpinSlider.setMinimum(1)
        self.toolWidget.ySpinSlider.setMinimum(1)
        self.toolWidget.zSpinSlider.setMinimum(1)

        self.toolWidget.xSpinSlider.valueChanged.connect(self.setX)
        self.toolWidget.ySpinSlider.valueChanged.connect(self.setY)
        self.toolWidget.zSpinSlider.valueChanged.connect(self.setZ)

        self.toolWidget.brushShapeInput.shapeChanged.connect(self.updateCursor)
        self.toolWidget.brushShapeInput.shapeOptionsChanged.connect(self.updateCursor)

        self.fillBlock = editorSession.worldEditor.blocktypes["stone"]

        self.brushSize = BrushSizeSetting.value(QtGui.QVector3D(5, 5, 5)).toTuple()  # calls updateCursor

        self.toolWidget.xSpinSlider.setValue(self.brushSize[0])
        self.toolWidget.ySpinSlider.setValue(self.brushSize[1])
        self.toolWidget.zSpinSlider.setValue(self.brushSize[2])
Exemple #11
0
    def makeChunkVertices(self, chunk, limitBox):
        monsterPositions = []
        for i, entityRef in enumerate(chunk.Entities):
            if i % 10 == 0:
                yield
            ID = entityRef.id

            if ID in self.notMonsters:
                continue
            pos = entityRef.Position
            if limitBox and pos not in limitBox:
                continue
            monsterPositions.append(pos)

        if not len(monsterPositions):
            return

        monsters = self._computeVertices(monsterPositions,
                                         (0xff, 0x22, 0x22, 0x44),
                                         offset=True,
                                         chunkPosition=chunk.chunkPosition)
        yield

        vertexNode = VertexNode(monsters)
        vertexNode.addState(PolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE))
        vertexNode.addState(LineWidth(2.0))
        vertexNode.addState(DepthFunc(GL.GL_ALWAYS))

        self.sceneNode = Node("monsterLocations")
        self.sceneNode.addChild(vertexNode)

        vertexNode = VertexNode(monsters)
        self.sceneNode.addChild(vertexNode)
Exemple #12
0
    def makeChunkVertices(self, chunk, limitBox):
        tilePositions = []
        defaultColor = (0xff, 0xff, 0x33, 0x44)
        for i, ref in enumerate(chunk.TileEntities):
            if i % 10 == 0:
                yield

            if limitBox and ref.Position not in limitBox:
                continue
            if ref.id == "Control":
                continue
            tilePositions.append(ref.Position)

        if not len(tilePositions):
            return

        tiles = self._computeVertices(tilePositions, defaultColor, chunkPosition=chunk.chunkPosition)

        vertexNode = VertexNode([tiles])
        polygonMode = PolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
        vertexNode.addState(polygonMode)
        lineWidth = LineWidth(2.0)
        vertexNode.addState(lineWidth)
        depthFunc = DepthFunc(GL.GL_ALWAYS)
        vertexNode.addState(depthFunc)

        self.sceneNode = Node("tileEntityLocations")
        self.sceneNode.addChild(vertexNode)

        vertexNode = VertexNode([tiles])
        self.sceneNode.addChild(vertexNode)
Exemple #13
0
def CommandVisuals(pos, commandObj):
    visualCls = _visualClasses.get(commandObj.name)
    if visualCls is None:
        log.warn("No command found for %s", commandObj.name)
        return Node("nullCommandVisuals")
    else:
        return visualCls(pos, commandObj)
Exemple #14
0
    def makeChunkVertices(self, chunk, limitBox):
        tilePositions = []
        tileColors = []
        defaultColor = (0xff, 0x33, 0x33, 0x44)
        for i, ref in enumerate(chunk.TileEntities):
            if i % 10 == 0:
                yield

            if limitBox and ref.Position not in limitBox:
                continue
            if ref.id == "Control":
                tilePositions.append(ref.Position)
                cmdText = ref.Command
                if len(cmdText):
                    if cmdText[0] == "/":
                        cmdText = cmdText[1:]
                    command, _ = cmdText.split(None, 1)
                    color = commandColor(command)
                    tileColors.append(color + (0x44,))
                else:
                    tileColors.append(defaultColor)
            else:
                continue

        tiles = self._computeVertices(tilePositions, tileColors, chunkPosition=chunk.chunkPosition)

        vertexNode = VertexNode([tiles])
        vertexNode.addState(PolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE))
        vertexNode.addState(LineWidth(2.0))
        vertexNode.addState(DepthFunc(GL.GL_ALWAYS))

        self.sceneNode = Node()
        self.sceneNode.addChild(vertexNode)
Exemple #15
0
    def makeChunkVertices(self, chunk, limitBox):
        monsterPositions = []
        for i, entityRef in enumerate(chunk.Entities):
            if i % 10 == 0:
                yield
            ID = entityRef.id

            if ID in self.notMonsters:
                continue
            pos = entityRef.Position
            if limitBox and pos not in limitBox:
                continue
            monsterPositions.append(pos)

        monsters = self._computeVertices(monsterPositions,
                                         (0xff, 0x22, 0x22, 0x44),
                                         offset=True,
                                         chunkPosition=chunk.chunkPosition)
        yield

        vertexNode = VertexNode(monsters)
        polyNode = PolygonModeNode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
        polyNode.addChild(vertexNode)
        lineNode = LineWidthNode(2.0)
        lineNode.addChild(polyNode)
        depthNode = DepthFuncNode(GL.GL_ALWAYS)
        depthNode.addChild(lineNode)

        self.sceneNode = Node()
        self.sceneNode.addChild(depthNode)

        vertexNode = VertexNode(monsters)
        self.sceneNode.addChild(vertexNode)
Exemple #16
0
    def __init__(self, editorSession, *args, **kwargs):
        super(BrushTool, self).__init__(editorSession, *args, **kwargs)
        self.toolWidget = BrushToolWidget()
        self.brushMode = None
        self.brushLoader = None

        self.brushModesByName = {
            cls.name: cls(self)
            for cls in BrushModeClasses
        }
        brushModes = self.brushModesByName.values()
        self.toolWidget.brushModeInput.setModes(brushModes)
        BrushModeSetting.connectAndCall(self.modeSettingChanged)

        self.cursorWorldScene = None
        self.cursorNode = Node("brushCursor")
        self.cursorTranslate = Translate()
        self.cursorNode.addState(self.cursorTranslate)

        self.toolWidget.xSpinSlider.setMinimum(1)
        self.toolWidget.ySpinSlider.setMinimum(1)
        self.toolWidget.zSpinSlider.setMinimum(1)

        self.toolWidget.xSpinSlider.valueChanged.connect(self.setX)
        self.toolWidget.ySpinSlider.valueChanged.connect(self.setY)
        self.toolWidget.zSpinSlider.valueChanged.connect(self.setZ)

        self.toolWidget.brushShapeInput.shapeChanged.connect(self.updateCursor)
        self.toolWidget.brushShapeInput.shapeOptionsChanged.connect(
            self.updateCursor)

        self.fillBlock = editorSession.worldEditor.blocktypes["stone"]

        self.brushSize = BrushSizeSetting.value(QtGui.QVector3D(
            5, 5, 5)).toTuple()  # calls updateCursor

        self.toolWidget.xSpinSlider.setValue(self.brushSize[0])
        self.toolWidget.ySpinSlider.setValue(self.brushSize[1])
        self.toolWidget.zSpinSlider.setValue(self.brushSize[2])
        self.toolWidget.hoverSpinSlider.setValue(1)

        self.dragPoints = []
Exemple #17
0
class CommandBlockLocationMesh(EntityMeshBase):
    layer = Layer.CommandBlockLocations

    def makeChunkVertices(self, chunk, limitBox):
        tilePositions = []
        tileColors = []
        defaultColor = (0xff, 0x33, 0x33, 0x44)
        for i, ref in enumerate(chunk.TileEntities):
            if i % 10 == 0:
                yield

            if limitBox and ref.Position not in limitBox:
                continue
            if ref.id == "Control":
                tilePositions.append(ref.Position)
                cmdText = ref.Command
                if len(cmdText):
                    if cmdText[0] == "/":
                        cmdText = cmdText[1:]
                    command, _ = cmdText.split(None, 1)
                    color = commandColor(command)
                    tileColors.append(color + (0x44, ))
                else:
                    tileColors.append(defaultColor)
            else:
                continue

        if not len(tileColors):
            return

        tiles = self._computeVertices(tilePositions,
                                      tileColors,
                                      chunkPosition=chunk.chunkPosition)

        vertexNode = VertexNode([tiles])
        vertexNode.addState(PolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_LINE))
        vertexNode.addState(LineWidth(2.0))
        vertexNode.addState(DepthFunc(GL.GL_ALWAYS))

        self.sceneNode = Node("commandBlockLocations")
        self.sceneNode.addChild(vertexNode)
Exemple #18
0
    def makeChunkVertices(self, chunk, limitBox):
        tilePositions = []
        tileColors = []
        defaultColor = (0xff, 0x33, 0x33, 0x44)
        for i, ref in enumerate(chunk.TileEntities):
            if i % 10 == 0:
                yield

            if limitBox and ref.Position not in limitBox:
                continue
            if ref.id == "Control":
                tilePositions.append(ref.Position)
                cmdText = ref.Command
                if len(cmdText):
                    if cmdText[0] == "/":
                        cmdText = cmdText[1:]
                    command, _ = cmdText.split(None, 1)
                    color = commandColor(command)
                    tileColors.append(color + (0x44, ))
                else:
                    tileColors.append(defaultColor)
            else:
                continue

        tiles = self._computeVertices(tilePositions,
                                      tileColors,
                                      chunkPosition=chunk.chunkPosition)

        vertexNode = VertexNode([tiles])
        polyNode = PolygonModeNode(GL.GL_FRONT_AND_BACK, GL.GL_LINE)
        polyNode.addChild(vertexNode)
        lineNode = LineWidthNode(2.0)
        lineNode.addChild(polyNode)
        depthNode = DepthFuncNode(GL.GL_ALWAYS)
        depthNode.addChild(lineNode)

        self.sceneNode = Node()
        self.sceneNode.addChild(depthNode)
Exemple #19
0
    def __init__(self, editorSession, *args, **kwargs):
        super(BrushTool, self).__init__(editorSession, *args, **kwargs)
        self.toolWidget = BrushToolWidget()
        self.brushMode = None
        self.brushLoader = None

        self.brushModesByName = {cls.name:cls(self) for cls in BrushModeClasses}
        brushModes = self.brushModesByName.values()
        self.toolWidget.brushModeInput.setModes(brushModes)
        BrushModeSetting.connectAndCall(self.modeSettingChanged)

        self.cursorWorldScene = None
        self.cursorBoxNode = None
        self.cursorNode = Node("brushCursor")
        self.cursorTranslate = Translate()
        self.cursorNode.addState(self.cursorTranslate)

        self.toolWidget.xSpinSlider.setMinimum(1)
        self.toolWidget.ySpinSlider.setMinimum(1)
        self.toolWidget.zSpinSlider.setMinimum(1)

        self.toolWidget.xSpinSlider.valueChanged.connect(self.setX)
        self.toolWidget.ySpinSlider.valueChanged.connect(self.setY)
        self.toolWidget.zSpinSlider.valueChanged.connect(self.setZ)

        self.toolWidget.brushShapeInput.shapeChanged.connect(self.updateCursor)
        self.toolWidget.brushShapeInput.shapeOptionsChanged.connect(self.updateCursor)

        self.brushSize = BrushSizeSetting.value(QtGui.QVector3D(5, 5, 5)).toTuple()  # calls updateCursor

        self.toolWidget.xSpinSlider.setValue(self.brushSize[0])
        self.toolWidget.ySpinSlider.setValue(self.brushSize[1])
        self.toolWidget.zSpinSlider.setValue(self.brushSize[2])
        self.toolWidget.hoverSpinSlider.setValue(1)

        self.dragPoints = []
def LineArcNode(p1, p2, color):
    arcSegments = 20

    rgba = [c * 255 for c in color]
    points = [p1]
    x, y, z = p1
    dx = p2[0] - p1[0]
    dz = p2[2] - p1[2]
    dx /= arcSegments
    dz /= arcSegments
    heightDiff = p2[1] - p1[1]
    # maxRise = 8

    # initial y-velocity
    dy = 0.3 if heightDiff >= 0 else -0.3
    dy += 2 * heightDiff / arcSegments

    # the height of p2 without gravity

    overshot = y + dy * arcSegments - p2[1]

    # needed gravity so the last point is p2
    ddy = -overshot / (arcSegments * (arcSegments-1) / 2)

    for i in range(arcSegments):
        y += dy
        dy += ddy
        x += dx
        z += dz
        points.append((x, y, z))

    arcNode = Node("lineArc")

    lineNode = LineStripNode(points, rgba)
    arcNode.addChild(lineNode)

    arcNode.addState(LineWidth(3.0))

    backLineNode = Node("lineArcBack")
    backLineNode.addChild(lineNode)
    arcNode.addChild(backLineNode)

    backLineNode.addState(DepthFunc(GL.GL_GREATER))
    backLineNode.addState(LineWidth(1.0))

    return arcNode
Exemple #21
0
def LineArcNode(p1, p2, color):
    arcSegments = 20

    rgba = [c * 255 for c in color]
    points = [p1]
    x, y, z = p1
    dx = p2[0] - p1[0]
    dz = p2[2] - p1[2]
    dx /= arcSegments
    dz /= arcSegments
    heightDiff = p2[1] - p1[1]
    # maxRise = 8

    # initial y-velocity
    dy = 0.3 if heightDiff >= 0 else -0.3
    dy += 2 * heightDiff / arcSegments

    # the height of p2 without gravity

    overshot = y + dy * arcSegments - p2[1]

    # needed gravity so the last point is p2
    ddy = -overshot / (arcSegments * (arcSegments - 1) / 2)

    for i in range(arcSegments):
        y += dy
        dy += ddy
        x += dx
        z += dz
        points.append((x, y, z))

    arcNode = Node("lineArc")

    lineNode = LineStripNode(points, rgba)
    arcNode.addChild(lineNode)

    arcNode.addState(LineWidth(3.0))

    backLineNode = Node("lineArcBack")
    backLineNode.addChild(lineNode)
    arcNode.addChild(backLineNode)

    backLineNode.addState(DepthFunc(GL.GL_GREATER))
    backLineNode.addState(LineWidth(1.0))

    return arcNode
Exemple #22
0
class WorldScene(scenenode.Node):
    def __init__(self, dimension, textureAtlas=None, geometryCache=None, bounds=None):
        super(WorldScene, self).__init__()

        self.dimension = dimension
        self.textureAtlas = textureAtlas
        self.depthOffset = DepthOffset(DepthOffsets.Renderer)
        self.addState(self.depthOffset)

        self.textureAtlasNode = Node()
        self.textureAtlasState = TextureAtlasState(textureAtlas)
        self.textureAtlasNode.addState(self.textureAtlasState)
        self.addChild(self.textureAtlasNode)

        self.renderstateNodes = {}
        for rsClass in renderstates.allRenderstates:
            rsNode = Node()
            rsNode.addState(rsClass())
            self.textureAtlasNode.addChild(rsNode)
            self.renderstateNodes[rsClass] = rsNode

        self.groupNodes = {}  # by renderstate
        self.chunkRenderInfo = {}
        self.visibleLayers = set(Layer.DefaultVisibleLayers)

        self.updateTask = SceneUpdateTask(self, textureAtlas)

        if geometryCache is None:
            geometryCache = GeometryCache()
        self.geometryCache = geometryCache

        self.showRedraw = False

        self.minlod = 0
        self.bounds = bounds

    def setTextureAtlas(self, textureAtlas):
        if textureAtlas is not self.textureAtlas:
            self.textureAtlas = textureAtlas
            self.textureAtlasState.textureAtlas = textureAtlas
            self.updateTask.textureAtlas = textureAtlas
            self.discardAllChunks()

    def chunkPositions(self):
        return self.chunkRenderInfo.iterkeys()

    def getRenderstateGroup(self, rsClass):
        groupNode = self.groupNodes.get(rsClass)
        if groupNode is None:
            groupNode = ChunkGroupNode()
            self.groupNodes[rsClass] = groupNode
            self.renderstateNodes[rsClass].addChild(groupNode)

        return groupNode

    def discardChunk(self, cx, cz):
        """
        Discard the chunk at the given position from the scene
        """
        for groupNode in self.groupNodes.itervalues():
            groupNode.discardChunkNode(cx, cz)
        self.chunkRenderInfo.pop((cx, cz), None)

    def discardChunks(self, chunks):
        for cx, cz in chunks:
            self.discardChunk(cx, cz)

    def discardAllChunks(self):
        for groupNode in self.groupNodes.itervalues():
            groupNode.clear()
        self.chunkRenderInfo.clear()

    def invalidateChunk(self, cx, cz, invalidLayers=None):
        """
        Mark the chunk for regenerating vertex data
        """
        if invalidLayers is None:
            invalidLayers = Layer.AllLayers

        node = self.chunkRenderInfo.get((cx, cz))
        if node:
            node.invalidLayers.update(invalidLayers)

    _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 wantsChunk(self, cPos):
        return self.updateTask.wantsChunk(cPos)

    def workOnChunk(self, chunk, visibleSections=None):
        return self.updateTask.workOnChunk(chunk, visibleSections)

    def chunkNotPresent(self, cPos):
        self.updateTask.chunkNotPresent(cPos)

    def getChunkRenderInfo(self, cPos):
        chunkInfo = self.chunkRenderInfo.get(cPos)
        if chunkInfo is None:
            #log.info("Creating ChunkRenderInfo %s in %s", cPos, self.worldScene)
            chunkInfo = ChunkRenderInfo(self, cPos)
            self.chunkRenderInfo[cPos] = chunkInfo

        return chunkInfo

    def setLayerVisible(self, layerName, visible):
        if visible:
            self.visibleLayers.add(layerName)
        else:
            self.visibleLayers.discard(layerName)

        for groupNode in self.groupNodes.itervalues():
            groupNode.setLayerVisible(layerName, visible)

    def setVisibleLayers(self, layerNames):
        self.visibleLayers = set(layerNames)
Exemple #23
0
class WorldView(QGLWidget):
    """
    Superclass for the following views:

    IsoWorldView: Display the world using an isometric viewing angle, without perspective.
    Click and drag to pan the viewing area. Use the mouse wheel or a UI control to zoom.

    CameraWorldView: Display the world using a first-person viewing angle with perspective.
    Click and drag to pan the camera. Use WASD or click and drag to move the camera.

    CutawayWorldView: Display a single slice of the world. Click and drag to move sideways.
    Use the mouse wheel or a UI widget to move forward or backward. Use a UI widget to zoom.

    FourUpWorldView: Display up to four other world views at once. Default to three cutaways
    and one isometric view.

    """
    viewportMoved = QtCore.Signal(QtGui.QWidget)
    cursorMoved = QtCore.Signal(QtGui.QMouseEvent)

    urlsDropped = QtCore.Signal(QtCore.QMimeData, Vector, faces.Face)
    mapItemDropped = QtCore.Signal(QtCore.QMimeData, Vector, faces.Face)

    mouseBlockPos = Vector(0, 0, 0)
    mouseBlockFace = faces.FaceYIncreasing

    doSwapBuffers = QtCore.Signal()

    def __init__(self,
                 dimension,
                 textureAtlas=None,
                 geometryCache=None,
                 sharedGLWidget=None):
        """

        :param dimension:
        :type dimension: WorldEditorDimension
        :param textureAtlas:
        :type textureAtlas: TextureAtlas
        :param geometryCache:
        :type geometryCache: GeometryCache
        :param sharedGLWidget:
        :type sharedGLWidget: QGLWidget
        :return:
        :rtype:
        """
        QGLWidget.__init__(self, shareWidget=sharedGLWidget)
        self.dimension = None
        self.worldScene = None
        self.loadableChunksNode = None
        self.textureAtlas = None

        validateWidgetQGLContext(self)

        self.bufferSwapDone = True

        if THREADED_BUFFER_SWAP:
            self.setAutoBufferSwap(False)
            self.bufferSwapThread = QtCore.QThread()
            self.bufferSwapper = BufferSwapper(self)
            self.bufferSwapper.moveToThread(self.bufferSwapThread)
            self.doSwapBuffers.connect(self.bufferSwapper.swap)
            self.bufferSwapThread.start()

        self.setAcceptDrops(True)
        self.setSizePolicy(QtGui.QSizePolicy.Policy.Expanding,
                           QtGui.QSizePolicy.Policy.Expanding)
        self.setFocusPolicy(Qt.ClickFocus)

        self.layerToggleGroup = LayerToggleGroup()
        self.layerToggleGroup.layerToggled.connect(self.setLayerVisible)

        self.mouseRay = Ray(Vector(0, 1, 0), Vector(0, -1, 0))

        self.setMouseTracking(True)

        self.lastAutoUpdate = time.time()
        self.autoUpdateInterval = 0.5  # frequency of screen redraws in response to loaded chunks

        self.compassNode = self.createCompass()
        self.compassOrtho = Ortho((1, float(self.height()) / self.width()))
        self.compassNode.addState(self.compassOrtho)

        self.viewActions = []
        self.pressedKeys = set()

        self.setTextureAtlas(textureAtlas)

        if geometryCache is None and sharedGLWidget is not None:
            geometryCache = sharedGLWidget.geometryCache
        if geometryCache is None:
            geometryCache = GeometryCache()
        self.geometryCache = geometryCache

        self.worldNode = None
        self.skyNode = None
        self.overlayNode = scenenode.Node("WorldView Overlay")

        self.sceneGraph = None
        self.renderGraph = None

        self.frameSamples = deque(maxlen=500)
        self.frameSamples.append(time.time())

        self.cursorNode = None

        self.setDimension(dimension)

    def waitForSwapThread(self):
        while not self.bufferSwapDone:
            QtGui.QApplication.processEvents(
                QtCore.QEventLoop.ExcludeUserInputEvents)

    def dealloc(self):
        log.info("Deallocating GL resources for worldview %s", self)
        if THREADED_BUFFER_SWAP:
            self.waitForSwapThread()
            self.bufferSwapThread.quit()
        self.makeCurrent()
        self.renderGraph.dealloc()

    def __str__(self):
        try:
            if self.dimension:
                dimName = displayName(self.dimension.worldEditor.filename
                                      ) + ": " + self.dimension.dimName
            else:
                dimName = "None"
        except Exception as e:
            return "%s trying to get node name" % e
        return "%s(%r)" % (self.__class__.__name__, dimName)

    # --- Displayed world ---

    def setDimension(self, dimension):
        """

        :param dimension:
        :type dimension: WorldEditorDimension
        :return:
        :rtype:
        """
        log.info("Changing %s to dimension %s", self, dimension)
        self.dimension = dimension
        self.waitForSwapThread()
        self.makeCurrent()
        if self.renderGraph:
            self.renderGraph.dealloc()
        self.sceneGraph = self.createSceneGraph()
        self.renderGraph = rendernode.createRenderNode(self.sceneGraph)
        self.resetLoadOrder()
        self.update()

    def setTextureAtlas(self, textureAtlas):
        self.textureAtlas = textureAtlas
        if self.worldScene:
            self.worldScene.setTextureAtlas(textureAtlas)

        if textureAtlas is not None:
            self.waitForSwapThread()
            self.makeCurrent()
            textureAtlas.load()
            self.resetLoadOrder()

    # --- Graph construction ---

    def createCompass(self):
        return compass.CompassNode()

    def createWorldScene(self):
        return worldscene.WorldScene(self.dimension, self.textureAtlas,
                                     self.geometryCache)

    def createSceneGraph(self):
        sceneGraph = scenenode.Node("WorldView SceneGraph")
        self.worldScene = self.createWorldScene()
        self.worldScene.setVisibleLayers(
            self.layerToggleGroup.getVisibleLayers())

        clearNode = ClearNode()
        self.skyNode = sky.SkyNode()
        self.loadableChunksNode = loadablechunks.LoadableChunksNode(
            self.dimension)

        self.worldNode = Node("World Container")
        self.matrixState = MatrixState()
        self.worldNode.addState(self.matrixState)
        self._updateMatrices()

        self.worldNode.addChild(self.loadableChunksNode)
        self.worldNode.addChild(self.worldScene)
        self.worldNode.addChild(self.overlayNode)

        sceneGraph.addChild(clearNode)
        sceneGraph.addChild(self.skyNode)
        sceneGraph.addChild(self.worldNode)
        sceneGraph.addChild(self.compassNode)
        if self.cursorNode:
            self.worldNode.addChild(self.cursorNode)

        return sceneGraph

    # --- Tool support ---

    def setToolCursor(self, cursorNode):
        if self.cursorNode:
            self.worldNode.removeChild(self.cursorNode)
        self.cursorNode = cursorNode
        if cursorNode:
            self.worldNode.addChild(cursorNode)

    def setToolOverlays(self, overlayNodes):
        self.overlayNode.clear()
        for node in overlayNodes:
            self.overlayNode.addChild(node)

    # --- View settings ---

    def setLayerVisible(self, layerName, visible):
        self.worldScene.setLayerVisible(layerName, visible)
        self.resetLoadOrder()

    def setDayTime(self, value):
        if self.skyNode:
            self.skyNode.setDayTime(value)

    def _updateMatrices(self):
        self.updateMatrices()
        self.updateFrustum()
        #min = self.unprojectPoint(0, 0)[0]
        #max = self.unprojectPoint(self.width(), self.height())[0]
        #self.visibleBox = BoundingBox(min, (0, 0, 0)).union(BoundingBox(max, (0, 0, 0)))

    def updateMatrices(self):
        """
        Subclasses must implement updateMatrices to set the projection and modelview matrices.

        Should set self.worldNode.projection and self.worldNode.modelview
        """
        raise NotImplementedError

    def updateFrustum(self):
        matrix = self.matrixState.projection * self.matrixState.modelview
        self.frustum = Frustum.fromViewingMatrix(numpy.array(matrix.data()))

    def getViewCorners(self):
        """
        Returns corners:
            bottom left, near
            bottom left, far
            top left, near
            top left, far
            bottom right, near
            bottom right, far
            top right, near
            top right, far

        :return:
        :rtype: list[QVector4D]
        """
        corners = [
            QtGui.QVector4D(x, y, z, 1.)
            for x, y, z in itertools.product((-1., 1.), (-1., 1.), (0., 1.))
        ]

        matrix = self.matrixState.projection * self.matrixState.modelview
        matrix, inverted = matrix.inverted()
        worldCorners = [matrix.map(corner) for corner in corners]
        worldCorners = [
            Vector(*((corner / corner.w()).toTuple()[:3]))
            for corner in worldCorners
        ]
        return worldCorners

    def getViewBounds(self):
        """
        Get the corners of the viewing area, intersected with the world's bounds.
        xxx raycast to intersect with terrain height too

        :return:
        :rtype:
        """
        corners = self.getViewCorners()
        # Convert the 4 corners into rays extending from the near point, then interpolate each ray at the
        # current dimension's height limits
        pairs = []
        for i in range(0, 8, 2):
            pairs.append(corners[i:i + 2])

        rays = [Ray.fromPoints(p1, p2) for p1, p2 in pairs]
        bounds = self.dimension.bounds
        pointPairs = [(r.atHeight(bounds.maxy), r.atHeight(bounds.miny))
                      for r in rays]

        return sum(pointPairs, ())

    scaleChanged = QtCore.Signal(float)

    _scale = 1. / 16

    @property
    def scale(self):
        return self._scale

    @scale.setter
    def scale(self, value):
        self._scale = value
        self._updateMatrices()
        log.debug("update(): scale %s %s", self, value)
        self.update()
        self.scaleChanged.emit(value)
        self.viewportMoved.emit(self)

    _centerPoint = Vector(0, 0, 0)

    @property
    def centerPoint(self):
        return self._centerPoint

    @centerPoint.setter
    def centerPoint(self, value):
        value = Vector(*value)
        if value != self._centerPoint:
            self._centerPoint = value
            self._updateMatrices()
            log.debug("update(): centerPoint %s %s", self, value)
            self.update()
            self.resetLoadOrder()
            self.viewportMoved.emit(self)

    def centerOnPoint(self, pos, distance=None):
        """Center the view on the given position"""
        # delta = self.viewCenter() - self.centerPoint
        # self.centerPoint = pos - delta
        self.centerPoint = pos
        self.update()

    def viewCenter(self):
        """Return the world position at the center of the view."""
        #return self.unprojectAtHeight(self.width() / 2, self.height() / 2, 64.)
        # ray = self.rayAtPosition(self.width() / 2, self.height() / 2)
        # try:
        #     point, face = raycast.rayCastInBounds(ray, self.dimension, 600)
        # except (raycast.MaxDistanceError, ValueError):
        #     point = ray.atHeight(0)
        # return point or ray.point
        return self.centerPoint

    # --- Events ---

    def resizeEvent(self, event):
        center = self.viewCenter()
        self.compassOrtho.size = (1, float(self.height()) / self.width())
        super(WorldView, self).resizeEvent(event)
        # log.info("WorldView: resized. moving to %s", center)
        # self.centerOnPoint(center)

    acceptableMimeTypes = [
        MimeFormats.MapItem,
    ]

    def dragEnterEvent(self, event):
        # xxx show drop preview as scene node
        print("DRAG ENTER. FORMATS:\n%s" % event.mimeData().formats())
        for mimeType in self.acceptableMimeTypes:
            if event.mimeData().hasFormat(mimeType):
                event.acceptProposedAction()
                return
        if event.mimeData().hasUrls():
            event.acceptProposedAction()

    def dropEvent(self, event):
        mimeData = event.mimeData()
        x = event.pos().x()
        y = event.pos().y()
        ray = self.rayAtPosition(x, y)
        dropPosition, face = self.rayCastInView(ray)

        if mimeData.hasFormat(MimeFormats.MapItem):
            self.mapItemDropped.emit(mimeData, dropPosition, face)
        elif mimeData.hasUrls:
            self.urlsDropped.emit(mimeData, dropPosition, face)

    def keyPressEvent(self, event):
        self.augmentKeyEvent(event)
        self.pressedKeys.add(event.key())
        for action in self.viewActions:
            if action.matchKeyEvent(event):
                action.keyPressEvent(event)

    def keyReleaseEvent(self, event):
        self.augmentKeyEvent(event)
        self.pressedKeys.discard(event.key())
        for action in self.viewActions:
            if action.matchKeyEvent(event):
                action.keyReleaseEvent(event)

    def mousePressEvent(self, event):
        self.augmentMouseEvent(event)
        for action in self.viewActions:
            if action.button & event.button():
                if action.matchModifiers(event):
                    action.mousePressEvent(event)

    def mouseMoveEvent(self, event):
        self.augmentMouseEvent(event)
        for action in self.viewActions:
            if not action.button or action.button == event.buttons(
            ) or action.button & event.buttons():
                # Important! mouseMove checks event.buttons(), press and release check event.button()
                if action.matchModifiers(event):
                    if not action.key or action.key in self.pressedKeys:
                        action.mouseMoveEvent(event)
        self.cursorMoved.emit(event)
        self.update()

    def mouseReleaseEvent(self, event):
        # Ignore modifiers on mouse release event and send mouse release to any
        # actions that are set to the given button. This handles this series of inputs,
        # for example:  Control Key down, Mouse1 down, Control Key up, Mouse1 up
        self.augmentMouseEvent(event)
        for action in self.viewActions:
            if action.button & event.button():
                action.mouseReleaseEvent(event)

    wheelPos = 0

    def wheelEvent(self, event):
        self.augmentMouseEvent(event)
        for action in self.viewActions:
            if action.acceptsMouseWheel and (
                (action.modifiers & event.modifiers())
                    or action.modifiers == event.modifiers()):
                self.wheelPos += event.delta()
                # event.delta reports eighths of a degree. a standard wheel tick is 15 degrees, or 120 eighths.
                # keep count of wheel position and emit an event for each 15 degrees turned.
                # xxx will we ever need sub-click precision for wheel events?
                clicks = 0
                while self.wheelPos >= 120:
                    self.wheelPos -= 120
                    clicks += 1
                while self.wheelPos <= -120:
                    self.wheelPos += 120
                    clicks -= 1

                if action.button == action.WHEEL_UP and clicks > 0:
                    for i in range(abs(clicks)):
                        action.keyPressEvent(event)

                if action.button == action.WHEEL_DOWN and clicks < 0:
                    for i in range(abs(clicks)):
                        action.keyPressEvent(event)

    def augmentMouseEvent(self, event):
        x, y = event.x(), event.y()
        return self.augmentEvent(x, y, event)

    def augmentKeyEvent(self, event):
        globalPos = QtGui.QCursor.pos()
        mousePos = self.mapFromGlobal(globalPos)
        x = mousePos.x()
        y = mousePos.y()

        # xxx fake position of mouse event -- need to use mcedit internal event object already
        event.x = lambda: x
        event.y = lambda: y

        return self.augmentEvent(x, y, event)

    @profiler.function
    def augmentEvent(self, x, y, event):
        ray = self.rayAtPosition(x, y)

        event.ray = ray
        event.view = self

        position, face = self.rayCastInView(ray)

        self.mouseBlockPos = event.blockPosition = position
        self.mouseBlockFace = event.blockFace = face
        self.mouseRay = ray

    # --- OpenGL support ---

    def initializeGL(self, *args, **kwargs):
        GL.glEnableClientState(GL.GL_VERTEX_ARRAY)
        GL.glAlphaFunc(GL.GL_NOTEQUAL, 0)
        GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA)
        GL.glEnable(GL.GL_DEPTH_TEST)

    def resizeGL(self, width, height):
        GL.glViewport(0, 0, width, height)

        self._updateMatrices()

    maxFPS = 45

    @profiler.function
    def glDraw(self, *args, **kwargs):
        if not self.bufferSwapDone:
            return
        frameInterval = 1.0 / self.maxFPS
        if time.time() - self.frameSamples[-1] < frameInterval:
            return
        super(WorldView, self).glDraw(*args, **kwargs)

    shouldRender = True

    def paintGL(self):
        if not self.shouldRender:
            return
        try:
            with profiler.context("paintGL: %s" % self):
                self.frameSamples.append(time.time())
                if self.textureAtlas:
                    self.textureAtlas.update()

                with profiler.context("renderScene"):
                    rendernode.renderScene(self.renderGraph)

            if THREADED_BUFFER_SWAP:
                self.doneCurrent()
                self.bufferSwapDone = False
                self.doSwapBuffers.emit()
        except:
            self.shouldRender = False
            raise

    def swapDone(self):
        self.bufferSwapDone = True

    @property
    def fps(self):
        samples = 3
        if len(self.frameSamples) <= samples:
            return 0.0

        return (samples - 1) / (self.frameSamples[-1] -
                                self.frameSamples[-samples])

    # --- Screen<->world space conversion ---

    def rayCastInView(self, ray):
        try:
            result = raycast.rayCastInBounds(ray,
                                             self.dimension,
                                             maxDistance=200)
            position, face = result

        except (raycast.MaxDistanceError, ValueError):
            # GL.glReadBuffer(GL.GL_BACK)
            # pixel = GL.glReadPixels(x, self.height() - y, 1, 1, GL.GL_DEPTH_COMPONENT, GL.GL_FLOAT)
            # depth = -1 + 2 * pixel[0, 0]
            # p = self.pointsAtPositions((x, y, depth))[0]
            #
            # face = faces.FaceYIncreasing
            # position = p.intfloor()
            defaultDistance = 200
            position = (ray.point + ray.vector * defaultDistance).intfloor()
            face = faces.FaceUp

        return position, face

    def rayAtPosition(self, x, y):
        """
        Given coordinates in screen space, return a ray in 3D space.

        Parameters:
            x and y are coordinates local to this QWidget

        :rtype: Ray
        """

        p0, p1 = self.pointsAtPositions((x, y, 0.0), (x, y, 1.0))
        return Ray(p0, (p1 - p0).normalize())

    def rayAtCenter(self):
        return self.rayAtPosition(self.width() / 2, self.height() / 2)

    def pointsAtPositions(self, *screenPoints):
        w = float(self.width())
        h = float(self.height())
        matrix = self.matrixState.projection * self.matrixState.modelview
        inverse, ok = matrix.inverted()

        if not ok or 0 in (w, h):
            return [Vector(0, 0, 0) for i in screenPoints]

        def _unproject():
            for x, y, z in screenPoints:
                x = float(x)
                y = float(y)
                y = h - y
                x = 2 * x / w - 1
                y = 2 * y / h - 1

                def v(p):
                    return Vector(p.x(), p.y(), p.z())

                yield v(inverse.map(QtGui.QVector3D(x, y, z)))

        return list(_unproject())

    def unprojectAtHeight(self, x, y, h):
        """
        Given coordinates in screen space, find the corresponding point in 3D space.

        Like rayAtPosition, but the third parameter is a height value in 3D space.
        """
        ray = self.rayAtPosition(x, y)
        return ray.atHeight(h)

    # --- Chunk loading ---

    _chunkIter = None

    def resetLoadOrder(self):
        self._chunkIter = None

    def makeChunkIter(self):
        x, y, z = self.viewCenter()
        return iterateChunks(
            x, z, 1 + max(self.width() * self.scale,
                          self.height() * self.scale) // 32)

    def requestChunk(self):
        if self._chunkIter is None:
            self._chunkIter = self.makeChunkIter()
        try:
            for c in self._chunkIter:
                if self.worldScene.wantsChunk(c):
                    return c
        except StopIteration:
            pass

    def wantsChunk(self, c):
        if not self.worldScene.wantsChunk(c):
            return False

        if hasattr(self, 'frustum'):
            point = [
                c[0] * 16 + 8,
                self.dimension.bounds.miny + self.dimension.bounds.height / 2,
                c[1] * 16 + 8, 1.0
            ]
            return self.frustum.visible1(point=point,
                                         radius=self.dimension.bounds.height /
                                         2)

        return True

    def chunkNotPresent(self, cPos):
        self.worldScene.chunkNotPresent(cPos)

    def recieveChunk(self, chunk):
        t = time.time()
        if self.lastAutoUpdate + self.autoUpdateInterval < t:
            self.lastAutoUpdate = t
            log.debug("update(): receivedChunk %s %s", self, chunk)
            self.update()

        with profiler.getProfiler().context("preloadCulling"):
            if hasattr(self, 'frustum'):
                cx, cz = chunk.chunkPosition
                points = [(cx * 16 + 8, h + 8, cz * 16 + 8, 1.0)
                          for h in chunk.sectionPositions()]
                points = numpy.array(points)

                visibleSections = self.frustum.visible(points,
                                                       radius=8 * math.sqrt(2))
            else:
                visibleSections = None

        return self.worldScene.workOnChunk(chunk, visibleSections)

    def chunkInvalid(self, (cx, cz), deleted):
        self.worldScene.invalidateChunk(cx, cz)
        if deleted:
            self.loadableChunksNode.dirty = True

        self.resetLoadOrder()
Exemple #24
0
class BrushTool(EditorTool):
    name = "Brush"
    iconName = "brush"
    maxBrushSize = 512

    def __init__(self, editorSession, *args, **kwargs):
        super(BrushTool, self).__init__(editorSession, *args, **kwargs)
        self.toolWidget = BrushToolWidget()
        self.brushMode = None
        self.brushLoader = None

        self.brushModesByName = {
            cls.name: cls(self)
            for cls in BrushModeClasses
        }
        brushModes = self.brushModesByName.values()
        self.toolWidget.brushModeInput.setModes(brushModes)
        BrushModeSetting.connectAndCall(self.modeSettingChanged)

        self.cursorWorldScene = None
        self.cursorNode = Node("brushCursor")
        self.cursorTranslate = Translate()
        self.cursorNode.addState(self.cursorTranslate)

        self.toolWidget.xSpinSlider.setMinimum(1)
        self.toolWidget.ySpinSlider.setMinimum(1)
        self.toolWidget.zSpinSlider.setMinimum(1)

        self.toolWidget.xSpinSlider.valueChanged.connect(self.setX)
        self.toolWidget.ySpinSlider.valueChanged.connect(self.setY)
        self.toolWidget.zSpinSlider.valueChanged.connect(self.setZ)

        self.toolWidget.brushShapeInput.shapeChanged.connect(self.updateCursor)
        self.toolWidget.brushShapeInput.shapeOptionsChanged.connect(
            self.updateCursor)

        self.fillBlock = editorSession.worldEditor.blocktypes["stone"]

        self.brushSize = BrushSizeSetting.value(QtGui.QVector3D(
            5, 5, 5)).toTuple()  # calls updateCursor

        self.toolWidget.xSpinSlider.setValue(self.brushSize[0])
        self.toolWidget.ySpinSlider.setValue(self.brushSize[1])
        self.toolWidget.zSpinSlider.setValue(self.brushSize[2])
        self.toolWidget.hoverSpinSlider.setValue(1)

        self.dragPoints = []

    @property
    def hoverDistance(self):
        return self.toolWidget.hoverSpinSlider.value()

    _brushSize = (0, 0, 0)

    @property
    def brushSize(self):
        return self._brushSize

    @brushSize.setter
    def brushSize(self, value):
        self._brushSize = value
        BrushSizeSetting.setValue(QtGui.QVector3D(*self.brushSize))
        self.updateCursor()

    def setX(self, val):
        x, y, z = self.brushSize
        x = float(val)
        self.brushSize = x, y, z

    def setY(self, val):
        x, y, z = self.brushSize
        y = float(val)
        self.brushSize = x, y, z

    def setZ(self, val):
        x, y, z = self.brushSize
        z = float(val)
        self.brushSize = x, y, z

    def setBlocktypes(self, types):
        if len(types) == 0:
            return

        self.fillBlock = types[0]
        self.updateCursor()

    def hoverPosition(self, event):
        if event.blockPosition:
            vector = (event.blockFace.vector * self.hoverDistance)
            pos = event.blockPosition + vector
            return pos

    def mousePress(self, event):
        self.dragPoints[:] = []
        pos = self.hoverPosition(event)
        if pos:
            self.dragPoints.append(pos)

    def mouseMove(self, event):
        pos = self.hoverPosition(event)
        if pos:
            self.cursorTranslate.translateOffset = pos

    def mouseDrag(self, event):
        p2 = self.hoverPosition(event)
        if p2:
            if len(self.dragPoints):
                p1 = self.dragPoints.pop(-1)
                points = list(bresenham.bresenham(p1, p2))
                self.dragPoints.extend(points)
            else:
                self.dragPoints.append(p2)

    def mouseRelease(self, event):
        if not len(self.dragPoints):
            pos = self.hoverPosition(event)
            if pos:
                self.dragPoints.append(pos)
        if len(self.dragPoints):
            dragPoints = sorted(set(self.dragPoints))
            self.dragPoints[:] = []
            command = BrushCommand(self.editorSession, dragPoints,
                                   self.options)
            self.editorSession.pushCommand(command)

    @property
    def options(self):
        options = {
            'brushSize': self.brushSize,
            'brushShape': self.brushShape,
            'brushMode': self.brushMode
        }
        options.update(self.brushMode.getOptions())
        return options

    def modeSettingChanged(self, value):
        self.brushMode = self.brushModesByName[value]
        stack = self.toolWidget.modeOptionsStack
        while stack.count():
            stack.removeWidget(stack.widget(0))
        if self.brushMode.optionsWidget:
            stack.addWidget(self.brushMode.optionsWidget)

    @property
    def brushShape(self):
        return self.toolWidget.brushShapeInput.currentShape

    def updateCursor(self):
        log.info("Updating brush cursor")
        if self.cursorWorldScene:
            self.brushLoader.timer.stop()
            self.cursorNode.removeChild(self.cursorWorldScene)
            self.cursorNode.removeChild(self.cursorBoxNode)

        cursorLevel = self.brushMode.createCursorLevel(self)
        if cursorLevel is not None:
            self.cursorWorldScene = worldscene.WorldScene(
                cursorLevel, self.editorSession.textureAtlas)
            self.cursorWorldScene.depthOffset.depthOffset = DepthOffsets.PreviewRenderer
            self.cursorNode.addChild(self.cursorWorldScene)

            self.brushLoader = WorldLoader(self.cursorWorldScene)
            self.brushLoader.startLoader()

        cursorBox = self.brushMode.brushBoxForPoint((0, 0, 0), self.options)
        if cursorBox is not None:
            self.cursorBoxNode = SelectionBoxNode()
            self.cursorBoxNode.selectionBox = cursorBox
            self.cursorBoxNode.filled = False

            self.cursorNode.addChild(self.cursorBoxNode)
Exemple #25
0
    def __init__(self, *args, **kwargs):
        EditorTool.__init__(self, *args, **kwargs)
        self.livePreview = False
        self.blockPreview = False
        self.glPreview = True

        toolWidget = QtGui.QWidget()

        self.toolWidget = toolWidget

        column = []
        self.generatorTypes = [pluginClass(self) for pluginClass in _pluginClasses]
        self.currentGenerator = None
        if len(self.generatorTypes):
            self.currentGenerator = self.generatorTypes[0]

        self.generatorTypeInput = QtGui.QComboBox()
        self.generatorTypesChanged()

        self.generatorTypeInput.currentIndexChanged.connect(self.currentTypeChanged)

        self.livePreviewCheckbox = QtGui.QCheckBox("Live Preview")
        self.livePreviewCheckbox.setChecked(self.livePreview)
        self.livePreviewCheckbox.toggled.connect(self.livePreviewToggled)

        self.blockPreviewCheckbox = QtGui.QCheckBox("Block Preview")
        self.blockPreviewCheckbox.setChecked(self.blockPreview)
        self.blockPreviewCheckbox.toggled.connect(self.blockPreviewToggled)

        self.glPreviewCheckbox = QtGui.QCheckBox("GL Preview")
        self.glPreviewCheckbox.setChecked(self.glPreview)
        self.glPreviewCheckbox.toggled.connect(self.glPreviewToggled)

        self.optionsHolder = QtGui.QStackedWidget()
        self.optionsHolder.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)

        self.generateButton = QtGui.QPushButton(self.tr("Generate"))
        self.generateButton.clicked.connect(self.generateClicked)

        column.append(self.generatorTypeInput)
        column.append(self.livePreviewCheckbox)
        column.append(self.blockPreviewCheckbox)
        column.append(self.glPreviewCheckbox)
        column.append(self.optionsHolder)
        column.append(self.generateButton)

        self.toolWidget.setLayout(Column(*column))

        self.overlayNode = scenenode.Node()

        self.sceneHolderNode = Node()
        self.sceneTranslate = Translate()
        self.sceneHolderNode.addState(self.sceneTranslate)

        self.overlayNode.addChild(self.sceneHolderNode)

        self.previewNode = None

        self.boxHandleNode = BoxHandle()
        self.boxHandleNode.boundsChanged.connect(self.boundsDidChange)
        self.boxHandleNode.boundsChangedDone.connect(self.boundsDidChangeDone)
        self.overlayNode.addChild(self.boxHandleNode)

        self.worldScene = None
        self.loader = None

        self.previewBounds = None
        self.schematicBounds = None
        self.currentSchematic = None

        self.currentTypeChanged(0)

        # Name of last selected generator plugin is saved after unloading
        # so it can be reselected if it is immediately reloaded
        self._lastTypeName = None

        _GeneratePlugins.instance.pluginAdded.connect(self.addPlugin)
        _GeneratePlugins.instance.pluginRemoved.connect(self.removePlugin)
Exemple #26
0
class PendingImportNode(Node, QtCore.QObject):
    __node_id_counter = 0

    def __init__(self, pendingImport, textureAtlas, hasHandle=True):
        """
        A scenegraph node displaying an object that will be imported later, including
        live and deferred views of the object with transformed items, and a BoxHandle
        for moving the item.

        Parameters
        ----------

        pendingImport: PendingImport
            The object to be imported. The PendingImportNode responds to changes in this
            object's position, rotation, and scale.
        textureAtlas: TextureAtlas
            The textures and block models used to render the preview of the object.

        Attributes
        ----------

        basePosition: Vector
            The pre-transform position of the pending import. This is equal to
            `self.pendingImport.basePosition` except when the node is currently being
            dragged.
        transformedPosition: Vector
            The post-transform position of the pending import. This is equal to
            `self.pendingImport.importPos` except when the node is currently being
            dragged, scaled, or rotated.
        """
        super(PendingImportNode, self).__init__()

        self.textureAtlas = textureAtlas
        self.pendingImport = pendingImport
        self.hasHandle = hasHandle

        dim = pendingImport.sourceDim

        self.transformedPosition = Vector(0, 0, 0)

        # worldScene is contained by rotateNode, and
        # translates the world scene back to 0, 0, 0 so the rotateNode can
        # rotate it around the anchor, and the plainSceneNode can translate
        # it to the current position.

        self.worldScene = WorldScene(dim, textureAtlas, bounds=pendingImport.selection)
        self.worldScene.depthOffset.depthOffset = DepthOffsets.PreviewRenderer

        self.worldSceneTranslate = Translate(-self.pendingImport.selection.origin)
        self.worldScene.addState(self.worldSceneTranslate)

        # rotateNode is used to rotate the non-transformed preview during live rotation

        self.rotateNode = Rotate3DNode()
        self.rotateNode.setAnchor(self.pendingImport.selection.size * 0.5)
        self.rotateNode.addChild(self.worldScene)

        # plainSceneNode contains the non-transformed preview of the imported
        # object, including its world scene. This preview will be rotated model-wise
        # while the user is dragging the rotate controls.

        self.plainSceneNode = Node("plainScene")
        self.positionTranslate = Translate()
        self.plainSceneNode.addState(self.positionTranslate)
        self.plainSceneNode.addChild(self.rotateNode)

        self.addChild(self.plainSceneNode)

        # transformedSceneNode contains the transformed preview of the imported
        # object, including a world scene that displays the object wrapped by a
        # DimensionTransform.

        self.transformedSceneNode = Node("transformedScene")
        self.transformedSceneTranslate = Translate()
        self.transformedSceneNode.addState(self.transformedSceneTranslate)

        self.transformedWorldScene = None
        self.addChild(self.transformedSceneNode)

        box = BoundingBox(pendingImport.importPos, pendingImport.importBounds.size)

        if hasHandle:
            # handleNode displays a bounding box that can be moved around, and responds
            # to mouse events.
            self.handleNode = BoxHandle()
            self.handleNode.bounds = box
            self.handleNode.resizable = False
            self.boxNode = None
        else:
            # boxNode displays a plain, non-movable bounding box
            self.boxNode = SelectionBoxNode()
            self.boxNode.wireColor = (1, 1, 1, .2)
            self.boxNode.filled = False
            self.handleNode = None
            self.addChild(self.boxNode)

        self.updateTransformedScene()
        self.basePosition = pendingImport.basePosition

        if hasHandle:
            self.handleNode.boundsChanged.connect(self.handleBoundsChanged)
            self.handleNode.boundsChangedDone.connect(self.handleBoundsChangedDone)

            self.addChild(self.handleNode)

        # loads the non-transformed world scene asynchronously.
        self.loader = WorldLoader(self.worldScene,
                                  list(pendingImport.selection.chunkPositions()))
        self.loader.startLoader(0.1 if self.hasHandle else 0.0)

        self.pendingImport.positionChanged.connect(self.setPosition)
        self.pendingImport.rotationChanged.connect(self.setRotation)

    # Emitted when the user finishes dragging the box handle and releases the mouse
    # button. Arguments are (newPosition, oldPosition).
    importMoved = QtCore.Signal(object, object)

    # Emitted while the user is dragging the box handle. Argument is the box origin.
    importIsMoving = QtCore.Signal(object)

    def handleBoundsChangedDone(self, bounds, oldBounds):
        point = self.getBaseFromTransformed(bounds.origin)
        oldPoint = self.getBaseFromTransformed(oldBounds.origin)
        if point != oldPoint:
            self.importMoved.emit(point, oldPoint)

    def handleBoundsChanged(self, bounds):
        self.setPreviewBasePosition(bounds.origin)

    def setPreviewBasePosition(self, origin):
        point = self.getBaseFromTransformed(origin)
        if self.basePosition != point:
            self.basePosition = point
            self.importIsMoving.emit(point)

    def getBaseFromTransformed(self, point):
        return point - self.pendingImport.transformOffset

    def setPreviewRotation(self, rots):
        self.plainSceneNode.visible = True
        self.transformedSceneNode.visible = False
        self.rotateNode.setRotation(rots)

    def setRotation(self, rots):
        self.updateTransformedScene()
        self.updateBoxHandle()
        self.rotateNode.setRotation(rots)

    def updateTransformedScene(self):
        if self.pendingImport.transformedDim is not None:
            log.info("Showing transformed scene")
            self.plainSceneNode.visible = False
            self.transformedSceneNode.visible = True

            if self.transformedWorldScene:
                self.transformedSceneNode.removeChild(self.transformedWorldScene)

            self.transformedWorldScene = WorldScene(self.pendingImport.transformedDim,
                                                    self.textureAtlas)
            self.transformedWorldScene.depthOffset.depthOffset = DepthOffsets.PreviewRenderer
            self.transformedSceneNode.addChild(self.transformedWorldScene)

            self.updateTransformedPosition()

            cPos = list(self.pendingImport.transformedDim.chunkPositions())
            self.loader = WorldLoader(self.transformedWorldScene,
                                      cPos)

            # ALERT!: self.hasHandle is overloaded with the meaning:
            #  "not the first clone in a repeated clone"
            self.loader.startLoader(0.1 if self.hasHandle else 0.0)

        else:
            log.info("Hiding transformed scene")
            self.plainSceneNode.visible = True
            self.transformedSceneNode.visible = False
            if self.transformedWorldScene:
                self.transformedSceneNode.removeChild(self.transformedWorldScene)
                self.transformedWorldScene = None

    def updateTransformedPosition(self):
        self.transformedPosition = self.basePosition + self.pendingImport.transformOffset
        self.transformedSceneTranslate.translateOffset = self.transformedPosition - self.pendingImport.importDim.bounds.origin

    def updateBoxHandle(self):
        if self.transformedWorldScene is None:
            bounds = BoundingBox(self.basePosition, self.pendingImport.bounds.size)
        else:
            origin = self.transformedPosition
            bounds = BoundingBox(origin, self.pendingImport.importBounds.size)
        #if self.handleNode.bounds.size != bounds.size:
        if self.hasHandle:
            self.handleNode.bounds = bounds
        else:
            self.boxNode.selectionBox = bounds

    @property
    def basePosition(self):
        return self.positionTranslate.translateOffset

    @basePosition.setter
    def basePosition(self, value):
        value = Vector(*value)
        if value == self.positionTranslate.translateOffset:
            return

        self.positionTranslate.translateOffset = value
        self.updateTransformedPosition()
        self.updateBoxHandle()

    def setPosition(self, pos):
        self.basePosition = pos

    # --- Mouse events ---

    # inherit from BoxHandle?
    def mouseMove(self, event):
        if not self.hasHandle:
            return
        self.handleNode.mouseMove(event)

    def mousePress(self, event):
        if not self.hasHandle:
            return
        self.handleNode.mousePress(event)

    def mouseRelease(self, event):
        if not self.hasHandle:
            return
        self.handleNode.mouseRelease(event)
Exemple #27
0
class GenerateTool(EditorTool):
    name = "Generate"
    iconName = "generate"

    instantDisplayChunks = 32
    modifiesWorld = True

    def __init__(self, *args, **kwargs):
        EditorTool.__init__(self, *args, **kwargs)
        self.livePreview = False
        self.blockPreview = False
        self.glPreview = True

        toolWidget = QtGui.QWidget()

        self.toolWidget = toolWidget

        column = []
        self.generatorTypes = [
            pluginClass(self)
            for pluginClass in GeneratePlugins.registeredPlugins
        ]
        self.currentGenerator = None
        if len(self.generatorTypes):
            self.currentGenerator = self.generatorTypes[0]

        self.generatorTypeInput = QtGui.QComboBox()
        self.generatorTypesChanged()

        self.generatorTypeInput.currentIndexChanged.connect(
            self.currentTypeChanged)

        self.livePreviewCheckbox = QtGui.QCheckBox("Live Preview")
        self.livePreviewCheckbox.setChecked(self.livePreview)
        self.livePreviewCheckbox.toggled.connect(self.livePreviewToggled)

        self.blockPreviewCheckbox = QtGui.QCheckBox("Block Preview")
        self.blockPreviewCheckbox.setChecked(self.blockPreview)
        self.blockPreviewCheckbox.toggled.connect(self.blockPreviewToggled)

        self.glPreviewCheckbox = QtGui.QCheckBox("GL Preview")
        self.glPreviewCheckbox.setChecked(self.glPreview)
        self.glPreviewCheckbox.toggled.connect(self.glPreviewToggled)

        self.optionsHolder = QtGui.QStackedWidget()
        self.optionsHolder.setSizePolicy(QtGui.QSizePolicy.Preferred,
                                         QtGui.QSizePolicy.Expanding)

        self.generateButton = QtGui.QPushButton(self.tr("Generate"))
        self.generateButton.clicked.connect(self.generateClicked)

        column.append(self.generatorTypeInput)
        column.append(self.livePreviewCheckbox)
        column.append(self.blockPreviewCheckbox)
        column.append(self.glPreviewCheckbox)
        column.append(self.optionsHolder)
        column.append(self.generateButton)

        self.toolWidget.setLayout(Column(*column))

        self.overlayNode = scenenode.Node("generateOverlay")

        self.sceneHolderNode = Node("sceneHolder")
        self.sceneTranslate = Translate()
        self.sceneHolderNode.addState(self.sceneTranslate)

        self.overlayNode.addChild(self.sceneHolderNode)

        self.previewNode = None

        self.boxHandleNode = BoxHandle()
        self.boxHandleNode.boundsChanged.connect(self.boundsDidChange)
        self.boxHandleNode.boundsChangedDone.connect(self.boundsDidChangeDone)
        self.overlayNode.addChild(self.boxHandleNode)

        self.worldScene = None
        self.loader = None

        self.previewBounds = None
        self.schematicBounds = None
        self.currentSchematic = None

        self.currentTypeChanged(0)

        # Name of last selected generator plugin is saved after unloading
        # so it can be reselected if it is immediately reloaded
        self._lastTypeName = None

        GeneratePlugins.pluginAdded.connect(self.addPlugin)
        GeneratePlugins.pluginRemoved.connect(self.removePlugin)

    def removePlugin(self, cls):
        log.info("Removing plugin %s", cls.__name__)
        self.generatorTypes[:] = [
            gt for gt in self.generatorTypes if not isinstance(gt, cls)
        ]
        self.generatorTypesChanged()
        if self.currentGenerator not in self.generatorTypes:
            lastTypeName = self.currentGenerator.__class__.__name__
            self.currentTypeChanged(0)  # resets self._lastTypeName
            self._lastTypeName = lastTypeName

    def addPlugin(self, cls):
        log.info("Adding plugin %s", cls.__name__)
        self.generatorTypes.append(cls(self))
        self.generatorTypesChanged()
        if self._lastTypeName is not None:
            if cls.__name__ == self._lastTypeName:
                self.currentTypeChanged(len(self.generatorTypes) - 1)

    def generatorTypesChanged(self):
        self.generatorTypeInput.clear()
        for gt in self.generatorTypes:
            if hasattr(gt, 'displayName'):
                displayName = gt.displayName
            else:
                displayName = gt.__class__.__name__
            self.generatorTypeInput.addItem(displayName, gt)

    def livePreviewToggled(self, value):
        self.livePreview = value

    def blockPreviewToggled(self, value):
        self.blockPreview = value
        if value:
            if self.currentSchematic:
                self.displaySchematic(self.currentSchematic,
                                      self.schematicBounds.origin)
            else:
                self.updateBlockPreview()
        else:
            self.clearSchematic()

    def glPreviewToggled(self, value):
        self.glPreview = value
        if value:
            self.updateNodePreview()  # xxx cache previewNode?
        else:
            self.clearNode()

    def currentTypeChanged(self, index):
        # user selected generator type after old type was unloaded, so forget the old type
        self._lastTypeName = None

        self.optionsHolder.removeWidget(self.optionsHolder.widget(0))
        if index < len(self.generatorTypes):
            self.currentGenerator = self.generatorTypes[index]
            self.optionsHolder.addWidget(
                self.currentGenerator.getOptionsWidget())
            self.updatePreview()
        else:
            self.currentGenerator = None
            self.clearSchematic()
            self.clearNode()

        log.info("Chose generator %s", repr(self.currentGenerator))

    def mousePress(self, event):
        self.boxHandleNode.mousePress(event)

    def mouseMove(self, event):
        self.boxHandleNode.mouseMove(event)

    def mouseRelease(self, event):
        self.boxHandleNode.mouseRelease(event)

    def boundsDidChange(self, bounds):
        # box still being resized
        if not self.currentGenerator:
            return

        if not self.livePreview:
            return

        self.previewBounds = bounds
        self.currentGenerator.boundsChanged(bounds)
        self.updatePreview()

    def boundsDidChangeDone(self, bounds, oldBounds):
        # box finished resize
        if not self.currentGenerator:
            return

        self.previewBounds = bounds
        self.schematicBounds = bounds
        self.currentGenerator.boundsChanged(bounds)
        self.updatePreview()

    def clearNode(self):
        if self.previewNode:
            self.overlayNode.removeChild(self.previewNode)
            self.previewNode = None

    def updatePreview(self):
        if self.blockPreview:
            self.updateBlockPreview()
        else:
            self.clearSchematic()

        if self.glPreview:
            self.updateNodePreview()
        else:
            self.clearNode()

    def updateBlockPreview(self):
        bounds = self.previewBounds

        if bounds is not None and bounds.volume > 0:
            self.generateNextSchematic(bounds)
        else:
            self.clearSchematic()

    def updateNodePreview(self):
        bounds = self.previewBounds
        if self.currentGenerator is None:
            return

        if bounds is not None and bounds.volume > 0:
            try:
                node = self.currentGenerator.getPreviewNode(bounds)
            except Exception:
                log.exception(
                    "Error while getting scene nodes from generator:")
            else:
                if node is not None:
                    self.clearNode()

                    if isinstance(node, list):
                        nodes = node
                        node = scenenode.Node("generatePreviewHolder")
                        for c in nodes:
                            node.addChild(c)

                    self.overlayNode.addChild(node)
                    self.previewNode = node

    def generateNextSchematic(self, bounds):
        if bounds is None:
            self.clearSchematic()
            return
        if self.currentGenerator is None:
            return

        try:
            schematic = self.currentGenerator.generate(
                bounds, self.editorSession.worldEditor.blocktypes)
            self.currentSchematic = schematic
            self.displaySchematic(schematic, bounds.origin)
        except Exception as e:
            log.exception("Error while running generator %s: %s",
                          self.currentGenerator, e)
            QtGui.QMessageBox.warning(
                QtGui.qApp.mainWindow, "Error while running generator",
                "An error occurred while running the generator: \n  %s.\n\n"
                "Traceback: %s" % (e, traceback.format_exc()))
            self.livePreview = False

    def displaySchematic(self, schematic, offset):
        if schematic is not None:
            dim = schematic.getDimension()

            if self.worldScene:
                self.sceneHolderNode.removeChild(self.worldScene)
                self.loader.timer.stop()
                self.loader = None

            atlas = self.editorSession.textureAtlas
            self.worldScene = WorldScene(dim, atlas)
            self.sceneTranslate.translateOffset = offset
            self.sceneHolderNode.addChild(self.worldScene)

            self.loader = WorldLoader(self.worldScene)
            if dim.chunkCount() <= self.instantDisplayChunks:
                exhaust(self.loader.work())
            else:
                self.loader.startLoader()
        else:
            self.clearSchematic()

    def clearSchematic(self):
        if self.worldScene:
            self.sceneHolderNode.removeChild(self.worldScene)
            self.worldScene = None
            self.loader.timer.stop()
            self.loader = None

    def generateClicked(self):
        if self.currentGenerator is None:
            return

        if self.schematicBounds is None:
            log.info("schematicBounds is None, not generating")
            return

        if self.currentSchematic is None:
            log.info("Generating new schematic for import")
            currentSchematic = self.currentGenerator.generate(
                self.schematicBounds,
                self.editorSession.worldEditor.blocktypes)
        else:
            log.info("Importing previously generated schematic.")
            currentSchematic = self.currentSchematic

        command = GenerateCommand(self, self.schematicBounds)
        try:
            with command.begin():
                if currentSchematic is not None:
                    task = self.editorSession.currentDimension.importSchematicIter(
                        currentSchematic, self.schematicBounds.origin)
                    showProgress(self.tr("Importing generated object..."),
                                 task)
                else:
                    task = self.currentGenerator.generateInWorld(
                        self.schematicBounds,
                        self.editorSession.currentDimension)
                    showProgress(self.tr("Generating object in world..."),
                                 task)
        except Exception as e:
            log.exception("Error while importing or generating in world: %r" %
                          e)
            command.undo()
        else:
            self.editorSession.pushCommand(command)
Exemple #28
0
class WorldView(QGLWidget):
    """
    Superclass for the following views:

    IsoWorldView: Display the world using an isometric viewing angle, without perspective.
    Click and drag to pan the viewing area. Use the mouse wheel or a UI control to zoom.

    CameraWorldView: Display the world using a first-person viewing angle with perspective.
    Click and drag to pan the camera. Use WASD or click and drag to move the camera.

    CutawayWorldView: Display a single slice of the world. Click and drag to move sideways.
    Use the mouse wheel or a UI widget to move forward or backward. Use a UI widget to zoom.

    FourUpWorldView: Display up to four other world views at once. Default to three cutaways
    and one isometric view.

    """
    viewportMoved = QtCore.Signal(QtGui.QWidget)
    cursorMoved = QtCore.Signal(QtGui.QMouseEvent)

    urlsDropped = QtCore.Signal(QtCore.QMimeData, Vector, faces.Face)
    mapItemDropped = QtCore.Signal(QtCore.QMimeData, Vector, faces.Face)

    mouseBlockPos = Vector(0, 0, 0)
    mouseBlockFace = faces.FaceYIncreasing

    doSwapBuffers = QtCore.Signal()

    def __init__(self, dimension, textureAtlas=None, geometryCache=None, sharedGLWidget=None):
        """

        :param dimension:
        :type dimension: WorldEditorDimension
        :param textureAtlas:
        :type textureAtlas: TextureAtlas
        :param geometryCache:
        :type geometryCache: GeometryCache
        :param sharedGLWidget:
        :type sharedGLWidget: QGLWidget
        :return:
        :rtype:
        """
        QGLWidget.__init__(self, shareWidget=sharedGLWidget)
        self.dimension = None
        self.worldScene = None
        self.loadableChunksNode = None
        self.textureAtlas = None

        validateWidgetQGLContext(self)


        self.bufferSwapDone = True

        if THREADED_BUFFER_SWAP:
            self.setAutoBufferSwap(False)
            self.bufferSwapThread = QtCore.QThread()
            self.bufferSwapper = BufferSwapper(self)
            self.bufferSwapper.moveToThread(self.bufferSwapThread)
            self.doSwapBuffers.connect(self.bufferSwapper.swap)
            self.bufferSwapThread.start()

        self.setAcceptDrops(True)
        self.setSizePolicy(QtGui.QSizePolicy.Policy.Expanding, QtGui.QSizePolicy.Policy.Expanding)
        self.setFocusPolicy(Qt.ClickFocus)

        self.layerToggleGroup = LayerToggleGroup()
        self.layerToggleGroup.layerToggled.connect(self.setLayerVisible)

        self.mouseRay = Ray(Vector(0, 1, 0), Vector(0, -1, 0))

        self.setMouseTracking(True)

        self.lastAutoUpdate = time.time()
        self.autoUpdateInterval = 0.5  # frequency of screen redraws in response to loaded chunks

        self.compassNode = self.createCompass()
        self.compassOrtho = Ortho((1, float(self.height()) / self.width()))
        self.compassNode.addState(self.compassOrtho)

        self.viewActions = []
        self.pressedKeys = set()

        self.setTextureAtlas(textureAtlas)

        if geometryCache is None and sharedGLWidget is not None:
            geometryCache = sharedGLWidget.geometryCache
        if geometryCache is None:
            geometryCache = GeometryCache()
        self.geometryCache = geometryCache

        self.worldNode = None
        self.skyNode = None
        self.overlayNode = scenenode.Node("WorldView Overlay")

        self.sceneGraph = None
        self.renderGraph = None

        self.frameSamples = deque(maxlen=500)
        self.frameSamples.append(time.time())

        self.cursorNode = None

        self.setDimension(dimension)

    def waitForSwapThread(self):
        while not self.bufferSwapDone:
            QtGui.QApplication.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents)


    def dealloc(self):
        log.info("Deallocating GL resources for worldview %s", self)
        if THREADED_BUFFER_SWAP:
            self.waitForSwapThread()
            self.bufferSwapThread.quit()
        self.makeCurrent()
        self.renderGraph.dealloc()

    def __str__(self):
        try:
            if self.dimension:
                dimName = displayName(self.dimension.worldEditor.filename) + ": " + self.dimension.dimName
            else:
                dimName = "None"
        except Exception as e:
            return "%s trying to get node name" % e
        return "%s(%r)" % (self.__class__.__name__, dimName)

    # --- Displayed world ---

    def setDimension(self, dimension):
        """

        :param dimension:
        :type dimension: WorldEditorDimension
        :return:
        :rtype:
        """
        log.info("Changing %s to dimension %s", self, dimension)
        self.dimension = dimension
        self.waitForSwapThread()
        self.makeCurrent()
        if self.renderGraph:
            self.renderGraph.dealloc()
        self.sceneGraph = self.createSceneGraph()
        self.renderGraph = rendernode.createRenderNode(self.sceneGraph)
        self.resetLoadOrder()
        self.update()

    def setTextureAtlas(self, textureAtlas):
        self.textureAtlas = textureAtlas
        if self.worldScene:
            self.worldScene.setTextureAtlas(textureAtlas)

        if textureAtlas is not None:
            self.waitForSwapThread()
            self.makeCurrent()
            textureAtlas.load()
            self.resetLoadOrder()

    # --- Graph construction ---

    def createCompass(self):
        return compass.CompassNode()

    def createWorldScene(self):
        return worldscene.WorldScene(self.dimension, self.textureAtlas, self.geometryCache)

    def createSceneGraph(self):
        sceneGraph = scenenode.Node("WorldView SceneGraph")
        self.worldScene = self.createWorldScene()
        self.worldScene.setVisibleLayers(self.layerToggleGroup.getVisibleLayers())

        clearNode = ClearNode()
        self.skyNode = sky.SkyNode()
        self.loadableChunksNode = loadablechunks.LoadableChunksNode(self.dimension)

        self.worldNode = Node("World Container")
        self.matrixState = MatrixState()
        self.worldNode.addState(self.matrixState)
        self._updateMatrices()

        self.worldNode.addChild(self.loadableChunksNode)
        self.worldNode.addChild(self.worldScene)
        self.worldNode.addChild(self.overlayNode)

        sceneGraph.addChild(clearNode)
        sceneGraph.addChild(self.skyNode)
        sceneGraph.addChild(self.worldNode)
        sceneGraph.addChild(self.compassNode)
        if self.cursorNode:
            self.worldNode.addChild(self.cursorNode)

        return sceneGraph

    # --- Tool support ---

    def setToolCursor(self, cursorNode):
        if self.cursorNode:
            self.worldNode.removeChild(self.cursorNode)
        self.cursorNode = cursorNode
        if cursorNode:
            self.worldNode.addChild(cursorNode)

    def setToolOverlays(self, overlayNodes):
        self.overlayNode.clear()
        for node in overlayNodes:
            self.overlayNode.addChild(node)

    # --- View settings ---

    def setLayerVisible(self, layerName, visible):
        self.worldScene.setLayerVisible(layerName, visible)
        self.resetLoadOrder()

    def setDayTime(self, value):
        if self.skyNode:
            self.skyNode.setDayTime(value)

    def _updateMatrices(self):
        self.updateMatrices()
        self.updateFrustum()
        #min = self.unprojectPoint(0, 0)[0]
        #max = self.unprojectPoint(self.width(), self.height())[0]
        #self.visibleBox = BoundingBox(min, (0, 0, 0)).union(BoundingBox(max, (0, 0, 0)))

    def updateMatrices(self):
        """
        Subclasses must implement updateMatrices to set the projection and modelview matrices.

        Should set self.worldNode.projection and self.worldNode.modelview
        """
        raise NotImplementedError

    def updateFrustum(self):
        matrix = self.matrixState.projection * self.matrixState.modelview
        self.frustum = Frustum.fromViewingMatrix(numpy.array(matrix.data()))

    def getViewCorners(self):
        """
        Returns corners:
            bottom left, near
            bottom left, far
            top left, near
            top left, far
            bottom right, near
            bottom right, far
            top right, near
            top right, far

        :return:
        :rtype: list[QVector4D]
        """
        corners = [QtGui.QVector4D(x, y, z, 1.)
                   for x, y, z in itertools.product((-1., 1.), (-1., 1.), (0., 1.))]

        matrix = self.matrixState.projection * self.matrixState.modelview
        matrix, inverted = matrix.inverted()
        worldCorners = [matrix.map(corner) for corner in corners]
        worldCorners = [Vector(*((corner / corner.w()).toTuple()[:3])) for corner in worldCorners]
        return worldCorners

    def getViewBounds(self):
        """
        Get the corners of the viewing area, intersected with the world's bounds.
        xxx raycast to intersect with terrain height too

        :return:
        :rtype:
        """
        corners = self.getViewCorners()
        # Convert the 4 corners into rays extending from the near point, then interpolate each ray at the
        # current dimension's height limits
        pairs = []
        for i in range(0, 8, 2):
            pairs.append(corners[i:i+2])

        rays = [Ray.fromPoints(p1, p2) for p1, p2 in pairs]
        bounds = self.dimension.bounds
        pointPairs = [(r.atHeight(bounds.maxy), r.atHeight(bounds.miny)) for r in rays]

        return sum(pointPairs, ())

    scaleChanged = QtCore.Signal(float)

    _scale = 1. / 16

    @property
    def scale(self):
        return self._scale

    @scale.setter
    def scale(self, value):
        self._scale = value
        self._updateMatrices()
        log.debug("update(): scale %s %s", self, value)
        self.update()
        self.scaleChanged.emit(value)
        self.viewportMoved.emit(self)

    _centerPoint = Vector(0, 0, 0)

    @property
    def centerPoint(self):
        return self._centerPoint

    @centerPoint.setter
    def centerPoint(self, value):
        value = Vector(*value)
        if value != self._centerPoint:
            self._centerPoint = value
            self._updateMatrices()
            log.debug("update(): centerPoint %s %s", self, value)
            self.update()
            self.resetLoadOrder()
            self.viewportMoved.emit(self)

    def centerOnPoint(self, pos, distance=None):
        """Center the view on the given position"""
        # delta = self.viewCenter() - self.centerPoint
        # self.centerPoint = pos - delta
        self.centerPoint = pos
        self.update()

    def viewCenter(self):
        """Return the world position at the center of the view."""
        #return self.unprojectAtHeight(self.width() / 2, self.height() / 2, 64.)
        # ray = self.rayAtPosition(self.width() / 2, self.height() / 2)
        # try:
        #     point, face = raycast.rayCastInBounds(ray, self.dimension, 600)
        # except (raycast.MaxDistanceError, ValueError):
        #     point = ray.atHeight(0)
        # return point or ray.point
        return self.centerPoint

    # --- Events ---

    def resizeEvent(self, event):
        center = self.viewCenter()
        self.compassOrtho.size = (1, float(self.height()) / self.width())
        super(WorldView, self).resizeEvent(event)
        # log.info("WorldView: resized. moving to %s", center)
        # self.centerOnPoint(center)

    acceptableMimeTypes = [
        MimeFormats.MapItem,
    ]

    def dragEnterEvent(self, event):
        # xxx show drop preview as scene node
        print("DRAG ENTER. FORMATS:\n%s" % event.mimeData().formats())
        for mimeType in self.acceptableMimeTypes:
            if event.mimeData().hasFormat(mimeType):
                event.acceptProposedAction()
                return
        if event.mimeData().hasUrls():
            event.acceptProposedAction()

    def dropEvent(self, event):
        mimeData = event.mimeData()
        x = event.pos().x()
        y = event.pos().y()
        ray = self.rayAtPosition(x, y)
        dropPosition, face = self.rayCastInView(ray)

        if mimeData.hasFormat(MimeFormats.MapItem):
            self.mapItemDropped.emit(mimeData, dropPosition, face)
        elif mimeData.hasUrls:
            self.urlsDropped.emit(mimeData, dropPosition, face)

    def keyPressEvent(self, event):
        self.augmentKeyEvent(event)
        self.pressedKeys.add(event.key())
        for action in self.viewActions:
            if action.matchKeyEvent(event):
                action.keyPressEvent(event)

    def keyReleaseEvent(self, event):
        self.augmentKeyEvent(event)
        self.pressedKeys.discard(event.key())
        for action in self.viewActions:
            if action.matchKeyEvent(event):
                action.keyReleaseEvent(event)

    def mousePressEvent(self, event):
        self.augmentMouseEvent(event)
        for action in self.viewActions:
            if action.button & event.button():
                if action.matchModifiers(event):
                    if not action.key or action.key in self.pressedKeys:
                        action.mousePressEvent(event)

    def mouseMoveEvent(self, event):
        self.augmentMouseEvent(event)
        for action in self.viewActions:
            if not action.button or action.button == event.buttons() or action.button & event.buttons():
                # Important! mouseMove checks event.buttons(), press and release check event.button()
                if action.matchModifiers(event):
                    if not action.key or action.key in self.pressedKeys:
                        action.mouseMoveEvent(event)
        self.cursorMoved.emit(event)
        self.update()

    def mouseReleaseEvent(self, event):
        self.augmentMouseEvent(event)
        for action in self.viewActions:
            if action.button & event.button():
                if action.matchModifiers(event):
                    if not action.key or action.key in self.pressedKeys:
                        action.mouseReleaseEvent(event)

    wheelPos = 0

    def wheelEvent(self, event):
        self.augmentMouseEvent(event)
        for action in self.viewActions:
            if action.acceptsMouseWheel and ((action.modifiers & event.modifiers()) or action.modifiers == event.modifiers()):
                self.wheelPos += event.delta()
                # event.delta reports eighths of a degree. a standard wheel tick is 15 degrees, or 120 eighths.
                # keep count of wheel position and emit an event for each 15 degrees turned.
                # xxx will we ever need sub-click precision for wheel events?
                clicks = 0
                while self.wheelPos >= 120:
                    self.wheelPos -= 120
                    clicks += 1
                while self.wheelPos <= -120:
                    self.wheelPos += 120
                    clicks -= 1

                if action.button == action.WHEEL_UP and clicks > 0:
                    for i in range(abs(clicks)):
                        action.keyPressEvent(event)

                if action.button == action.WHEEL_DOWN and clicks < 0:
                    for i in range(abs(clicks)):
                        action.keyPressEvent(event)

    def augmentMouseEvent(self, event):
        x, y = event.x(), event.y()
        return self.augmentEvent(x, y, event)

    def augmentKeyEvent(self, event):
        globalPos = QtGui.QCursor.pos()
        mousePos = self.mapFromGlobal(globalPos)
        x = mousePos.x()
        y = mousePos.y()

        # xxx fake position of mouse event -- need to use mcedit internal event object already
        event.x = lambda: x
        event.y = lambda: y

        return self.augmentEvent(x, y, event)

    @profiler.function
    def augmentEvent(self, x, y, event):
        ray = self.rayAtPosition(x, y)

        event.ray = ray
        event.view = self

        position, face = self.rayCastInView(ray)

        self.mouseBlockPos = event.blockPosition = position
        self.mouseBlockFace = event.blockFace = face
        self.mouseRay = ray

    # --- OpenGL support ---

    def initializeGL(self, *args, **kwargs):
        GL.glEnableClientState(GL.GL_VERTEX_ARRAY)
        GL.glAlphaFunc(GL.GL_NOTEQUAL, 0)
        GL.glBlendFunc(GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA)
        GL.glEnable(GL.GL_DEPTH_TEST)

    def resizeGL(self, width, height):
        GL.glViewport(0, 0, width, height)

        self._updateMatrices()

    maxFPS = 45

    @profiler.function
    def glDraw(self, *args, **kwargs):
        if not self.bufferSwapDone:
            return
        frameInterval = 1.0 / self.maxFPS
        if time.time() - self.frameSamples[-1] < frameInterval:
            return
        super(WorldView, self).glDraw(*args, **kwargs)

    shouldRender = True

    def paintGL(self):
        if not self.shouldRender:
            return
        try:
            with profiler.context("paintGL: %s" % self):
                self.frameSamples.append(time.time())
                if self.textureAtlas:
                    self.textureAtlas.update()

                with profiler.context("renderScene"):
                    rendernode.renderScene(self.renderGraph)

            if THREADED_BUFFER_SWAP:
                self.doneCurrent()
                self.bufferSwapDone = False
                self.doSwapBuffers.emit()
        except:
            self.shouldRender = False
            raise

    def swapDone(self):
        self.bufferSwapDone = True

    @property
    def fps(self):
        samples = 3
        if len(self.frameSamples) <= samples:
            return 0.0

        return (samples - 1) / (self.frameSamples[-1] - self.frameSamples[-samples])

    # --- Screen<->world space conversion ---

    def rayCastInView(self, ray):
        try:
            result = raycast.rayCastInBounds(ray, self.dimension, maxDistance=200)
            position, face = result

        except (raycast.MaxDistanceError, ValueError):
            # GL.glReadBuffer(GL.GL_BACK)
            # pixel = GL.glReadPixels(x, self.height() - y, 1, 1, GL.GL_DEPTH_COMPONENT, GL.GL_FLOAT)
            # depth = -1 + 2 * pixel[0, 0]
            # p = self.pointsAtPositions((x, y, depth))[0]
            #
            # face = faces.FaceYIncreasing
            # position = p.intfloor()
            defaultDistance = 200
            position = (ray.point + ray.vector * defaultDistance).intfloor()
            face = faces.FaceUp

        return position, face

    def rayAtPosition(self, x, y):
        """
        Given coordinates in screen space, return a ray in 3D space.

        Parameters:
            x and y are coordinates local to this QWidget

        :rtype: Ray
        """

        p0, p1 = self.pointsAtPositions((x, y, 0.0), (x, y, 1.0))
        return Ray(p0, (p1 - p0).normalize())

    def rayAtCenter(self):
        return self.rayAtPosition(self.width()/2, self.height()/2)

    def pointsAtPositions(self, *screenPoints):
        w = float(self.width())
        h = float(self.height())
        matrix = self.matrixState.projection * self.matrixState.modelview
        inverse, ok = matrix.inverted()

        if not ok or 0 in (w, h):
            return [Vector(0, 0, 0) for i in screenPoints]

        def _unproject():
            for x, y, z in screenPoints:
                x = float(x)
                y = float(y)
                y = h - y
                x = 2 * x / w - 1
                y = 2 * y / h - 1

                def v(p):
                    return Vector(p.x(), p.y(), p.z())

                yield v(inverse.map(QtGui.QVector3D(x, y, z)))

        return list(_unproject())

    def unprojectAtHeight(self, x, y, h):
        """
        Given coordinates in screen space, find the corresponding point in 3D space.

        Like rayAtPosition, but the third parameter is a height value in 3D space.
        """
        ray = self.rayAtPosition(x, y)
        return ray.atHeight(h)

    # --- Chunk loading ---

    _chunkIter = None

    def resetLoadOrder(self):
        self._chunkIter = None

    def makeChunkIter(self):
        x, y, z = self.viewCenter()
        return iterateChunks(x, z, 1 + max(self.width() * self.scale, self.height() * self.scale) // 32)

    def requestChunk(self):
        if self._chunkIter is None:
            self._chunkIter = self.makeChunkIter()
        try:
            for c in self._chunkIter:
                if self.worldScene.wantsChunk(c):
                    return c
        except StopIteration:
            pass

    def wantsChunk(self, c):
        if not self.worldScene.wantsChunk(c):
            return False

        if hasattr(self, 'frustum'):
            point = [
                c[0] * 16 + 8,
                self.dimension.bounds.miny + self.dimension.bounds.height / 2,
                c[1] * 16 + 8,
                1.0
            ]
            return self.frustum.visible1(point=point, radius=self.dimension.bounds.height / 2)

        return True

    def chunkNotPresent(self, cPos):
        self.worldScene.chunkNotPresent(cPos)

    def recieveChunk(self, chunk):
        t = time.time()
        if self.lastAutoUpdate + self.autoUpdateInterval < t:
            self.lastAutoUpdate = t
            log.debug("update(): receivedChunk %s %s", self, chunk)
            self.update()

        with profiler.getProfiler().context("preloadCulling"):
            if hasattr(self, 'frustum'):
                cx, cz = chunk.chunkPosition
                points = [(cx * 16 + 8, h + 8, cz * 16 + 8, 1.0)
                          for h in chunk.sectionPositions()]
                points = numpy.array(points)

                visibleSections = self.frustum.visible(points, radius=8 * math.sqrt(2))
            else:
                visibleSections = None

        return self.worldScene.workOnChunk(chunk, visibleSections)

    def chunkInvalid(self, (cx, cz), deleted):
        self.worldScene.invalidateChunk(cx, cz)
        if deleted:
            self.loadableChunksNode.dirty = True
            
        self.resetLoadOrder()
Exemple #29
0
class BrushTool(EditorTool):
    name = "Brush"
    iconName = "brush"
    maxBrushSize = 512

    modifiesWorld = True

    def __init__(self, editorSession, *args, **kwargs):
        super(BrushTool, self).__init__(editorSession, *args, **kwargs)
        self.toolWidget = BrushToolWidget()
        self.brushMode = None
        self.brushLoader = None

        self.brushModesByName = {cls.name:cls(self) for cls in BrushModeClasses}
        brushModes = self.brushModesByName.values()
        self.toolWidget.brushModeInput.setModes(brushModes)
        BrushModeSetting.connectAndCall(self.modeSettingChanged)

        self.cursorWorldScene = None
        self.cursorBoxNode = None
        self.cursorNode = Node("brushCursor")
        self.cursorTranslate = Translate()
        self.cursorNode.addState(self.cursorTranslate)

        self.toolWidget.xSpinSlider.setMinimum(1)
        self.toolWidget.ySpinSlider.setMinimum(1)
        self.toolWidget.zSpinSlider.setMinimum(1)

        self.toolWidget.xSpinSlider.valueChanged.connect(self.setX)
        self.toolWidget.ySpinSlider.valueChanged.connect(self.setY)
        self.toolWidget.zSpinSlider.valueChanged.connect(self.setZ)

        self.toolWidget.brushShapeInput.shapeChanged.connect(self.updateCursor)
        self.toolWidget.brushShapeInput.shapeOptionsChanged.connect(self.updateCursor)

        self.brushSize = BrushSizeSetting.value(QtGui.QVector3D(5, 5, 5)).toTuple()  # calls updateCursor

        self.toolWidget.xSpinSlider.setValue(self.brushSize[0])
        self.toolWidget.ySpinSlider.setValue(self.brushSize[1])
        self.toolWidget.zSpinSlider.setValue(self.brushSize[2])
        self.toolWidget.hoverSpinSlider.setValue(1)

        self.dragPoints = []

    @property
    def hoverDistance(self):
        return self.toolWidget.hoverSpinSlider.value()

    _brushSize = (0, 0, 0)

    @property
    def brushSize(self):
        return self._brushSize

    @brushSize.setter
    def brushSize(self, value):
        self._brushSize = value
        BrushSizeSetting.setValue(QtGui.QVector3D(*self.brushSize))
        self.updateCursor()

    def setX(self, val):
        x, y, z = self.brushSize
        x = float(val)
        self.brushSize = x, y, z

    def setY(self, val):
        x, y, z = self.brushSize
        y = float(val)
        self.brushSize = x, y, z

    def setZ(self, val):
        x, y, z = self.brushSize
        z = float(val)
        self.brushSize = x, y, z

    def hoverPosition(self, event):
        if event.blockPosition:
            vector = (event.blockFace.vector * self.hoverDistance)
            pos = event.blockPosition + vector
            return pos

    def mousePress(self, event):
        self.dragPoints[:] = []
        pos = self.hoverPosition(event)
        if pos:
            self.dragPoints.append(pos)
            
    def mouseMove(self, event):
        pos = self.hoverPosition(event)
        if pos:
            self.cursorTranslate.translateOffset = pos

    def mouseDrag(self, event):
        p2 = self.hoverPosition(event)
        if p2:
            if len(self.dragPoints):
                p1 = self.dragPoints.pop(-1)
                points = list(bresenham.bresenham(p1, p2))
                self.dragPoints.extend(points)
            else:
                self.dragPoints.append(p2)

    def mouseRelease(self, event):
        if not len(self.dragPoints):
            pos = self.hoverPosition(event)
            if pos:
                self.dragPoints.append(pos)
        if len(self.dragPoints):
            dragPoints = sorted(set(self.dragPoints))
            self.dragPoints[:] = []
            command = BrushCommand(self.editorSession, dragPoints, self.options)
            self.editorSession.pushCommand(command)

    @property
    def options(self):
        options = {'brushSize': self.brushSize,
                   'brushShape': self.brushShape,
                   'brushMode': self.brushMode,
                   'brushHollow': self.brushHollow}
        options.update(self.brushMode.getOptions())
        return options

    def modeSettingChanged(self, value):
        self.brushMode = self.brushModesByName[value]
        stack = self.toolWidget.modeOptionsStack
        while stack.count():
            stack.removeWidget(stack.widget(0))
        if self.brushMode.optionsWidget:
            stack.addWidget(self.brushMode.optionsWidget)

    @property
    def brushShape(self):
        return self.toolWidget.brushShapeInput.currentShape

    @property
    def brushHollow(self):
        return self.toolWidget.hollowCheckBox.isChecked()

    def updateCursor(self):
        log.info("Updating brush cursor")
        if self.cursorWorldScene:
            self.brushLoader.timer.stop()
            self.cursorNode.removeChild(self.cursorWorldScene)
            self.cursorWorldScene = None

        if self.cursorBoxNode:
            self.cursorNode.removeChild(self.cursorBoxNode)
            self.cursorBoxNode = None

        cursorLevel = self.brushMode.createCursorLevel(self)
        if cursorLevel is not None:
            self.cursorWorldScene = worldscene.WorldScene(cursorLevel, self.editorSession.textureAtlas)
            self.cursorWorldScene.depthOffset.depthOffset = DepthOffsets.PreviewRenderer
            self.cursorNode.addChild(self.cursorWorldScene)

            self.brushLoader = WorldLoader(self.cursorWorldScene)
            self.brushLoader.startLoader()

        cursorBox = self.brushMode.brushBoxForPoint((0, 0, 0), self.options)
        if cursorBox is not None:
            self.cursorBoxNode = SelectionBoxNode()
            self.cursorBoxNode.selectionBox = cursorBox
            self.cursorBoxNode.filled = False

            self.cursorNode.addChild(self.cursorBoxNode)
Exemple #30
0
    def __init__(self, pendingImport, textureAtlas, hasHandle=True):
        """
        A scenegraph node displaying an object that will be imported later, including
        live and deferred views of the object with transformed items, and a BoxHandle
        for moving the item.

        Parameters
        ----------

        pendingImport: PendingImport
            The object to be imported. The PendingImportNode responds to changes in this
            object's position, rotation, and scale.
        textureAtlas: TextureAtlas
            The textures and block models used to render the preview of the object.

        Attributes
        ----------

        basePosition: Vector
            The pre-transform position of the pending import. This is equal to
            `self.pendingImport.basePosition` except when the node is currently being
            dragged.
        transformedPosition: Vector
            The post-transform position of the pending import. This is equal to
            `self.pendingImport.importPos` except when the node is currently being
            dragged, scaled, or rotated.
        """
        super(PendingImportNode, self).__init__()

        self.textureAtlas = textureAtlas
        self.pendingImport = pendingImport
        self.hasHandle = hasHandle

        dim = pendingImport.sourceDim

        self.transformedPosition = Vector(0, 0, 0)

        # worldScene is contained by rotateNode, and
        # translates the world scene back to 0, 0, 0 so the rotateNode can
        # rotate it around the anchor, and the plainSceneNode can translate
        # it to the current position.

        self.worldScene = WorldScene(dim, textureAtlas, bounds=pendingImport.selection)
        self.worldScene.depthOffset.depthOffset = DepthOffsets.PreviewRenderer

        self.worldSceneTranslate = Translate(-self.pendingImport.selection.origin)
        self.worldScene.addState(self.worldSceneTranslate)

        # rotateNode is used to rotate the non-transformed preview during live rotation

        self.rotateNode = Rotate3DNode()
        self.rotateNode.setAnchor(self.pendingImport.selection.size * 0.5)
        self.rotateNode.addChild(self.worldScene)

        # plainSceneNode contains the non-transformed preview of the imported
        # object, including its world scene. This preview will be rotated model-wise
        # while the user is dragging the rotate controls.

        self.plainSceneNode = Node("plainScene")
        self.positionTranslate = Translate()
        self.plainSceneNode.addState(self.positionTranslate)
        self.plainSceneNode.addChild(self.rotateNode)

        self.addChild(self.plainSceneNode)

        # transformedSceneNode contains the transformed preview of the imported
        # object, including a world scene that displays the object wrapped by a
        # DimensionTransform.

        self.transformedSceneNode = Node("transformedScene")
        self.transformedSceneTranslate = Translate()
        self.transformedSceneNode.addState(self.transformedSceneTranslate)

        self.transformedWorldScene = None
        self.addChild(self.transformedSceneNode)

        box = BoundingBox(pendingImport.importPos, pendingImport.importBounds.size)

        if hasHandle:
            # handleNode displays a bounding box that can be moved around, and responds
            # to mouse events.
            self.handleNode = BoxHandle()
            self.handleNode.bounds = box
            self.handleNode.resizable = False
            self.boxNode = None
        else:
            # boxNode displays a plain, non-movable bounding box
            self.boxNode = SelectionBoxNode()
            self.boxNode.wireColor = (1, 1, 1, .2)
            self.boxNode.filled = False
            self.handleNode = None
            self.addChild(self.boxNode)

        self.updateTransformedScene()
        self.basePosition = pendingImport.basePosition

        if hasHandle:
            self.handleNode.boundsChanged.connect(self.handleBoundsChanged)
            self.handleNode.boundsChangedDone.connect(self.handleBoundsChangedDone)

            self.addChild(self.handleNode)

        # loads the non-transformed world scene asynchronously.
        self.loader = WorldLoader(self.worldScene,
                                  list(pendingImport.selection.chunkPositions()))
        self.loader.startLoader(0.1 if self.hasHandle else 0.0)

        self.pendingImport.positionChanged.connect(self.setPosition)
        self.pendingImport.rotationChanged.connect(self.setRotation)
Exemple #31
0
class BrushTool(EditorTool):
    name = "Brush"
    iconName = "brush"
    maxBrushSize = 512

    def __init__(self, editorSession, *args, **kwargs):
        super(BrushTool, self).__init__(editorSession, *args, **kwargs)
        self.toolWidget = BrushToolWidget()
        self.brushMode = None
        self.brushLoader = None

        self.brushModesByName = {cls.name:cls() for cls in BrushModeClasses}
        modes = self.brushModesByName.values()
        modes.sort(key=lambda m: m.name)
        self.toolWidget.brushModeInput.setModes(modes)
        BrushModeSetting.connectAndCall(self.modeSettingChanged)

        self.cursorWorldScene = None
        self.cursorNode = Node("brushCursor")
        self.cursorTranslate = Translate()
        self.cursorNode.addState(self.cursorTranslate)

        self.toolWidget.xSpinSlider.setMinimum(1)
        self.toolWidget.ySpinSlider.setMinimum(1)
        self.toolWidget.zSpinSlider.setMinimum(1)

        self.toolWidget.xSpinSlider.valueChanged.connect(self.setX)
        self.toolWidget.ySpinSlider.valueChanged.connect(self.setY)
        self.toolWidget.zSpinSlider.valueChanged.connect(self.setZ)

        self.toolWidget.brushShapeInput.shapeChanged.connect(self.updateCursor)
        self.toolWidget.brushShapeInput.shapeOptionsChanged.connect(self.updateCursor)

        self.fillBlock = editorSession.worldEditor.blocktypes["stone"]

        self.brushSize = BrushSizeSetting.value(QtGui.QVector3D(5, 5, 5)).toTuple()  # calls updateCursor

        self.toolWidget.xSpinSlider.setValue(self.brushSize[0])
        self.toolWidget.ySpinSlider.setValue(self.brushSize[1])
        self.toolWidget.zSpinSlider.setValue(self.brushSize[2])

    @property
    def hoverDistance(self):
        return self.toolWidget.hoverSpinSlider.value()

    _brushSize = (0, 0, 0)

    @property
    def brushSize(self):
        return self._brushSize

    @brushSize.setter
    def brushSize(self, value):
        self._brushSize = value
        BrushSizeSetting.setValue(QtGui.QVector3D(*self.brushSize))
        self.updateCursor()

    def setX(self, val):
        x, y, z = self.brushSize
        x = float(val)
        self.brushSize = x, y, z

    def setY(self, val):
        x, y, z = self.brushSize
        y = float(val)
        self.brushSize = x, y, z

    def setZ(self, val):
        x, y, z = self.brushSize
        z = float(val)
        self.brushSize = x, y, z

    def setBlocktypes(self, types):
        if len(types) == 0:
            return

        self.fillBlock = types[0]
        self.updateCursor()

    def mousePress(self, event):
        pos = event.blockPosition
        vector = (event.blockFace.vector * self.hoverDistance)
        command = BrushCommand(self.editorSession, [pos + vector], self.options)
        self.editorSession.pushCommand(command)

    def mouseMove(self, event):
        if event.blockPosition:
            vector = (event.blockFace.vector * self.hoverDistance)
            assert isinstance(vector, Vector), "vector isa %s" % type(vector)
            self.cursorTranslate.translateOffset = event.blockPosition + vector

    @property
    def options(self):
        options = {'brushSize': self.brushSize,
                   'brushShape': self.brushShape,
                   'brushMode': self.brushMode}
        options.update(self.brushMode.getOptions())
        return options

    def modeSettingChanged(self, value):
        self.brushMode = self.brushModesByName[value]
        stack = self.toolWidget.modeOptionsStack
        while stack.count():
            stack.removeWidget(stack.widget(0))
        widget = self.brushMode.createOptionsWidget(self)
        if widget:
            stack.addWidget(widget)

    @property
    def brushShape(self):
        return self.toolWidget.brushShapeInput.currentShape

    def updateCursor(self):
        log.info("Updating brush cursor")
        if self.cursorWorldScene:
            self.brushLoader.timer.stop()
            self.cursorNode.removeChild(self.cursorWorldScene)
            self.cursorNode.removeChild(self.cursorBoxNode)

        cursorLevel = self.brushMode.createCursorLevel(self)
        cursorBox = self.brushMode.brushBoxForPoint((0, 0, 0), self.options)

        self.cursorBoxNode = SelectionBoxNode()
        self.cursorBoxNode.selectionBox = cursorBox
        self.cursorBoxNode.filled = False

        self.cursorWorldScene = worldscene.WorldScene(cursorLevel, self.editorSession.textureAtlas)
        self.cursorWorldScene.depthOffset.depthOffset = DepthOffsets.PreviewRenderer
        self.cursorNode.addChild(self.cursorWorldScene)
        self.cursorNode.addChild(self.cursorBoxNode)

        self.brushLoader = WorldLoader(self.cursorWorldScene)
        self.brushLoader.startLoader()
Exemple #32
0
class PendingImportNode(Node, QtCore.QObject):
    __node_id_counter = 0

    def __init__(self, pendingImport, textureAtlas, hasHandle=True):
        """
        A scenegraph node displaying an object that will be imported later, including
        live and deferred views of the object with transformed items, and a BoxHandle
        for moving the item.

        Parameters
        ----------

        pendingImport: PendingImport
            The object to be imported. The PendingImportNode responds to changes in this
            object's position, rotation, and scale.
        textureAtlas: TextureAtlas
            The textures and block models used to render the preview of the object.
        hasHandle: bool
            True if this import node should have a user-interactive BoxHandle associated
            with it. This is False for the extra copies displayed by a repeated clone.

        Attributes
        ----------

        basePosition: Vector
            The pre-transform position of the pending import. This is equal to
            `self.pendingImport.basePosition` except when the node is currently being
            dragged.
        transformedPosition: Vector
            The post-transform position of the pending import. This is equal to
            `self.pendingImport.importPos` except when the node is currently being
            dragged, scaled, or rotated.
        """
        super(PendingImportNode, self).__init__()

        self.textureAtlas = textureAtlas
        self.pendingImport = pendingImport
        self.hasHandle = hasHandle

        dim = pendingImport.sourceDim

        self.transformedPosition = Vector(0, 0, 0)

        # worldScene is contained by rotateNode, and
        # translates the world scene back to 0, 0, 0 so the rotateNode can
        # rotate it around the anchor, and the plainSceneNode can translate
        # it to the current position.

        self.worldScene = WorldScene(dim, textureAtlas, bounds=pendingImport.selection)
        self.worldScene.depthOffset.depthOffset = DepthOffsets.PreviewRenderer

        self.worldSceneTranslate = Translate(-self.pendingImport.selection.origin)
        self.worldScene.addState(self.worldSceneTranslate)

        # rotateNode is used to rotate the non-transformed preview during live rotation

        self.rotateNode = Rotate3DNode()
        self.rotateNode.setAnchor(self.pendingImport.selection.size * 0.5)
        self.rotateNode.addChild(self.worldScene)

        self.scaleNode = Scale3DNode()
        self.scaleNode.setAnchor(self.pendingImport.selection.size * 0.5)
        self.scaleNode.addChild(self.rotateNode)

        # plainSceneNode contains the non-transformed preview of the imported
        # object, including its world scene. This preview will be rotated model-wise
        # while the user is dragging the rotate controls.

        self.plainSceneNode = Node("plainScene")
        self.positionTranslate = Translate()
        self.plainSceneNode.addState(self.positionTranslate)
        self.plainSceneNode.addChild(self.scaleNode)

        self.addChild(self.plainSceneNode)

        # transformedSceneNode contains the transformed preview of the imported
        # object, including a world scene that displays the object wrapped by a
        # DimensionTransform.

        self.transformedSceneNode = Node("transformedScene")
        self.transformedSceneTranslate = Translate()
        self.transformedSceneNode.addState(self.transformedSceneTranslate)

        self.transformedWorldScene = None
        self.addChild(self.transformedSceneNode)

        box = BoundingBox(pendingImport.importPos, pendingImport.importBounds.size)

        if hasHandle:
            # handleNode displays a bounding box that can be moved around, and responds
            # to mouse events.
            self.handleNode = BoxHandle()
            self.handleNode.bounds = box
            self.handleNode.resizable = False
            self.boxNode = None
        else:
            # boxNode displays a plain, non-movable bounding box
            self.boxNode = SelectionBoxNode()
            self.boxNode.wireColor = (1, 1, 1, .2)
            self.boxNode.filled = False
            self.handleNode = None
            self.addChild(self.boxNode)

        self.updateTransformedScene()
        self.basePosition = pendingImport.basePosition

        if hasHandle:
            self.handleNode.boundsChanged.connect(self.handleBoundsChanged)
            self.handleNode.boundsChangedDone.connect(self.handleBoundsChangedDone)

            self.addChild(self.handleNode)

        # loads the non-transformed world scene asynchronously.
        self.loader = WorldLoader(self.worldScene,
                                  list(pendingImport.selection.chunkPositions()))
        self.loader.startLoader(0.1 if self.hasHandle else 0.0)

        self.pendingImport.positionChanged.connect(self.setPosition)
        self.pendingImport.rotationChanged.connect(self.setRotation)
        self.pendingImport.scaleChanged.connect(self.setScale)

    # Emitted when the user finishes dragging the box handle and releases the mouse
    # button. Arguments are (newPosition, oldPosition).
    importMoved = QtCore.Signal(object, object)

    # Emitted while the user is dragging the box handle. Argument is the box origin.
    importIsMoving = QtCore.Signal(object)

    def handleBoundsChangedDone(self, bounds, oldBounds):
        point = self.getBaseFromTransformed(bounds.origin)
        oldPoint = self.getBaseFromTransformed(oldBounds.origin)
        if point != oldPoint:
            self.importMoved.emit(point, oldPoint)

    def handleBoundsChanged(self, bounds):
        log.info("handleBoundsChanged: %s", bounds)
        self.setPreviewBasePosition(bounds.origin)

    def setPreviewBasePosition(self, origin):
        point = self.getBaseFromTransformed(origin)
        if self.basePosition != point:
            self.basePosition = point
            self.importIsMoving.emit(point)

    def getBaseFromTransformed(self, point):
        return point - self.pendingImport.transformOffset

    def setPreviewRotation(self, rots):
        self.plainSceneNode.visible = True
        self.transformedSceneNode.visible = False
        self.rotateNode.setRotation(rots)

    def setRotation(self, rots):
        self.updateTransformedScene()
        self.updateBoxHandle()
        self.rotateNode.setRotation(rots)

    def setPreviewScale(self, scale):
        self.plainSceneNode.visible = True
        self.transformedSceneNode.visible = False
        self.scaleNode.setScale(scale)

    def setScale(self, scale):
        self.updateTransformedScene()
        self.updateBoxHandle()
        self.scaleNode.setScale(scale)

    def updateTransformedScene(self):
        if self.pendingImport.transformedDim is not None:
            log.info("Showing transformed scene")
            self.plainSceneNode.visible = False
            self.transformedSceneNode.visible = True

            if self.transformedWorldScene:
                self.transformedSceneNode.removeChild(self.transformedWorldScene)

            self.transformedWorldScene = WorldScene(self.pendingImport.transformedDim,
                                                    self.textureAtlas)
            self.transformedWorldScene.depthOffset.depthOffset = DepthOffsets.PreviewRenderer
            self.transformedSceneNode.addChild(self.transformedWorldScene)

            self.updateTransformedPosition()

            cPos = list(self.pendingImport.transformedDim.chunkPositions())
            self.loader = WorldLoader(self.transformedWorldScene,
                                      cPos)

            # ALERT!: self.hasHandle is overloaded with the meaning:
            #  "not the first clone in a repeated clone"
            self.loader.startLoader(0.1 if self.hasHandle else 0.0)

        else:
            log.info("Hiding transformed scene")
            self.plainSceneNode.visible = True
            self.transformedSceneNode.visible = False
            if self.transformedWorldScene:
                self.transformedSceneNode.removeChild(self.transformedWorldScene)
                self.transformedWorldScene = None

    def updateTransformedPosition(self):
        self.transformedPosition = self.basePosition + self.pendingImport.transformOffset
        self.transformedSceneTranslate.translateOffset = self.transformedPosition - self.pendingImport.importDim.bounds.origin

    def updateBoxHandle(self):
        if self.transformedWorldScene is None:
            bounds = BoundingBox(self.basePosition, self.pendingImport.bounds.size)
        else:
            origin = self.transformedPosition
            bounds = BoundingBox(origin, self.pendingImport.importBounds.size)
        #if self.handleNode.bounds.size != bounds.size:
        if self.hasHandle:
            self.handleNode.bounds = bounds
        else:
            self.boxNode.selectionBox = bounds

    @property
    def basePosition(self):
        return self.positionTranslate.translateOffset

    @basePosition.setter
    def basePosition(self, value):
        value = Vector(*value)
        if value == self.positionTranslate.translateOffset:
            return

        self.positionTranslate.translateOffset = value
        self.updateTransformedPosition()
        self.updateBoxHandle()

    def setPosition(self, pos):
        self.basePosition = pos

    # --- Mouse events ---

    # inherit from BoxHandle?
    def mouseMove(self, event):
        if not self.hasHandle:
            return
        self.handleNode.mouseMove(event)

    def mousePress(self, event):
        if not self.hasHandle:
            return
        self.handleNode.mousePress(event)

    def mouseRelease(self, event):
        if not self.hasHandle:
            return
        self.handleNode.mouseRelease(event)
Exemple #33
0
    def __init__(self, pendingImport, textureAtlas, hasHandle=True):
        """
        A scenegraph node displaying an object that will be imported later, including
        live and deferred views of the object with transformed items, and a BoxHandle
        for moving the item.

        Parameters
        ----------

        pendingImport: PendingImport
            The object to be imported. The PendingImportNode responds to changes in this
            object's position, rotation, and scale.
        textureAtlas: TextureAtlas
            The textures and block models used to render the preview of the object.
        hasHandle: bool
            True if this import node should have a user-interactive BoxHandle associated
            with it. This is False for the extra copies displayed by a repeated clone.

        Attributes
        ----------

        basePosition: Vector
            The pre-transform position of the pending import. This is equal to
            `self.pendingImport.basePosition` except when the node is currently being
            dragged.
        transformedPosition: Vector
            The post-transform position of the pending import. This is equal to
            `self.pendingImport.importPos` except when the node is currently being
            dragged, scaled, or rotated.
        """
        super(PendingImportNode, self).__init__()

        self.textureAtlas = textureAtlas
        self.pendingImport = pendingImport
        self.hasHandle = hasHandle

        dim = pendingImport.sourceDim

        self.transformedPosition = Vector(0, 0, 0)

        # worldScene is contained by rotateNode, and
        # translates the world scene back to 0, 0, 0 so the rotateNode can
        # rotate it around the anchor, and the plainSceneNode can translate
        # it to the current position.

        self.worldScene = WorldScene(dim, textureAtlas, bounds=pendingImport.selection)
        self.worldScene.depthOffset.depthOffset = DepthOffsets.PreviewRenderer

        self.worldSceneTranslate = Translate(-self.pendingImport.selection.origin)
        self.worldScene.addState(self.worldSceneTranslate)

        # rotateNode is used to rotate the non-transformed preview during live rotation

        self.rotateNode = Rotate3DNode()
        self.rotateNode.setAnchor(self.pendingImport.selection.size * 0.5)
        self.rotateNode.addChild(self.worldScene)

        self.scaleNode = Scale3DNode()
        self.scaleNode.setAnchor(self.pendingImport.selection.size * 0.5)
        self.scaleNode.addChild(self.rotateNode)

        # plainSceneNode contains the non-transformed preview of the imported
        # object, including its world scene. This preview will be rotated model-wise
        # while the user is dragging the rotate controls.

        self.plainSceneNode = Node("plainScene")
        self.positionTranslate = Translate()
        self.plainSceneNode.addState(self.positionTranslate)
        self.plainSceneNode.addChild(self.scaleNode)

        self.addChild(self.plainSceneNode)

        # transformedSceneNode contains the transformed preview of the imported
        # object, including a world scene that displays the object wrapped by a
        # DimensionTransform.

        self.transformedSceneNode = Node("transformedScene")
        self.transformedSceneTranslate = Translate()
        self.transformedSceneNode.addState(self.transformedSceneTranslate)

        self.transformedWorldScene = None
        self.addChild(self.transformedSceneNode)

        box = BoundingBox(pendingImport.importPos, pendingImport.importBounds.size)

        if hasHandle:
            # handleNode displays a bounding box that can be moved around, and responds
            # to mouse events.
            self.handleNode = BoxHandle()
            self.handleNode.bounds = box
            self.handleNode.resizable = False
            self.boxNode = None
        else:
            # boxNode displays a plain, non-movable bounding box
            self.boxNode = SelectionBoxNode()
            self.boxNode.wireColor = (1, 1, 1, .2)
            self.boxNode.filled = False
            self.handleNode = None
            self.addChild(self.boxNode)

        self.updateTransformedScene()
        self.basePosition = pendingImport.basePosition

        if hasHandle:
            self.handleNode.boundsChanged.connect(self.handleBoundsChanged)
            self.handleNode.boundsChangedDone.connect(self.handleBoundsChangedDone)

            self.addChild(self.handleNode)

        # loads the non-transformed world scene asynchronously.
        self.loader = WorldLoader(self.worldScene,
                                  list(pendingImport.selection.chunkPositions()))
        self.loader.startLoader(0.1 if self.hasHandle else 0.0)

        self.pendingImport.positionChanged.connect(self.setPosition)
        self.pendingImport.rotationChanged.connect(self.setRotation)
        self.pendingImport.scaleChanged.connect(self.setScale)
Exemple #34
0
    def __init__(self, *args, **kwargs):
        EditorTool.__init__(self, *args, **kwargs)
        self.livePreview = False
        self.blockPreview = False
        self.glPreview = True

        toolWidget = QtGui.QWidget()

        self.toolWidget = toolWidget

        column = []
        self.generatorTypes = [
            pluginClass(self)
            for pluginClass in GeneratePlugins.registeredPlugins
        ]
        self.currentGenerator = None
        if len(self.generatorTypes):
            self.currentGenerator = self.generatorTypes[0]

        self.generatorTypeInput = QtGui.QComboBox()
        self.generatorTypesChanged()

        self.generatorTypeInput.currentIndexChanged.connect(
            self.currentTypeChanged)

        self.livePreviewCheckbox = QtGui.QCheckBox("Live Preview")
        self.livePreviewCheckbox.setChecked(self.livePreview)
        self.livePreviewCheckbox.toggled.connect(self.livePreviewToggled)

        self.blockPreviewCheckbox = QtGui.QCheckBox("Block Preview")
        self.blockPreviewCheckbox.setChecked(self.blockPreview)
        self.blockPreviewCheckbox.toggled.connect(self.blockPreviewToggled)

        self.glPreviewCheckbox = QtGui.QCheckBox("GL Preview")
        self.glPreviewCheckbox.setChecked(self.glPreview)
        self.glPreviewCheckbox.toggled.connect(self.glPreviewToggled)

        self.optionsHolder = QtGui.QStackedWidget()
        self.optionsHolder.setSizePolicy(QtGui.QSizePolicy.Preferred,
                                         QtGui.QSizePolicy.Expanding)

        self.generateButton = QtGui.QPushButton(self.tr("Generate"))
        self.generateButton.clicked.connect(self.generateClicked)

        column.append(self.generatorTypeInput)
        column.append(self.livePreviewCheckbox)
        column.append(self.blockPreviewCheckbox)
        column.append(self.glPreviewCheckbox)
        column.append(self.optionsHolder)
        column.append(self.generateButton)

        self.toolWidget.setLayout(Column(*column))

        self.overlayNode = scenenode.Node("generateOverlay")

        self.sceneHolderNode = Node("sceneHolder")
        self.sceneTranslate = Translate()
        self.sceneHolderNode.addState(self.sceneTranslate)

        self.overlayNode.addChild(self.sceneHolderNode)

        self.previewNode = None

        self.boxHandleNode = BoxHandle()
        self.boxHandleNode.boundsChanged.connect(self.boundsDidChange)
        self.boxHandleNode.boundsChangedDone.connect(self.boundsDidChangeDone)
        self.overlayNode.addChild(self.boxHandleNode)

        self.worldScene = None
        self.loader = None

        self.previewBounds = None
        self.schematicBounds = None
        self.currentSchematic = None

        self.currentTypeChanged(0)

        # Name of last selected generator plugin is saved after unloading
        # so it can be reselected if it is immediately reloaded
        self._lastTypeName = None

        GeneratePlugins.pluginAdded.connect(self.addPlugin)
        GeneratePlugins.pluginRemoved.connect(self.removePlugin)
Exemple #35
0
class GenerateTool(EditorTool):
    name = "Generate"
    iconName = "generate"

    instantDisplayChunks = 32

    def __init__(self, *args, **kwargs):
        EditorTool.__init__(self, *args, **kwargs)
        self.livePreview = False
        self.blockPreview = False
        self.glPreview = True

        toolWidget = QtGui.QWidget()

        self.toolWidget = toolWidget

        column = []
        self.generatorTypes = [pluginClass(self) for pluginClass in _pluginClasses]
        self.currentGenerator = None
        if len(self.generatorTypes):
            self.currentGenerator = self.generatorTypes[0]

        self.generatorTypeInput = QtGui.QComboBox()
        self.generatorTypesChanged()

        self.generatorTypeInput.currentIndexChanged.connect(self.currentTypeChanged)

        self.livePreviewCheckbox = QtGui.QCheckBox("Live Preview")
        self.livePreviewCheckbox.setChecked(self.livePreview)
        self.livePreviewCheckbox.toggled.connect(self.livePreviewToggled)

        self.blockPreviewCheckbox = QtGui.QCheckBox("Block Preview")
        self.blockPreviewCheckbox.setChecked(self.blockPreview)
        self.blockPreviewCheckbox.toggled.connect(self.blockPreviewToggled)

        self.glPreviewCheckbox = QtGui.QCheckBox("GL Preview")
        self.glPreviewCheckbox.setChecked(self.glPreview)
        self.glPreviewCheckbox.toggled.connect(self.glPreviewToggled)

        self.optionsHolder = QtGui.QStackedWidget()
        self.optionsHolder.setSizePolicy(QtGui.QSizePolicy.Preferred, QtGui.QSizePolicy.Expanding)

        self.generateButton = QtGui.QPushButton(self.tr("Generate"))
        self.generateButton.clicked.connect(self.generateClicked)

        column.append(self.generatorTypeInput)
        column.append(self.livePreviewCheckbox)
        column.append(self.blockPreviewCheckbox)
        column.append(self.glPreviewCheckbox)
        column.append(self.optionsHolder)
        column.append(self.generateButton)

        self.toolWidget.setLayout(Column(*column))

        self.overlayNode = scenenode.Node()

        self.sceneHolderNode = Node()
        self.sceneTranslate = Translate()
        self.sceneHolderNode.addState(self.sceneTranslate)

        self.overlayNode.addChild(self.sceneHolderNode)

        self.previewNode = None

        self.boxHandleNode = BoxHandle()
        self.boxHandleNode.boundsChanged.connect(self.boundsDidChange)
        self.boxHandleNode.boundsChangedDone.connect(self.boundsDidChangeDone)
        self.overlayNode.addChild(self.boxHandleNode)

        self.worldScene = None
        self.loader = None

        self.previewBounds = None
        self.schematicBounds = None
        self.currentSchematic = None

        self.currentTypeChanged(0)

        # Name of last selected generator plugin is saved after unloading
        # so it can be reselected if it is immediately reloaded
        self._lastTypeName = None

        _GeneratePlugins.instance.pluginAdded.connect(self.addPlugin)
        _GeneratePlugins.instance.pluginRemoved.connect(self.removePlugin)

    def removePlugin(self, cls):
        log.info("Removing plugin %s", cls.__name__)
        self.generatorTypes[:] = [gt for gt in self.generatorTypes if not isinstance(gt, cls)]
        self.generatorTypesChanged()
        if self.currentGenerator not in self.generatorTypes:
            lastTypeName = self.currentGenerator.__class__.__name__
            self.currentTypeChanged(0)  # resets self._lastTypeName
            self._lastTypeName = lastTypeName

    def addPlugin(self, cls):
        log.info("Adding plugin %s", cls.__name__)
        self.generatorTypes.append(cls(self))
        self.generatorTypesChanged()
        if self._lastTypeName is not None:
            if cls.__name__ == self._lastTypeName:
                self.currentTypeChanged(len(self.generatorTypes)-1)

    def generatorTypesChanged(self):
        self.generatorTypeInput.clear()
        for gt in self.generatorTypes:
            if hasattr(gt, 'displayName'):
                displayName = gt.displayName
            else:
                displayName = gt.__class__.__name__
            self.generatorTypeInput.addItem(displayName, gt)


    def livePreviewToggled(self, value):
        self.livePreview = value

    def blockPreviewToggled(self, value):
        self.blockPreview = value
        if value:
            if self.currentSchematic:
                self.displaySchematic(self.currentSchematic, self.schematicBounds.origin)
            else:
                self.updateBlockPreview()
        else:
            self.clearSchematic()

    def glPreviewToggled(self, value):
        self.glPreview = value
        if value:
            self.updateNodePreview()  # xxx cache previewNode?
        else:
            self.clearNode()

    def currentTypeChanged(self, index):
        # user selected generator type after old type was unloaded, so forget the old type
        self._lastTypeName = None

        self.optionsHolder.removeWidget(self.optionsHolder.widget(0))
        if index < len(self.generatorTypes):
            self.currentGenerator = self.generatorTypes[index]
            self.optionsHolder.addWidget(self.currentGenerator.getOptionsWidget())
            self.updatePreview()
        else:
            self.currentGenerator = None
            self.clearSchematic()
            self.clearNode()

        log.info("Chose generator %s", repr(self.currentGenerator))

    def mousePress(self, event):
        self.boxHandleNode.mousePress(event)

    def mouseMove(self, event):
        self.boxHandleNode.mouseMove(event)

    def mouseRelease(self, event):
        self.boxHandleNode.mouseRelease(event)

    def boundsDidChange(self, bounds):
        # box still being resized
        if not self.currentGenerator:
            return

        if not self.livePreview:
            return

        self.previewBounds = bounds
        self.currentGenerator.boundsChanged(bounds)
        self.updatePreview()

    def boundsDidChangeDone(self, bounds, oldBounds):
        # box finished resize
        if not self.currentGenerator:
            return

        self.previewBounds = bounds
        self.schematicBounds = bounds
        self.currentGenerator.boundsChanged(bounds)
        self.updatePreview()

    def clearNode(self):
        if self.previewNode:
            self.overlayNode.removeChild(self.previewNode)
            self.previewNode = None

    def updatePreview(self):
        if self.blockPreview:
            self.updateBlockPreview()
        else:
            self.clearSchematic()

        if self.glPreview:
            self.updateNodePreview()
        else:
            self.clearNode()

    def updateBlockPreview(self):
        bounds = self.previewBounds

        if bounds is not None and bounds.volume > 0:
            self.generateNextSchematic(bounds)
        else:
            self.clearSchematic()

    def updateNodePreview(self):
        bounds = self.previewBounds
        if self.currentGenerator is None:
            return

        if bounds is not None and bounds.volume > 0:
            try:
                node = self.currentGenerator.getPreviewNode(bounds)
            except Exception:
                log.exception("Error while getting scene nodes from generator:")
            else:
                if node is not None:
                    self.clearNode()

                    if isinstance(node, list):
                        nodes = node
                        node = scenenode.Node()
                        for c in nodes:
                            node.addChild(c)

                    self.overlayNode.addChild(node)
                    self.previewNode = node

    def generateNextSchematic(self, bounds):
        if bounds is None:
            self.clearSchematic()
            return
        if self.currentGenerator is None:
            return

        try:
            schematic = self.currentGenerator.generate(bounds, self.editorSession.worldEditor.blocktypes)
            self.currentSchematic = schematic
            self.displaySchematic(schematic, bounds.origin)
        except Exception as e:
            log.exception("Error while running generator %s: %s", self.currentGenerator, e)
            QtGui.QMessageBox.warning(QtGui.qApp.mainWindow, "Error while running generator",
                                      "An error occurred while running the generator: \n  %s.\n\n"
                                      "Traceback: %s" % (e, traceback.format_exc()))
            self.livePreview = False

    def displaySchematic(self, schematic, offset):
        if schematic is not None:
            dim = schematic.getDimension()

            if self.worldScene:
                self.sceneHolderNode.removeChild(self.worldScene)
                self.loader.timer.stop()
                self.loader = None

            atlas = self.editorSession.textureAtlas
            self.worldScene = WorldScene(dim, atlas)
            self.sceneTranslate.translateOffset = offset
            self.sceneHolderNode.addChild(self.worldScene)

            self.loader = WorldLoader(self.worldScene)
            if dim.chunkCount() <= self.instantDisplayChunks:
                exhaust(self.loader.work())
            else:
                self.loader.startLoader()
        else:
            self.clearSchematic()

    def clearSchematic(self):
        if self.worldScene:
            self.sceneHolderNode.removeChild(self.worldScene)
            self.worldScene = None
            self.loader.timer.stop()
            self.loader = None

    def generateClicked(self):
        if self.currentGenerator is None:
            return

        if self.schematicBounds is None:
            log.info("schematicBounds is None, not generating")
            return

        if self.currentSchematic is None:
            log.info("Generating new schematic for import")
            currentSchematic = self.currentGenerator.generate(self.schematicBounds, self.editorSession.worldEditor.blocktypes)
        else:
            log.info("Importing previously generated schematic.")
            currentSchematic = self.currentSchematic

        command = GenerateCommand(self, self.schematicBounds)
        try:
            with command.begin():
                if currentSchematic is not None:
                    task = self.editorSession.currentDimension.importSchematicIter(currentSchematic, self.schematicBounds.origin)
                    showProgress(self.tr("Importing generated object..."), task)
                else:
                    task = self.currentGenerator.generateInWorld(self.schematicBounds, self.editorSession.currentDimension)
                    showProgress(self.tr("Generating object in world..."), task)
        except Exception as e:
            log.exception("Error while importing or generating in world: %r" % e)
            command.undo()
        else:
            self.editorSession.pushCommand(command)