From 6bf52bbbaa2cf421554fbb4c379c6a26b16fc349 Mon Sep 17 00:00:00 2001 From: David Vierra Date: Tue, 7 Jul 2015 06:18:01 -1000 Subject: [PATCH] Fool around with an L-system for drawing Hilbert curves. --- src/mcedit2/synth/l_system_plugin.py | 10 +- src/plugins/hilbert.py | 213 +++++++++++++++++++++++++++ 2 files changed, 221 insertions(+), 2 deletions(-) create mode 100644 src/plugins/hilbert.py diff --git a/src/mcedit2/synth/l_system_plugin.py b/src/mcedit2/synth/l_system_plugin.py index 1f6cc42..06cd7e6 100644 --- a/src/mcedit2/synth/l_system_plugin.py +++ b/src/mcedit2/synth/l_system_plugin.py @@ -140,9 +140,12 @@ class LSystemPlugin(GeneratePlugin): log.info("Rendering symbols to OpenGL") - sceneNodes = renderSceneNodes(symbol_list) + sceneNodes = self.renderSceneNodes(symbol_list) return sceneNodes + def renderSceneNodes(self, symbol_list): + return renderSceneNodes(symbol_list) + def generateInSchematic(self, dimension, originalBounds): symbol_list = self.createSymbolList(originalBounds) if symbol_list is None: @@ -150,7 +153,7 @@ class LSystemPlugin(GeneratePlugin): log.info("Rendering symbols to blocks") - rendering = renderBlocks(symbol_list) + rendering = self.renderBlocks(symbol_list) log.info("Editing %d blocks" % len(rendering)) for x, y, z, blockType in rendering: @@ -160,3 +163,6 @@ class LSystemPlugin(GeneratePlugin): dimension.setBlock(x, y, z, blockType) + def renderBlocks(self, symbol_list): + return renderBlocks(symbol_list) + diff --git a/src/plugins/hilbert.py b/src/plugins/hilbert.py new file mode 100644 index 0000000..ff05c4d --- /dev/null +++ b/src/plugins/hilbert.py @@ -0,0 +1,213 @@ +# coding=utf-8 +""" + hilbert +""" +from __future__ import absolute_import, division, print_function +import logging +from math import pi, cos, sin +from OpenGL import GL +from PySide import QtGui +import math +from mcedit2.plugins import registerGeneratePlugin +from mcedit2.rendering.scenegraph import VertexNode +from mcedit2.rendering.vertexarraybuffer import VertexArrayBuffer + +from mcedit2.synth.l_system import Geometric, Line, Symbol +from mcedit2.synth.l_system_plugin import LSystemPlugin +from mcedit2.util import bresenham +from mcedit2.widgets.blockpicker import BlockTypeButton +from mceditlib import faces +from mceditlib.geometry import Vector + + +log = logging.getLogger(__name__) +""" + + + In 2D: + + Each cell has an initial subcell index, and a 'forward/backward' marker. + + A "forward" cell replaces itself with "backward, forward, forward, backward" cells. + A "backward" cell with "forward, backward, backward, forward" cells. + + A cell with initial subcell index N returns cells with subcell indices `(N, N, N, N+2)` + (modulo ncells) + + That is to say, a `0, forward` cell returns subcells `0, 1, 2, 3` each with subcell + indexes `0, 0, 0, 2` + + A `0, backward` cell returns subcells `0, 3, 2, 1` with indices `0, 0, 0, 2` + A `2, backward` cell returns subcells `2, 1, 0, 3` with indices `2, 2, 2, 0` + + Wikipedia gives this production rule for turtle graphics: + + A → − B F + A F A + F B − + B → + A F − B F B − F A + + + The 'counter' and 'clock' types correspond to the 'A' and 'B' types of the turtle + graphics system. The cardinal direction is stored in the orientation of the turtle. + Without a turtle, we need to store the direction in the cell itself. + + When counter-clockwise, return these subcells. When clockwise, return them + in reverse order. + + down: (0, 0), (0, 1), (1, 1), (1, 0) + right: (0, 1), (1, 1), (1, 0), (0, 0) + up: (1, 1), (1, 0), (0, 0), (0, 1) + left: (1, 0), (0, 0), (0, 1), (1, 1) + +""" +# +# offsets = [ +# (0, 0, 0), +# (0, 0, 1), +# (0, 1, 1), +# (0, 1, 0), +# (1, 1, 0), +# (1, 1, 1), +# (1, 0, 1), +# (1, 0, 0), +# ] +offsets = [ + (0, 0), (0, 1), (1, 1), (1, 0), +] + +ncells = len(offsets) +def nextSubcells(N): + return N, N, N, (N+2) % ncells + +def getOffsets(initialIndex): + return offsets[initialIndex:] + offsets[:initialIndex] + +class Cell(Symbol): + """ + A cell of a Hilbert curve. A cell can be replaced by eight smaller cells, each + half the size of this cell. Cell sides must be a power of two. + + Properties: + + blocktype + p1: Vector + p2: Vector + """ + def replace(self): + x, y, z = self.origin + cellSize = self.cellSize + initialIndex = self.initialIndex + forward = self.forward + + if cellSize == 2: + yield self + return + + cellSize >>= 1 + + subcellIndices = nextSubcells(initialIndex) + + if forward: + nextForwards = False, True, True, False + d=1 + else: + nextForwards = True, False, False, True + d=-1 + + offsets = getOffsets(initialIndex) + + + for i, (subcellIndex, forward) in enumerate(zip(subcellIndices, nextForwards)): + dx, dz = offsets[d*i] + origin = (x + dx * cellSize, y, z + dz * cellSize) + yield Cell(origin=origin, cellSize=cellSize, + forward=forward, initialIndex=subcellIndex) + + + + +Clock = True +Counter = False + +class HilbertCurveShell(Geometric): + """ + The outer shell of a Hilbert curve. This shell simply replaces itself with a + HilbertCurve the size of the largest power-of-two cube that fits inside itself. + + Properties: + blocktype + + + properties inherited from Geometric + """ + def replace(self): + size = min(self.size[0], self.size[2]) # xxx 3d + cellSize = 1 + while cellSize <= size: + cellSize <<= 1 + + cellSize >>= 1 + + yield Cell(origin=self.origin, cellSize=cellSize, + initialIndex=0, + forward=True, + blocktype=self.blocktype) + + +class HilbertCurvePlugin(LSystemPlugin): + displayName = "Hilbert Curve" + _optionsWidget = None + + def getOptionsWidget(self): + if self._optionsWidget: + return self._optionsWidget + + widget = self._optionsWidget = QtGui.QWidget() + + self.blockTypeButton = BlockTypeButton() + self.blockTypeButton.editorSession = self.editorSession + self.blockTypeButton.block = "minecraft:stone" + self.blockTypeButton.blocksChanged.connect(self.updatePreview) + + layout = QtGui.QFormLayout() + layout.addRow(self.tr("Iterations"), self.iterationsSlider) + layout.addRow(self.tr("Block"), self.blockTypeButton) + + widget.setLayout(layout) + return widget + + def createInitialSymbol(self, bounds): + symbol = HilbertCurveShell(bounds, blocktype=self.blockTypeButton.block) + + return symbol + + def renderSceneNodes(self, symbol_list): + points = [] + for cell in symbol_list: + if not isinstance(cell, Cell): + continue + points.append(cell.origin) + + vertexArray = VertexArrayBuffer(len(points), GL.GL_LINE_STRIP, False, False) + vertexArray.vertex[:] = points + vertexArray.vertex[:] += 0.5 # draw using box centers + vertexArray.rgba[:] = [(255, 0, 255, 255)] + + node = VertexNode([vertexArray]) # xxx LineNode + return [node] + + def _renderBlocks(self, symbol_list): + for cell1, cell2 in zip(symbol_list[:-1], symbol_list[1:]): + if not isinstance(cell1, Cell): + continue + for p in bresenham.bresenham(cell1.origin, cell2.origin): + yield p + (self.blockTypeButton.block, ) #xxx line + + def renderBlocks(self, symbol_list): + return list(self._renderBlocks(symbol_list)) + + def boundsChanged(self, bounds): + size = (bounds.width + bounds.length) / 2 + maxiter = math.log(size, 2) + 2 + self.iterationsSlider.setMaximum(maxiter) + +displayName = "Hilbert Curve" + +registerGeneratePlugin(HilbertCurvePlugin)