Fool around with an L-system for drawing Hilbert curves.
This commit is contained in:
parent
72f7bd0d2e
commit
6bf52bbbaa
@ -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
213
src/plugins/hilbert.py
Normal 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)
|
Reference in New Issue
Block a user