Fool around with an L-system for drawing Hilbert curves.

This commit is contained in:
David Vierra 2015-07-07 06:18:01 -10:00
parent 72f7bd0d2e
commit 6bf52bbbaa
2 changed files with 221 additions and 2 deletions

View File

@ -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)

213
src/plugins/hilbert.py Normal file
View File

@ -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)