Barnsly Sierpinski Triangle

by Aquioux
♥0 | Line 370 | Modified 2013-02-25 23:05:45 | MIT License
play

ActionScript3 source code

/**
 * Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/gutJ
 */

package {
    //import aquioux.display.colorUtil.CycleRGB;
    //import aquioux.display.fractal.Engine;
    //import aquioux.display.fractal.InputBehavior;
    //import aquioux.display.fractal.Viewer;
    import com.bit101.components.PushButton;
    import flash.display.Sprite;
    import flash.events.Event;
    [SWF(width = "465", height = "465", frameRate = "60", backgroundColor = "#000000")]
    /**
     * Barnsly Sierpinski Triangle
     * @see    http://aquioux.net/blog/?p=3359
     * @author YOSHIDA, Akio (Aquioux)
     */
    public class Main extends Sprite {
        // ビューアサイズ
        private const WIDTH:int  = stage.stageWidth;
        private const HEIGHT:int = stage.stageHeight;
        
        // 各連携クラス
        private var calculator_:Mandelbrot;            // フラクタル計算クラス
        private var viewer_:Viewer;                    // ビューアクラス
        private var inputBehavior_:InputBehavior;    // ユーザー入力挙動クラス
        private var engine_:Engine;                    // フラクタル描画エンジンクラス
        
        // コンストラクタ
        public function Main() {
            // カラーマップ生成
            var degree:int  = 15;
            var step:Number = 360 / (degree + 1);
            var start:int = Math.random() * 360 >> 0;
            var colorMap:Vector.<uint> = new Vector.<uint>(degree, true);
            for (var i:int = 0; i < degree; i++) colorMap[i] = CycleRGB.getColor(i * step + start);

            // フラクタル計算クラス
            calculator_ = new Mandelbrot();
            calculator_.colorMap = colorMap;
            
            // ビューアクラス
            viewer_ = new Viewer(WIDTH, HEIGHT);
            viewer_.heightOnceDraw = 5;
            addChild(viewer_);
            
            // ユーザー入力挙動クラス
            inputBehavior_ = new InputBehavior(viewer_);
            
            // フラクタル描画エンジンクラス
            engine_ = new Engine();
            engine_.viewer     = viewer_;
            engine_.controller = inputBehavior_;
            engine_.calculator = calculator_;
            
            // ボタンパラメータ
            var buttonWidth:int  = 75;
            var buttonHeight:int = 20;
            // リセットボタン
            // ズーム
            var zoomResetButton:PushButton = new PushButton(this, 0, 0, "ZoomReset", zoomResetButtonHandler);
            zoomResetButton.width  = buttonWidth;
            zoomResetButton.height = buttonHeight;
            
            // 初回の描画
            // ズーム初期化
            zoomResetButtonHandler(null);
        }
        
        // リセットボタンハンドラ
        // ズーム
        private function zoomResetButtonHandler(event:Event):void {
            engine_.reset();
            engine_.start();
        }
    }
}

//package {
    //import aquioux.display.fractal.ICalculator;
    //import aquioux.math.Complex;
    import flash.geom.Rectangle;
    /**
     * Barnsley Sierpinski Triangle 計算クラス
     * base : Mandelbrot
     * デフォルト時、(-2.0, -2.0) ~ (2.0, 2.0) の領域を計算する
     * @author YOSHIDA, Akio (Aquioux)
     */
    /*public*/ class Mandelbrot implements ICalculator {
        /**
         * 描画範囲
         */
        public function get DEFAULT_RECT():Rectangle { return RECTANGLE; }
        private const RECTANGLE:Rectangle = new Rectangle(START_X, START_Y, (END_X - START_X), (END_Y - START_Y));
        // 個別の開始座標、終了座標
        private const START_X:Number = -2.0;        // 開始X座標値
        private const START_Y:Number = -2.0;        // 開始Y座標値
        private const END_X:Number   =  2.0;        // 終了X座標値
        private const END_Y:Number   =  2.0;        // 終了Y座標値

        /**
         * 集合に該当する部分の色(一般的には色なし=黒)
         */
        public function set color(value:uint):void { _color = value; }
        private var _color:uint = 0x000000;
        /**
         * 発散部分のカラーリングマップ
         */
        public function set colorMap(value:Vector.<uint>):void {
            _colorMap = value;
            degree_   = value.length;
        }
        private var _colorMap:Vector.<uint>;
        
        // 発散チェックループ回数(_colorMap.length の値)
        private var degree_:int;
        
        
        /**
         * 指定座標の計算をおこなう
         * @param    x    X座標値
         * @param    y    Y座標値
         * @return    計算結果
         */
        public function calculate(x:Number, y:Number):uint {
            var r:int = formula(x, y);
            return (r >= 0) ? _colorMap[r] : _color;
        }
        /**
         * 漸化式
         * z ← 2 * z - a
         * Rl[z] > 0.5 a = 1
         * Im[z] > 0.5 a = i
         * else        a = 0
         * @param    zRl    複素数 z の実数部
         * @param    zIm    複素数 z の虚数部
         * @return    発散評価値
         * @private
         */
        private function formula(zRl:Number, zIm:Number):int {
            var i:int = degree_;
            while (i--) {
                // 発散の評価(|z| > 2 = |z|^2 > 4)
                if (zRl * zRl + zIm * zIm > 4) break;
                
                // 漸化式
                var aRl:Number = 0;
                var aIm:Number = 0;
                if (zRl > 0.5) {
                    aRl = 1;
                } else if (zIm > 0.5) {
                    aIm = 1;
                }
                zRl = zRl * 2 - aRl;
                zIm = zIm * 2 - aIm;
            }
            return i;
            // break で脱しなかった(発散しなかった)場合、while を回りきるので i は -1 になる
        }
    }
//}

//package aquioux.display.fractal {
    import flash.geom.Rectangle;
    /**
     * フラクタル計算クラスの interface
     * @author YOSHIDA, Akio (Aquioux)
     */
    /*public*/ interface ICalculator {
        function get DEFAULT_RECT():Rectangle;                // 描画範囲デフォルト値
        function set color(value:uint):void;                // 集合該当する部分の色
        function set colorMap(value:Vector.<uint>):void;    // 発散部分のカラーリングマップ
        function calculate(x:Number, y:Number):uint;        // 計算部
    }
//}

//package aquioux.display.fractal {
    import flash.events.Event;
    import flash.geom.Rectangle;
    /**
     * フラクタル描画エンジンクラス
     * @author YOSHIDA, Akio (Aquioux)
     */
    /*public*/ class Engine {
        /**
         * フラクタル計算クラス
         */
        public function set calculator(value:ICalculator):void { _calculator = value; }
        private var _calculator:ICalculator;
        
        /**
         * ユーザー入力挙動クラス
         */
        public function set controller(value:InputBehavior):void {
            _controller = value;
            _controller.addEventListener(InputBehavior.MOVING,  moveHandler);
            _controller.addEventListener(InputBehavior.ZOOMING, zoomHandler);
            _controller.addEventListener(Event.CHANGE, start);
        }
        private var _controller:InputBehavior;
        
        /**
         * ビューアクラス
         */
        public function set viewer(value:Viewer):void {
            _viewer = value;
            _viewer.addEventListener(Event.CHANGE, draw);
            displayWidth_  = _viewer.width;
            displayHeight_ = _viewer.height;
            dataLen_ = displayWidth_ * _viewer.drawRect.height;
            data_    = new Vector.<uint>(dataLen_, true);
        }
        private var _viewer:Viewer;
        
        /**
         * 描画計算領域を示す Rectangle
         */
        public function set rect(value:Rectangle):void {
            // 引数保持
            _rect = value;
            // 複素平面の走査開始座標を設定する
            startX_ = _rect.x;
            startY_ = _rect.y;
            // 複素平面の走査加算値を計算する
            stepX_ = _rect.width  / (displayWidth_  - 1);
            stepY_ = _rect.height / (displayHeight_ - 1);
        }
        private var _rect:Rectangle;
        
        // ビューア描画用データ
        private var data_:Vector.<uint>;
        // 描画ピクセル数(data_.length)
        private var dataLen_:int;
        
        // ビューアサイズ
        private var displayWidth_:int;        // 幅
        private var displayHeight_:int;        // 高

        // 複素平面の走査計算開始座標
        private var startX_:Number;            // 実数座標
        private var startY_:Number;            // 虚数座標

        // 複素平面の走査計算加算値
        private var stepX_:Number;            // 実数軸
        private var stepY_:Number;            // 虚数軸

        
        /**
         * 描画計算領域のリセット
         */
        public function reset():void {
            rect = _calculator.DEFAULT_RECT;
        }

        /**
         * 描画開始
         * @param    event    イベント
         */
        public function start(event:Event = null):void {
            if (event) {    // _controller の Event.CHANGE からの呼び出し
                var r:Rectangle = _controller.rect.clone();
                r.x = r.x * stepX_ + _rect.x;
                r.y = r.y * stepY_ + _rect.y;
                r.width  *= stepX_;
                r.height *= stepY_;
                rect = r;
            }
            _viewer.start();
        }
        // 描画実行
        private function draw(event:Event):void {
            // 描画部分の計算
            var drawOffset:int = _viewer.drawRect.y;
            for (var i:int = 0; i < dataLen_; i++) {
                var posX:int = i % displayWidth_;
                var posY:int = drawOffset + i / displayWidth_ >> 0;
                data_[i] = _calculator.calculate(posX * stepX_ + startX_, posY * stepY_ + startY_);
            }
            _viewer.draw(data_);
        }

        // 移動
        private function moveHandler(e:Event):void {
            _viewer.move(_controller.rect);
        }
        // 拡大・縮小
        private function zoomHandler(e:Event):void {
            _viewer.zoom(_controller.rect);
        }
    }
//}

//package aquioux.display.fractal {
    import flash.display.DisplayObject;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.geom.Rectangle;
    /**
     * ユーザー入力挙動クラス
     * マウスドラッグ
     *   矩形を描く、マウスボタンを離すとその矩形領域を全体として表示(拡大)
     * cntl ボタン+マウスドラッグ
     *   表示領域の移動(移動)
     * マウスホイール
     *   マウスカーソル位置を中心に拡大(↑)・縮小(↓)
     * @author YOSHIDA, Akio (Aquioux)
     */
    /*public*/ class InputBehavior extends EventDispatcher {
        /**
         * 移動カスタムイベント
         */
        public static const MOVING:String  = "moving";
        /**
         * ズームカスタムイベント
         */
        public static const ZOOMING:String = "zooming";

        /**
         * マウス挙動 Rectangle
         */ 
        public function get rect():Rectangle { return _rect; }
        private var _rect:Rectangle = new Rectangle();

        // 矩形描画、移動に係る変数
        private var startX_:Number;        // ドラッグ開始X座標
        private var startY_:Number;        // ドラッグ開始Y座標
        private var moveX_:Number;        // ドラッグによる移動量(X軸方向)
        private var moveY_:Number;        // ドラッグによる移動量(Y軸方向)
        private var isCtrlDown_:Boolean = false;    // cntl キーダウン
        
        // マウス入力挙動対象
        private var target_:DisplayObject;
        

        /**
         * コンストラクタ
         * @param    target    マウス入力挙動対象
         */
        public function InputBehavior(target:DisplayObject) {
            // マウス入力挙動対象
            target_ = target;
            // マウスイベントハンドラ登録
            target_.addEventListener(MouseEvent.MOUSE_DOWN,  mouseDownHandler);
            target_.addEventListener(MouseEvent.MOUSE_WHEEL, mouseWheelHandler);
            // キーボードイベントハンドラ登録
            target_.stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
            target_.stage.addEventListener(KeyboardEvent.KEY_UP,   keyUpHandler);
        }

        // マウスイベントハンドラ
        private function mouseDownHandler(event:MouseEvent):void {
            // 起点待避
            startX_ = target_.mouseX;
            startY_ = target_.mouseY;

            // マウスイベントハンドラ登録
            if (isCtrlDown_) {    // 移動
                _rect.width  = target_.width;
                _rect.height = target_.height;
                target_.addEventListener(MouseEvent.MOUSE_MOVE, moving);        // 移動中
                target_.addEventListener(MouseEvent.MOUSE_UP,   moved);            // 移動終了
            } else {            // 拡大
                target_.addEventListener(MouseEvent.MOUSE_MOVE, zooming);        // 拡大中
                target_.addEventListener(MouseEvent.MOUSE_UP,   zoomed);        // 拡大終了
            }
        }

        // マウスイベントハンドラ
        // 移動中
        private function moving(event:MouseEvent):void {
            // 移動量の計算
            moveX_ = startX_ - target_.mouseX;
            moveY_ = startY_ - target_.mouseY;
            // _rect 調整
            _rect.x = moveX_;
            _rect.y = moveY_;
            // イベント送出
            dispatchEvent(new Event(MOVING));
        }
        // 移動終了
        private function moved(event:MouseEvent):void {
            // マウスイベントハンドラ解除
            target_.removeEventListener(MouseEvent.MOUSE_MOVE, moving);
            target_.removeEventListener(MouseEvent.MOUSE_UP,   moved);
            // イベント送出
            update();
        }

        // 拡大中
        private function zooming(event:MouseEvent):void {
            // 移動量の計算
            moveX_ = target_.mouseX - startX_;
            moveY_ = target_.mouseY - startY_;
            // 選択領域を正方形にする
            // 絶対値を求める
            var isMinusWidth:Boolean  = moveX_ < 0 ? true : false;
            var isMinusHeight:Boolean = moveY_ < 0 ? true : false;
            if (isMinusWidth)  moveX_ *= -1;
            if (isMinusHeight) moveY_ *= -1;
            // 幅・高のうち大きい方を正方形の辺とする
            var edge:Number = moveX_ > moveY_ ? moveX_ : moveY_;
            moveX_ = edge;
            moveY_ = edge;
            // _rect 調整
            _rect.x      = startX_;
            _rect.y      = startY_;
            _rect.width  = moveX_;
            _rect.height = moveY_;
            if (isMinusWidth)  _rect.x -= _rect.width;
            if (isMinusHeight) _rect.y -= _rect.height;
            // イベント送出
            dispatchEvent(new Event(ZOOMING));
        }
        // 拡大終了
        private function zoomed(event:MouseEvent):void {
            // マウスイベントハンドラ解除
            target_.removeEventListener(MouseEvent.MOUSE_MOVE, zooming);
            target_.removeEventListener(MouseEvent.MOUSE_UP,   zoomed);
            // イベント送出
            update();
        }
        
        // マウスホイールイベントハンドラ
        private function mouseWheelHandler(event:MouseEvent):void {
            // マウスホイールの移動量をスケールに変換
            var delta:Number = event.delta * 0.5;
            delta = (delta < 0) ? -delta : 1 / delta;
            // _rect を計算
            var nowWidth:Number  = target_.width  * delta;
            var nowHeight:Number = target_.height * delta;
            _rect.x      = target_.mouseX - nowWidth  / 2;
            _rect.y      = target_.mouseY - nowHeight / 2;
            _rect.width  = nowWidth;
            _rect.height = nowHeight;
            // イベント送出
            update();
        }
        
        // イベント送出(マウスアップ時)
        private function update():void {
            dispatchEvent(new Event(Event.CHANGE));
        }
        
        // キーボードイベントハンドラ
        // キーダウン
        private function keyDownHandler(event:KeyboardEvent):void {
            if (event.ctrlKey) isCtrlDown_ = true;
        }
        // キーアップ
        private function keyUpHandler(event:KeyboardEvent):void {
            if (isCtrlDown_) isCtrlDown_ = false;
        }
    }
//}

//package aquioux.display.fractal {
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.BlendMode;
    import flash.display.Graphics;
    import flash.display.Shape;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.ColorTransform;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    /**
     * ビューア
     * @author YOSHIDA, Akio (Aquioux)
     */
    /*public*/ class Viewer extends Sprite {
        /**
         * 1度の draw で描画する高さ(drawRect.y への加算値)
         */
        public function get heightOnceDraw():int { return _heightOnceDraw; }
        public function set heightOnceDraw(value:int):void {
            _heightOnceDraw  = value;
            _drawRect.height = value;
        }
        private var _heightOnceDraw:int = 1;

        /**
         * キャンバス描画 Rectangle(1度に描画するエリアを示す Rectangle)
         */
        public function get drawRect():Rectangle { return _drawRect; }    // read only
        private var _drawRect:Rectangle;
        
        // キャンバス
        private var canvas_:BitmapData;
        private var canvasRect_:Rectangle;

        // 移動ガイド
        private var moveGuideBm_:Bitmap;
        private var moveGuideBmd_:BitmapData;
        // 移動ガイド表示中か否か
        private var isMove_:Boolean = false;
        
        // ズームガイド
        private var zoomGuideGraphics_:Graphics;
        
        // 描画のために使いまわす Point
        private var destPoint_:Point = new Point();

        // 画面を暗くする効果用
        private const FADE:ColorTransform = new ColorTransform(0.75, 0.75, 0.75);

        
        /**
         * コンストラクタ
         * @param    w    描画幅
         * @param    h    描画高
         */
        public function Viewer(w:int, h:int) {
            // キャンバス生成
            canvas_ = new BitmapData(w, h, false, 0x0);
            addChild(new Bitmap(canvas_));
            canvasRect_ = canvas_.rect;
            
            // キャンバス描画 Rectangle 生成
            _drawRect = new Rectangle(0, 0, w, _heightOnceDraw);

            // 移動ガイド生成
            moveGuideBmd_ = canvas_.clone();
            moveGuideBm_  = new Bitmap(moveGuideBmd_);
            moveGuideBm_.alpha = 0;
            addChild(moveGuideBm_);
            
            // ズームガイド生成
            var zoomGuideShape:Shape = new Shape();
            zoomGuideShape.blendMode = BlendMode.INVERT;
            addChild(zoomGuideShape);
            zoomGuideGraphics_ = zoomGuideShape.graphics;
        }

        /**
         * 移動時の挙動
         * @param    rect    挙動を制御する Rectangle
         */
        public function move(rect:Rectangle):void {
            if (!isMove_) {    // 移動開始時
                // キャンバス全体を暗くする
                canvas_.colorTransform(canvasRect_, FADE);
                // ガイドの生成(キャンバスをそのまま写す)
                destPoint_.x = 0;
                destPoint_.y = 0;
                moveGuideBmd_.copyPixels(canvas_, canvasRect_, destPoint_);
                moveGuideBm_.alpha = 0.5;
                isMove_ = true;
            }
            
            // 移動ガイドの移動
            moveGuideBm_.x = -rect.x;
            moveGuideBm_.y = -rect.y;
        }
        
        /**
         * ズーム時の挙動
         * @param    rect    挙動を制御する Rectangle
         */
        public function zoom(rect:Rectangle):void {
            // ズーム用枠の表示
            zoomGuideGraphics_.clear();
            zoomGuideGraphics_.lineStyle(0, 0x0);
            zoomGuideGraphics_.drawRect(rect.x, rect.y, rect.width, rect.height);
        }
        

        /**
         * 描画開始
         */
        public function start():void {
            if (isMove_) {    // 移動から呼び出されたとき
                // destPoint を更新
                destPoint_.x = moveGuideBm_.x;
                destPoint_.y = moveGuideBm_.y;
                // 移動ガイドをキャンバスに描画
                canvas_.lock();
                canvas_.copyPixels(moveGuideBmd_, canvasRect_, destPoint_);
                canvas_.unlock();
                // 移動ガイドの後処理
                moveGuideBm_.alpha = 0;
                moveGuideBm_.x = 0;
                moveGuideBm_.y = 0;
                isMove_ = false;
            } else {        // ズームから呼び出されたとき
                // ズームガイドに表示された枠を消す
                zoomGuideGraphics_.clear();
                // キャンバスを暗くする
                canvas_.colorTransform(canvasRect_, FADE);
            }
            
            // キャンバス描画 Rectangle の初期化
            _drawRect.y = 0;
            // ENTER_FRAME イベント開始
            addEventListener(Event.ENTER_FRAME, function():void { dispatchEvent(new Event(Event.CHANGE)); } );
        }
        /**
         * 描画実行
         * @param    data    キャンバスを描くための Vector.<uint>
         */
        public function draw(data:Vector.<uint>):void {
            // 指定領域の更新
            canvas_.lock();
            canvas_.setVector(_drawRect, data);
            canvas_.unlock();
            // キャンバス描画 Rectangle の更新
            _drawRect.y += _heightOnceDraw;
            // 終了判定
            if (_drawRect.y >= canvas_.width) removeEventListener(Event.ENTER_FRAME, arguments.callee);
        }
    }
//}

//package aquioux.math {
    /**
     * 複素数
     * @author Aquioux(Yoshida, Akio)
     */
    /*public*/ final class Complex {
        /**
         * 実数部
         */
        public function get real():Number { return _real; }
        public function set real(value:Number):void { _real = value; }
        public function get x():Number { return _real; }
        public function set x(value:Number):void { _real = value; }
        // 実数部
        private var _real:Number;

        /**
         * 虚数部
         */
        public function get imag():Number { return _imag; }
        public function set imag(value:Number):void { _imag = value; }
        public function get y():Number { return _imag; }
        public function set y(value:Number):void { _imag = value; }
        // 虚数部
        private var _imag:Number;


        /**
         * コンストラクタ
         * @param    real    実数部
         * @param    imag    虚数部
         */
        public function Complex(real:Number = 0.0, imag:Number = 0.0) {
            _real = real;
            _imag = imag;
        }

        /**
         * 複製
         * @return    複製した複素数
         */
        public function clone():Complex {
            return new Complex(_real, _imag);
        }

        /**
         * toString
         * @return    文字列表示
         */
        public function toString():String {
            return String(_real) + " + " + String(_imag) + " i";
        }
    }
//}

//package aquioux.display.colorUtil {
    /**
     * コサインカーブで色相環的な RGB を計算
     * @author Aquioux(YOSHIDA, Akio)
     */
    /*public*/ class CycleRGB {
        /**
         * 32bit カラーのためのアルファ値(0~255)
         */
        static public function get alpha():uint { return _alpha; }
        static public function set alpha(value:uint):void {
            _alpha = (value > 0xFF) ? 0xFF : value;
        }
        private static var _alpha:uint = 0xFF;
    
        private static const PI:Number = Math.PI;        // 円周率
        private static const DEGREE120:Number  = PI * 2 / 3;    // 120度(弧度法形式)
        

        /**
         * 角度に応じた RGB を得る
         * @param    angle    HSV のように角度(度数法)を指定
         * @return    色(0xNNNNNN)
         */
        public static function getColor(angle:Number):uint {
            var radian:Number = angle * PI / 180;
            var r:uint = (Math.cos(radian)             + 1) * 0xFF >> 1;
            var g:uint = (Math.cos(radian + DEGREE120) + 1) * 0xFF >> 1;
            var b:uint = (Math.cos(radian - DEGREE120) + 1) * 0xFF >> 1;
            return r << 16 | g << 8 | b;
        }
        
        /**
         * 角度に応じた RGB を得る(32bit カラー)
         * @param    angle    HSV のように角度(度数法)を指定
         * @return    色(0xNNNNNNNN)
         */
        public static function getColor32(angle:Number):uint {
            return _alpha << 24 | getColor(angle);
        }
    }
//}