In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

P3 Penrose tiling (scripted)

Try to edit script, it is executed instantly.
/**
 * Copyright signedvoid ( http://wonderfl.net/user/signedvoid )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/cF56
 */

package
{
    import com.bit101.components.CheckBox;
    import com.bit101.components.Label;
    import com.bit101.components.TextArea;
    
    import flash.display.Graphics;
    import flash.display.Sprite;
    import flash.events.Event;
    

    /*
     * 
    Target:
    Generate tilings with two types of rhombs:
    angles 36, 144 degrees and 72, 108 degrees.
    
    Step one:
     - learn how to draw each rhomb by 3 points

    Step two:
     - Edit placement rules in a script

    Step three:
     - Highlight anchor layer/figure/point when typing (TODO)
    */
    /*
    10 t=36 side=19 cx=232 cy=270
T 0 1 18
T 0 2 126
t 0 3 144
T 0 3 -18, T 0 2 18, t 0 3 36
    */
    [SWF(width="465", height="465", backgroundColor="0xCCCCCC")]
    public class PenroseP3Tiling extends Sprite
    {
        protected var rhombs:Vector.<Rhomb>;
        protected var container:Sprite;
        
        protected var textArea:TextArea;
        protected var message:Label;
        protected var textAreaControl:CheckBox;
        
        public function PenroseP3Tiling()
        {
            //init
            rhombs = new Vector.<Rhomb>();
            container = new Sprite();
            addChild(container);
            Rhomb.rhombs = rhombs;
            
            //gui
            var script:String =
                "//header format - repeat count, shape type (t or T), turning angle, side length, center point:\n" +
                "5 T=72 side=18 cx=232 cy=270\n" +
                "//regular row: t or T, anchor figure from previous level,\n" +
                "//then anchor point on that figure (0 - 3), then angle:\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 0 2 0, t 0 2 54, t 0 2 -54, T 1 2 -72, t 1 2 -18, t 1 2 -126, T 4 3 -72, T 5 1 0, T 6 2 108, T 6 2 -108, T 6 2 36, T 6 2 -36\n" +
                "T 3 2 36, T 3 2 -36, T 3 2 -108, T 3 2 -180, T 0 2 108, T 0 2 -108, T 0 2 36, T 0 2 -36\n";
            /*
            //header format - repeat count, shape type (t or T), turning angle, side
            10 t=36 side=18 cx=232 cy=270
            T 0 1 18
            T 0 2 -90
            t 0 1 108, t 0 1 0
            T 0 0 54, T 0 2 -18, T 1 2 126, T 1 2 -126
            T 3 2 -18, t 2 0 72, t 2 0 -72
            T 1 3 -90,
            */
            textArea = new TextArea(container, 0, 0, script);
            textArea.width = 455;
            textArea.height = 120;
            textArea.addEventListener(Event.CHANGE, onScriptChanged);
            message = new Label(container, 0, 445);
            textAreaControl = new CheckBox(container, 455, 0, "x", onTextAreaToggle);
            textAreaControl.selected = true;
            
            onScriptChanged(null);
        }

        protected function onScriptChanged(event:Event):void
        {
            var result:String = parseString(textArea.text);
            message.text = result;
            updateTiling();
        }
        
        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 cx:Number = 465 * 0.5;
            var cy:Number = 465 * 0.5;
            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 = /(\d{1,2})\s?(t|T)=(\d{1,3})\s+side=(\d{1,3})\s+cx=(\d{1,4})\s+cy=(\d{1,4})/g;
            var normalRegex:RegExp = /(t|T)\s?(\d{1,3})\s([0-3])\s([-+]?\d{1,3})\,?\s?/g;
            var lineNo:int = 0;
            
            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) return "Header invalid: line " + lineNo + ", pos " + headerRegex.lastIndex + ", line: \"" + line + "\"";
                    rounds = (int)(match[1]);
                    rhombType = match[2];
                    rhombAngle = (Number)(match[3]);
                    var side:Number = (Number)(match[4]);
                    if (side > 0)
                    {
                        var tcr:ThickRhomb = new ThickRhomb();
                        tcr.Side = side;
                        tcr.init();
                        var tnr:ThinRhomb = new ThinRhomb();
                        tnr.Side = side;
                        tnr.init();
                    }
                    cx = (int)(match[5]);
                    cy = (int)(match[6]);
                    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>();
                    for (round = 0; round < rounds; round++)
                    {
                        normalRegex.lastIndex = 0;
                        newLevelLength = 0;
                        do
                        {
                            match = normalRegex.exec(line);
                            if (!match) return "Line invalid: line " + lineNo + ", pos " + normalRegex.lastIndex;
                            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);
                        angle += angles[round];
                    }
                    lastLevelLength = newLevelLength;
                    lastLevel = thisLevel;
                }
            }
            
            return null;
        }

        protected function updateTiling():void
        {
            var g:Graphics = container.graphics;
            g.clear();
            g.lineStyle(1, 0xFFFFFF);
            g.drawRect(1, 1, 463, 463);
            g.lineStyle(1);
            for each (var rhomb:Rhomb in rhombs)
            {
                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();
            }
        }
    }
}

class Rhomb
{
    public function get AcuteAngle():Number { return 0; }
    public function get ObtuseAngle():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 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;
    }

    /*         p1
               ^
           . Obtuse .
       .               .
 p0 <  Acute)         (A > p2
       .               .
           .   O   .
               v
              p3
    */
    
    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;

    override public function get AcuteAngle():Number
    {
        return 36 * Math.PI / 180;
    }
    
    override public function get ObtuseAngle():Number
    {
        return 144 * Math.PI / 180;
    }

    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;

    override public function get AcuteAngle():Number
    {
        return 72 * Math.PI / 180;
    }

    override public function get ObtuseAngle():Number
    {
        return 108 * Math.PI / 180;
    }

    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);
    }
}