From 4e9a020f0ff9b9d836cdcb1c4bcf2d3f7b3e89c5 Mon Sep 17 00:00:00 2001 From: David Vierra Date: Wed, 13 May 2015 01:11:51 -1000 Subject: [PATCH] Add a somewhat flaky Generate plugin for L-systems that currently only runs the Koch Snowflake system. This plugin will probably not live for very long as it seems better to make Generate plugins directly for different L-systems. --- src/plugins/koch.py | 120 +++++++++++++++++++++++++++++++++ src/plugins/l_system_plugin.py | 107 +++++++++++++++++++++++++++++ 2 files changed, 227 insertions(+) create mode 100644 src/plugins/koch.py create mode 100644 src/plugins/l_system_plugin.py diff --git a/src/plugins/koch.py b/src/plugins/koch.py new file mode 100644 index 0000000..7c01fa9 --- /dev/null +++ b/src/plugins/koch.py @@ -0,0 +1,120 @@ +# coding=utf-8 +""" + koch +""" +from __future__ import absolute_import, division, print_function +import logging +from math import pi, cos, sin + +from mcedit2.synth.l_system import Geometric, Line +from mceditlib.geometry import Vector + + +log = logging.getLogger(__name__) + +class Side(Line): + """ + A side of a Koch snowflake. A side can be replaced by four shorter sides in a _/\_ configuration. + + Inherits from Line, so renders as a line. + + Properties: + + blocktype + p1: Vector + p2: Vector + """ + def replace(self): + p1 = self.p1 + p2 = self.p2 + d = p2 - p1 + + mid1 = p1 + d * 0.3333 + mid2 = p2 - d * 0.3333 + spike = mid1 + rotatePoint(d * -0.3333) + + # First segment + yield Side(p1=p1, p2=mid1, blocktype=self.blocktype) + + # Second segment + yield Side(p1=mid1, p2=spike, blocktype=self.blocktype) + + # Third segment + yield Side(p1=spike, p2=mid2, blocktype=self.blocktype) + + # Fourth segment + yield Side(p1=mid2, p2=p2, blocktype=self.blocktype) + + +# One-third of a circle +THETA = 2 * pi / 3 +COS_THETA = cos(THETA) +SIN_THETA = sin(THETA) + + +# Rotate the point around 0, 0 by one-third of a circle. +# Also works for vectors! We use it to rotate the vector +# added to get the new point when replacing a side with +# four smaller sides. + +def rotatePoint((x, y, z)): + u""" + Rotation matrix: + + | cos θ - sin θ | + | sin θ cos θ | + + """ + x2 = COS_THETA * x - SIN_THETA * z + z2 = SIN_THETA * x + COS_THETA * z + return Vector(x2, y, z2) + + +class Snowflake(Geometric): + """ + Koch snowflake. + + The initial symbol is replaced by six sides. + + Each side is replaced by four shorter sides, each 1/3 the length of the original side in a _/\_ configuration. + + The side replacement is recursively defined and may be repeated any number of times. + + This could probably be optimized a bit by introducing a LineStrip symbol and inheriting SideStrip from it. + + Properties: + blocktype + + + properties inherited from Geometric + """ + def replace(self): + # Find the first corner's position relative to the hexagon's center + center = Vector(self.center.x, self.miny, self.center.z) + firstPoint = Vector(self.maxx, self.miny, self.center.z) - center + + points = [] + point = firstPoint + for i in range(3): + points.append(point) + point = rotatePoint(point) + + # Translate the corners back to the hexagon's position + points = [p + center for p in points] + + # Compute the endpoints of the line segments + startPoints = points + endPoints = points[1:] + points[:1] + + for p1, p2 in zip(startPoints, endPoints): + yield Side(p1=p1, p2=p2, blocktype=self.blocktype) + + +if __name__ == '__main__': + point = 4.5, 0, 5.0 + print("THETA", THETA) + print("COS_THETA", COS_THETA) + print("SIN_THETA", SIN_THETA) + + for i in range(3): + print(point) + point = rotatePoint(point) diff --git a/src/plugins/l_system_plugin.py b/src/plugins/l_system_plugin.py new file mode 100644 index 0000000..bc29174 --- /dev/null +++ b/src/plugins/l_system_plugin.py @@ -0,0 +1,107 @@ +""" + l_system_plugin +""" +from __future__ import absolute_import, division, print_function +import logging + +from PySide import QtGui + +from mcedit2.editortools.generate import GeneratePlugin +from mcedit2.plugins import registerGeneratePlugin +import koch +from mcedit2.synth.l_system import renderBlocks, renderSceneNodes, applyReplacementsIterated +from mcedit2.util.showprogress import showProgress +from mcedit2.widgets.blockpicker import BlockTypeButton +from mcedit2.widgets.layout import Column +from mcedit2.widgets.spinslider import SpinSlider +from mceditlib.schematic import createSchematic + + +log = logging.getLogger(__name__) + +class LSystemPlugin(GeneratePlugin): + + def __init__(self, editorSession): + log.warn("type(LSystemPlugin): %s, type(self): %s (IS? %s)", + LSystemPlugin, type(self), + LSystemPlugin is type(self)) + super(LSystemPlugin, self).__init__(editorSession) + self.optionsWidget = None + self.displayName = self.tr("L-System Test") + + def getOptionsWidget(self): + if self.optionsWidget: + return self.optionsWidget + + widget = QtGui.QWidget() + + self.systemsBox = QtGui.QComboBox() + self.systemsBox.addItem("Koch Snowflake") + + self.blocktypeButton = BlockTypeButton() + self.blocktypeButton.editorSession = self.editorSession + self.blocktypeButton.block = "minecraft:stone" + self.blocktypeButton.blocksChanged.connect(self.updatePreview) + + self.iterationsSlider = SpinSlider() + self.iterationsSlider.setMinimum(1) + self.iterationsSlider.setMaximum(100) + self.iterationsSlider.setValue(3) + self.iterationsSlider.valueChanged.connect(self.updatePreview) + + widget.setLayout(Column(self.systemsBox, + self.iterationsSlider, + self.blocktypeButton, # xxx from systemsBox + None)) + + self.optionsWidget = widget + return widget + + def getPreviewNode(self, bounds): + system = koch.Snowflake(bounds, blocktype=self.blocktypeButton.block) + symbol_list = [system] + + max_iterations = self.iterationsSlider.value() + + def process(_symbol_list): + for iteration, _symbol_list in applyReplacementsIterated(_symbol_list, max_iterations): + yield iteration, max_iterations + + yield _symbol_list + + symbol_list = showProgress("Generating...", process(symbol_list), cancel=True) + if symbol_list is False: + return + + sceneNodes = renderSceneNodes(symbol_list) + return sceneNodes + + def generate(self, bounds, blocktypes): + # self.systemsBox.value() + schematic = createSchematic(bounds.size, blocktypes) + dim = schematic.getDimension() + system = koch.Snowflake(dim.bounds, blocktype=self.blocktypeButton.block) + symbol_list = [system] + + max_iterations = self.iterationsSlider.value() + def process(_symbol_list): + for iteration, _symbol_list in applyReplacementsIterated(_symbol_list, max_iterations): + yield iteration, max_iterations + + yield _symbol_list + + symbol_list = showProgress("Generating...", process(symbol_list), cancel=True) + if symbol_list is False: + return + + import pprint + pprint.pprint(symbol_list) + rendering = renderBlocks(symbol_list) + + print("Rendering %d blocks" % len(rendering)) + for x, y, z, blockType in rendering: + dim.setBlock(x, y, z, blockType) + + return schematic + +registerGeneratePlugin(LSystemPlugin)