Decagon fractal
Script interpreter, generates rhombs from Penrose tiling.
/**
* Copyright signedvoid ( http://wonderfl.net/user/signedvoid )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/z8pp
*/
// forked from signedvoid's P3 Penrose tiling (scripted)
package
{
import com.bit101.components.*;
import flash.display.BitmapData;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.Event;
import flash.filters.GlowFilter;
/*
*
Target:
Generate tilings with two types of rhombs:
angles 36, 144 degrees and 72, 108 degrees.
- learn how to draw each rhomb by point & angle
- automate rhombs creation with a script
- create suggestions of level/figure/point when typing new definition
TODO:
- advanced script options, like changing size (done), colors & effects
*/
[SWF(width="465", height="465", backgroundColor="0x3C3C3F")]
public class PenroseP3Tiling extends Sprite
{
protected var rhombs:Vector.<Rhomb>;
protected var container:Sprite;
protected var cx:Number = 465 * 0.5; //TODO: move into interpreter class
protected var cy:Number = 465 * 0.5;
protected var textArea:TextArea;
protected var message:Label;
protected var textAreaControl:CheckBox;
protected var presets:ComboBox;
protected var scripts:Array =
[
"//Star 1\n" +
"side=25 cx=232 cy=295 //set options - side length and center point\n" +
"5 T=72 //initial seed - five thick rhombs with angle 72 degrees\n" +
"//uncomment following lines\n" +
"//t 0 1 90, t 0 1 -18 // t for thin rhomb, then anchor figure 0, then anchor point 1, then angle\n" +
"//T 0 0 36"
,
"//Penrose P3\n" +
"side=15 cx=232 cy=295\n" +
"5 T=72\n" +
"t 0 2 126\n" +
"T 0 0 -72, T 0 0 72, T 0 2 72\n" +
"T 0 2 -36, t 0 2 54, t 0 2 -126, t 0 2 18, t 0 2 -90\n" +
"T 0 2 72, T 0 2 -144, T 4 3 -108, T 3 1 36\n" +
"T 0 0 0, T 0 0 -72, t 0 2 -54, t 1 2 -18\n" +
"T 2 1 108, T 3 3 -180, t 1 2 54\n" +
"t 0 2 -18, t 0 2 18, T 0 0 36, T 1 0 -108, T 2 3 36, T 2 3 -108, T 2 3 -36\n" +
"T 2 0 -36, T 3 0 -36, t 0 2 -90, t 1 2 90, t 6 2 90, t 6 2 -162, T 0 1 0\n" +
"T 6 2 108, T 6 2 -108, T 6 2 36, T 6 2 -36, t 1 2 -126, t 1 2 -18, T 1 2 -72, t 2 2 54, t 2 2 -54, T 2 2 0, T 4 3 -72, T 5 1 0\n" +
"T 0 3 72, t 0 3 18, T 7 2 -72, t 7 2 -18, T 5 2 -144, t 5 2 -90, T 9 2 -108, t 8 2 18, t 2 3 54, t 2 3 -54, T 2 3 0, T 6 2 -36, T 6 2 -108, T 9 2 36, T 9 2 -36, T 10 2 36, T 11 2 -108, t 10 2 -18, t 11 2 -54\n" +
"T10 2 -108, T10 2 108, t9 2 -90, t 8 2 90, t 8 2 54, t9 2 -54, T15 2 0, T15 2 -72, T 16 2 0, T16 2 -72, T 17 3 -36\n" +
"T 3 3 72, T 2 1 -72, T 2 0 0, T 3 0 0, t 0 1 90, T 0 1 36, T 1 3 -36, t 1 3 18, t 0 1 -18, t 7 2 54, t 6 2 -90, T 10 2 72, T 10 2 -144, t 8 2 -126, t 9 2 18\n"
,
"//Decagon fractal\n" +
"side=15 cx=232 cy=295\n" +
"10 t=36\n" +
"T 0 1 18\n" +
"T 0 2 -90\n" +
"t 0 1 108, t 0 1 0\n" +
"T 0 0 54, T 0 2 -18, T 1 2 126, T 1 2 -126\n" +
"T 3 2 -18, t 2 0 72, t 2 0 -72\n" +
"T 1 3 -90, t 0 2 72, t 0 2 -108\n" +
"t 2 1 72, t 0 1 0, T 1 2 -54, T 1 2 54, T 1 3 -54, T 2 1 18\n" +
"t 1 1 0, t 1 3 0, t 1 2 0, T 2 1 -54, T 2 2 -54, T 3 3 54, T 3 2 54\n" +
"T 0 2 126, T 2 2 126, T 2 3 -126, T 2 2 -126\n" +
"T 2 1 -126, T 3 1 -126, T 1 2 126, T 1 3 126, t 1 0 72, t 1 0 -72, t 3 1 -72, t 1 3 72\n" +
"T 0 2 -18, T 0 1 -18, T 2 3 18, t 4 3 72, t 4 2 72, t 5 1 -72, t 5 2 -72\n" +
"T 0 2 -18, T 5 0 90, T 5 1 90, T 3 3 -90, t 4 2 -72, t 3 2 -72, t 5 2 72, t 6 2 72\n" +
"T 2 3 90, t 4 2 -72, t 4 1 -72, t 7 2 72, t 7 3 72\n" +
"t 2 0 108, t 2 1 108, t 4 3 -108\n" +
"t 2 1 -108"
,
"//Decagon fractal 2\n" +
"side=6 cx=232 cy=295\n" +
"10 t=36\n" +
"T 0 1 18\n" +
"T 0 2 -90\n" +
"t 0 1 108, t 0 1 0\n" +
"T 0 0 54, T 0 2 -18, T 1 2 126, T 1 2 -126\n" +
"T 3 2 -18, t 2 0 72, t 2 0 -72\n" +
"T 1 3 -90, t 0 2 72, t 0 2 -108\n" +
"side=12 //next three lines are repeating\n" +
"t0 1 0, T0 1 54, T0 1 -54\n" +
"T0 2 126, T0 2 -126, T1 2 18, t 0 2 72, t 0 2 -72\n" +
"T3 3 -90, t 2 2 -72, t 2 2 108\n" +
"side=24\n" +
"t0 1 0, T0 1 54, T0 1 -54\n" +
"T0 2 126, T0 2 -126, T1 2 18, t 0 2 72, t 0 2 -72\n" +
"T3 3 -90, t 2 2 -72, t 2 2 108\n" +
"side=48\n" +
"t0 1 0, T0 1 54, T0 1 -54\n" +
"T0 2 126, T0 2 -126, T1 2 18, t 0 2 72, t 0 2 -72\n" +
"T3 3 -90, t 2 2 -72, t 2 2 108"
];
protected var selectionBitmap:BitmapData;
public function PenroseP3Tiling()
{
//init
rhombs = new Vector.<Rhomb>();
container = new Sprite();
addChild(container);
Rhomb.rhombs = rhombs;
selectionBitmap = new BitmapData(2, 2, false, 0xFFFFFF);
selectionBitmap.setPixel(0, 0, 0);
selectionBitmap.setPixel(1, 1, 0);
new ThinRhomb().init();
new ThickRhomb().init();
//gui
textArea = new TextArea(container, 20, 0, scripts[0]);
textArea.width = 445;
textArea.height = 130;
textArea.addEventListener(Event.CHANGE, onScriptChanged);
message = new Label(container, 0, 445);
textAreaControl = new CheckBox(container, 6, 6, "", onTextAreaToggle);
textAreaControl.selected = true;
var items:Array = [];
var firstLineRegex:RegExp = /\/\/(.*)\n|\r/;
var scriptNo:int = 1;
for each (var script:String in scripts)
{
var lineMatch:Object = firstLineRegex.exec(script);
if (lineMatch)
items.push(lineMatch[1]);
else
items.push("preset #" + scriptNo);
}
presets = new ComboBox(container, 20, 20, "", items);
presets.width = 110;
presets.rotation = 90;
presets.addEventListener(Event.SELECT, onPresetChanged);
presets.selectedIndex = 0;
onScriptChanged(null);
container.filters = [new GlowFilter(0x0, 0.8, 10, 10, 2, 1, false)];
}
protected function onScriptChanged(event:Event):void
{
var result:String = parseString(textArea.text);
message.text = result;
updateTiling();
}
protected function onPresetChanged(event:Event):void
{
textArea.text = scripts[presets.selectedIndex];
onScriptChanged(null);
}
protected function onTextAreaToggle(event:Event):void
{
textArea.visible = textAreaControl.selected;
}
protected function parseString(script:String):String
{
//state machine stuff
rhombs.length = 0;
var angles:Vector.<Number> = new Vector.<Number>();
var lastLevel:Vector.<Rhomb> = new Vector.<Rhomb>();
var angle:Number;
var rhombType:String;
var rhombAngle:Number;
var rhomb:Rhomb;
var lastLevelLength:int = 1;
var match:Object;
var newLevelLength:int;
var round:int, rounds:int;
//parsing stuff
var lines:Array = script.split(/\n|\r/);
var stateHeader:Boolean = true;
var headerRegex:RegExp = /\s*(\d{1,2})\s?(t|T)=(\d{1,3})\s*/g;
var normalRegex:RegExp = /\s*(t|T)\s?(\d{1,3})\s([0-3])\s([-+]?\d{1,3})\,?\s*/g;
var unfinishedRegex:RegExp = /\s*(t|T)\s?(\d{1,3})?\s?([0-3])?\s?([-+]?\d{1,3})?\,?\s*/g;
var lineNo:int = 0;
var message:String;
LINES : for each (var rawLine:String in lines)
{
lineNo++;
if (!rawLine || !rawLine.length) continue;
var comment:int = rawLine.indexOf("//");
if (!comment) continue;
var line:String = comment > 0 ? rawLine.substring(0, comment) : rawLine;
trace(line);
angle = 0;
if (stateHeader)
{
headerRegex.lastIndex = 0;
match = headerRegex.exec(line);
if (!match)
{
message = parseOptionsLine(line);
if (message)
return "Header invalid: line " + lineNo + ", pos " + headerRegex.lastIndex + ", " + message;
continue;
}
rounds = (int)(match[1]);
rhombType = match[2];
rhombAngle = (Number)(match[3]);
for (round = 0; round < rounds; round++)
{
rhomb = (rhombType == "t") ? new ThinRhomb() : new ThickRhomb();
rhomb.byAxis(cx, cy, angle);
angles.push(rhombAngle);
angle += rhombAngle;
lastLevel.push(rhomb);
}
stateHeader = false;
} else
{
if (!angles) return "No header found: line " + lineNo;
var thisLevel:Vector.<Rhomb> = new Vector.<Rhomb>();
ROUNDS : for (round = 0; round < rounds; round++)
{
var earlyExit:Boolean = false;
normalRegex.lastIndex = 0;
newLevelLength = 0;
do
{
unfinishedRegex.lastIndex = normalRegex.lastIndex;
match = normalRegex.exec(line);
if (!match)
{
//try unfinished regex
match = unfinishedRegex.exec(line);
if (!match)
{
message = parseOptionsLine(line);
if (message)
return "Line invalid: line " + lineNo + ", pos " + normalRegex.lastIndex + ", " + message;
else
continue LINES;
}
var prev:Rhomb;
if (match[2] == undefined)
{
//select entire previous level
for each (prev in lastLevel)
{
prev.Selected = true;
}
} else if (match[3] == undefined)
{
//select anchor figures
anchorIndex = (int)(match[2]);
if (anchorIndex < lastLevelLength)
{
for (var r:int = 0; r < rounds; r++)
{
prev = lastLevel[r * lastLevelLength + anchorIndex];
prev.Selected = true;
}
}
} else
{
//select anchor point
anchorIndex = (int)(match[2]);
anchorPoint = (int)(match[3]);
if (anchorIndex < lastLevelLength && anchorPoint <= 3)
{
for (var r1:int = 0; r1 < rounds; r1++)
{
prev = lastLevel[r1 * lastLevelLength + anchorIndex];
prev.PointSelected = true;
prev.SelectedX = prev.x(anchorPoint);
prev.SelectedY = prev.y(anchorPoint);
}
}
}
earlyExit = true;
continue;
//return "Unfinished token at " + lineNo;
}
rhombType = match[1];
var anchorIndex:int = (int)(match[2]);
var anchorPoint:int = (int)(match[3]);
rhombAngle = (Number)(match[4]);
rhomb = (rhombType == "t") ? new ThinRhomb() : new ThickRhomb();
var lastFigureIndex:int = round * lastLevelLength + anchorIndex;
if (lastFigureIndex >= lastLevel.length)
return "Anchor figure index out of range: " + lastFigureIndex;
var anchorFigure:Rhomb = lastLevel[lastFigureIndex];
var anchorX:Number = anchorFigure.x(anchorPoint);
var anchorY:Number = anchorFigure.y(anchorPoint);
rhomb.byAxis(anchorX, anchorY, angle + rhombAngle);
thisLevel.push(rhomb);
newLevelLength++;
} while (normalRegex.lastIndex < line.length && !earlyExit);
angle += angles[round];
}
lastLevelLength = newLevelLength;
lastLevel = thisLevel;
}
}
return null;
}
protected function parseOptionsLine(line:String):String
{
var optionsRegex:RegExp = /\s*([a-z]+)=(\d{1,})\s*/g;
do
{
var match:Object = optionsRegex.exec(line);
if (!match) return null;
var name:String = match[1];
var value:int = (int)(match[2]);
switch (name)
{
case "side":
if (value > 0)
{
var tcr:ThickRhomb = new ThickRhomb();
tcr.Side = value;
tcr.init();
var tnr:ThinRhomb = new ThinRhomb();
tnr.Side = value;
tnr.init();
}
break;
case "cx":
cx = value;
break;
case "cy":
cy = value;
break;
default:
return "unknown token: " + name;
}
} while (optionsRegex.lastIndex < line.length);
return null;
}
protected function updateTiling():void
{
var g:Graphics = container.graphics;
g.clear();
g.lineStyle(1, 0xFFFFFF);
g.drawRect(1, 1, 463, 463);
for each (var rhomb:Rhomb in rhombs)
{
g.lineStyle(1);
if (rhomb.Selected)
{
g.beginBitmapFill(selectionBitmap);
} else
{
g.beginFill(rhomb.color, 0.5);
}
g.moveTo(rhomb.p1x, rhomb.p1y);
g.lineTo(rhomb.p2x, rhomb.p2y);
g.lineTo(rhomb.p3x, rhomb.p3y);
g.lineTo(rhomb.p4x, rhomb.p4y);
g.endFill();
if (rhomb.PointSelected)
{
g.lineStyle(1, 0xFFFFFF);
g.drawCircle(rhomb.SelectedX, rhomb.SelectedY, 3);
}
}
}
}
}
import flash.geom.Point;
class Rhomb
{
public function get AcuteAngle():Number { return 0; }
public function get Color():uint { return 0; }
public function get BigDiag():Number { return 0; }
public function get SmallDiag():Number { return 0; }
protected static var side:Number = 10;
public var p1x:Number, p1y:Number;
public var p2x:Number, p2y:Number;
public var p3x:Number, p3y:Number;
public var p4x:Number, p4y:Number;
public var Selected:Boolean;
public var PointSelected:Boolean;
public var SelectedX:Number;
public var SelectedY:Number;
public static var rhombs:Vector.<Rhomb>;
public function get Side():Number
{
return side;
}
public function set Side(value:Number):void
{
side = value;
init();
}
public function Rhomb():void
{
if (rhombs) rhombs.push(this);
}
public function get color():uint
{
return Color;
}
public function x(index:int):Number
{
switch (index)
{
case 0: return p1x;
case 1: return p2x;
case 2: return p3x;
case 3: return p4x;
}
return NaN;
}
public function y(index:int):Number
{
switch (index)
{
case 0: return p1y;
case 1: return p2y;
case 2: return p3y;
case 3: return p4y;
}
return NaN;
}
/* p2
^
. a2 .
. .
p1 < a1) (a1 > p3
. .
. a2 .
v
p4
*/
public function byAxis(i1x:Number, i1y:Number, angle:Number):void
{
var aRad:Number = angle * Math.PI / 180;
p1x = i1x;
p1y = i1y;
p2x = p1x + Side * Math.sin(aRad + AcuteAngle * 0.5);
p2y = p1y + Side * Math.cos(aRad + AcuteAngle * 0.5);
p4x = p1x + Side * Math.sin(aRad - AcuteAngle * 0.5);
p4y = p1y + Side * Math.cos(aRad - AcuteAngle * 0.5);
p3x = p1x + BigDiag * Math.sin(aRad);
p3y = p1y + BigDiag * Math.cos(aRad);
}
public function init():void {}
}
class ThinRhomb extends Rhomb
{
protected static var bigDiag:Number, smallDiag:Number;
protected static var acuteAngle:Number = 36 * Math.PI / 180;
override public function get AcuteAngle():Number
{
return acuteAngle;
}
override public function get Color():uint
{
return 0xB00000;
}
override public function get BigDiag():Number
{
return bigDiag;
}
override public function get SmallDiag():Number
{
return smallDiag;
}
override public function init():void
{
bigDiag = side * 2 * Math.cos(AcuteAngle * 0.5);
smallDiag = side * 2 * Math.sin(AcuteAngle * 0.5);
}
}
class ThickRhomb extends Rhomb
{
protected static var bigDiag:Number, smallDiag:Number;
protected static const acuteAngle:Number = 72 * Math.PI / 180;
override public function get AcuteAngle():Number
{
return acuteAngle;
}
override public function get Color():uint
{
return 0x00B000;
}
override public function get BigDiag():Number
{
return bigDiag;
}
override public function get SmallDiag():Number
{
return smallDiag;
}
override public function init():void
{
bigDiag = side * 2 * Math.cos(AcuteAngle * 0.5);
smallDiag = side * 2 * Math.sin(AcuteAngle * 0.5);
}
}