Beautifl - Flash Gallery

Thumbnail : Glynn Fractal
Glynn Fractal
Aquioux 2011-12-16 MIT License

再生するにはFlash Playerが必要です。デスクトップのブラウザでご覧ください。

package {
    //import aquioux.display.colorUtil.CycleRGB;
    import com.bit101.components.PushButton;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.Rectangle;
    [SWF(width = "465", height = "465", frameRate = "60", backgroundColor = "#000000")]
    /**
     * Glynn Fractal の描画
     * @see    http://aquioux.net/blog/?p=2146
     * @author Aquioux(Yoshida, Akio)
     */
    public class Main extends Sprite {
        // Glynn Fractal でない部分の色
        private var colors0_:Vector.<uint>;
        private var colors1_:Vector.<uint>;
        
        // 計算クラス
        private var julia_:Julia;
        // 走査クラス
        private var scan_:Scan;
        // 表示用 BitmapData
        private var bmd_:BitmapData;
        
        // 前回押したボタン
        private var prevButtonColor_:PushButton;
        private var prevButtonScale_:PushButton;
        
        
        // コンストラクタ
        public function Main():void {
            // カラーセットパターン1
            colors0_ = new Vector.<uint>();
            var degree:int = 128;
            var step:Number = 0xFF / degree;
            for (var i:int = 0; i < degree; i++) {
                var c:uint = step * i;
                colors0_[i] = c << 16 | c << 8 | c;
            }
            colors0_.fixed = true;
            
            // カラーセットパターン2
            colors1_ = new Vector.<uint>();
            degree = 120;
            step = 360 / degree;
            for (i = 0; i < degree; i++) colors1_[i] = CycleRGB.getColor(i * step + 180);
            colors1_.reverse();
            colors1_.fixed = true;

            // ステージサイズ
            var sw:int = stage.stageWidth;
            var sh:int = stage.stageHeight;
            
            // Glynn Fractal クラスの生成
            julia_ = new Julia();
            julia_.colors = colors0_;
            
            // Scan クラスの生成
            scan_ = new Scan(sw, sh, julia_);

            // Viewer の作成
            bmd_ = new BitmapData(sw, sh, false, 0x0);
            addChild(new Bitmap(bmd_));
            
            // ボタンの作成
            var buttonWidth:int = 50;
            var buttonColor0:PushButton = new PushButton(this, 0,               sh - 40, "monochome", buttonColor0Handler);
            var buttonColor1:PushButton = new PushButton(this, buttonWidth,     sh - 40, "colorful",  buttonColor1Handler);
            var buttonScale0:PushButton = new PushButton(this, 0,               sh - 20, "* 1",       buttonScale0Handler);
            var buttonScale1:PushButton = new PushButton(this, buttonWidth,     sh - 20, "* 2",       buttonScale1Handler);
            var buttonScale2:PushButton = new PushButton(this, buttonWidth * 2, sh - 20, "* 10",      buttonScale2Handler);
            var buttonScale3:PushButton = new PushButton(this, buttonWidth * 3, sh - 20, "* 25",      buttonScale3Handler);
            var buttonScale4:PushButton = new PushButton(this, buttonWidth * 4, sh - 20, "* 50",      buttonScale4Handler);
            buttonColor0.width = buttonWidth;
            buttonColor1.width = buttonWidth;
            buttonScale0.width = buttonWidth;
            buttonScale1.width = buttonWidth;
            buttonScale2.width = buttonWidth;
            buttonScale3.width = buttonWidth;
            buttonScale4.width = buttonWidth;
            
            // 初回状態の表示
            buttonColor0Handler(null);
            buttonColor0.selected = true;
            changeButtonColor(buttonColor0);
            buttonScale2Handler(null);
            buttonScale2.selected = true;
            changeButtonScale(buttonScale2);
        }
        
        // 描画
        private function draw():void {
            scan_.update(bmd_, this);
        }
        
        // ボタンハンドラ(色)
        private function buttonColor0Handler(e:Event):void {
            if (e) changeButtonColor(PushButton(e.target));
            julia_.colors = colors0_;
            draw();
        }
        private function buttonColor1Handler(e:Event):void {
            if (e) changeButtonColor(PushButton(e.target));
            julia_.colors = colors1_;
            draw();
        }
        // ボタンハンドラ(スケール)
        private function buttonScale0Handler(e:Event):void {
            if (e) changeButtonScale(PushButton(e.target));
            scan_.reset();
            draw();
        }
        private function buttonScale1Handler(e:Event):void {
            if (e) changeButtonScale(PushButton(e.target));
            scan_.rect = new Rectangle( -0.68, -1, 2, 2);
            draw();
        }
        private function buttonScale2Handler(e:Event):void {
            if (e) changeButtonScale(PushButton(e.target));
            scan_.rect = new Rectangle( -0.43, -0.6, 0.4, 0.4);
            draw();
        }
        private function buttonScale3Handler(e:Event):void {
            if (e) changeButtonScale(PushButton(e.target));
            scan_.rect = new Rectangle( -0.37, -0.405, 0.168, 0.168);
            draw();
        }
        private function buttonScale4Handler(e:Event):void {
            if (e) changeButtonScale(PushButton(e.target));
            scan_.rect = new Rectangle( -0.35, -0.475, 0.08, 0.08);
            draw();
        }

        // カレントボタンの変更
        private function changeButtonColor(currentButton:PushButton):void {
            if (prevButtonColor_) prevButtonColor_.enabled = true;
            currentButton.enabled = false;
            prevButtonColor_ = currentButton;
        }
        private function changeButtonScale(currentButton:PushButton):void {
            if (prevButtonScale_) prevButtonScale_.enabled = true;
            currentButton.enabled = false;
            prevButtonScale_ = currentButton;
        }
    }
}


//package {
    //import aquioux.math.Complex;
    import flash.geom.Rectangle;
    /**
     * Glynn Fractal 描画クラス
     * _scale = 1.0 のとき (-2, -2) ~ (2, 2) の領域を対象に計算する
     * @author Aquioux(Yoshida, Akio)
     */
    /*public*/ class Julia implements ICalculator {
        /**
         * 描画範囲
         */
        public function get rect():Rectangle { return RECTANGLE; }
        private const RECTANGLE:Rectangle = new Rectangle(MIN_X, MIN_Y, (MAX_X - MIN_X), (MAX_Y - MIN_Y));
        // 個別の開始座標、終了座標
        private const MIN_X:Number = -2.0;        // X軸最小値
        private const MIN_Y:Number = -2.0;        // Y軸最小値
        private const MAX_X:Number =  2.0;        // X軸最大値
        private const MAX_Y:Number =  2.0;        // Y軸最大値

        /**
         * 集合に該当する部分の色(一般的には色なし=黒)
         */
        private var _color:uint = 0x000000;
        public function set color(value:uint):void { _color = value; }
        /**
         * 発散部分の色階調
         */
        private var _colors:Vector.<uint>;
        public function set colors(value:Vector.<uint>):void {
            _colors = value;
            degree_ = value.length;
        }
        
        /**
         * 漸化式の c
         */
        public function get c():Complex { return _c; }
        private var _c:Complex = new Complex( -0.2, 0);
        
        
        // 発散チェックループ回数(_colors.length の値)
        private var degree_:int;
        
        
        /**
         * Scan クラスからの走査データを受け、計算をおこなう
         * @param    x    X座標値
         * @param    y    Y座標値
         * @return    計算結果
         */
        public function calculate(x:Number, y:Number):uint {
            var r:int = formula(x, y, _c.real, _c.imag);
            return (r >= 0) ? _colors[r] : _color;
        }
        /**
         * 漸化式 z ← z^1.5 + c
         * @param    zRl    複素数 z の実数部
         * @param    zIm    複素数 z の虚数部
         * @param    cRl    複素数 c の実数部
         * @param    cIm    複素数 c の虚数部
         * @return    発散評価値
         * @private
         */
        private function formula(zRl:Number, zIm:Number, cRl:Number, cIm:Number):int {
            // 漸化式の計算要素の複素数
            var zRlSqr:Number;        // 実数部の2乗
            var zImSqr:Number;        // 虚数部の2乗
            var zSqr:Number;        // zRlSqr + zImSqr
            // ド・モアブルの定理
            var r1:Number;    // 局座標形式複素数の距離(計算前)
            var t1:Number;    // 局座標形式複素数の偏角(計算前)
            var r2:Number;    // 局座標形式複素数の距離(計算後)
            var t2:Number;    // 局座標形式複素数の偏角(計算後)
            
            var i:int = degree_;
            while (i--) {
                // 発散の評価
                zRlSqr = zRl * zRl;
                zImSqr = zIm * zIm;
                zSqr   = zRlSqr + zImSqr;
                if (zSqr > 4) break;
                
                // 発散していなかった場合、再評価へ向けて z_n の値を更新する
                // 複素数を局座標化
                r1 = Math.sqrt(zRlSqr + zImSqr);
                t1 = Math.atan2(zIm, zRl);
                // 冪乗計算
                r2 = Math.sqrt(r1 * r1 * r1);    // 距離
                t2 = t1 * 1.5;                    // 偏角
                zRl = Math.cos(t2) * r2 + cRl;
                zIm = Math.sin(t2) * r2 + cIm;
            }
            return i;
            // break で脱しなかった(発散しなかった)場合、while を回りきるので -1 になる
        }
    }
//}


//package {
    import flash.geom.Rectangle;
    /**
     * 計算クラスの interface
     * @author YOSHIDA, Akio (Aquioux)
     */
    /*public*/ interface ICalculator {
        function get rect():Rectangle;
        function calculate(x:Number, y:Number):uint;
    }
//}


//package {
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.geom.ColorTransform;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    /**
     * 二次元走査クラス
     * @author Aquioux(Yoshida, Akio)
     */
    /*public*/ class Scan {
        /**
         * 描画領域
         */
        private var _rect:Rectangle;
        public function set rect(value:Rectangle):void {
            _rect = value;
            
            // 複素平面の中心座標を計算
            centerX_ = _rect.x + _rect.width  * 0.5;
            centerY_ = _rect.y + _rect.height * 0.5;
            
            calcStart();    // 走査開始座標
            calcStep();        // 走査加算値
        }
        
        /**
         * スケール値
         */
        private var _scale:Number = 1.0;
        public function set scale(value:Number):void {
            var prevScale:Number = _scale;    // 現在の _scale を退避
            _scale = value;                    // 新たな _scale を代入

            // _scale 変更に伴う _rect の更新
            var rate:Number = prevScale / _scale;
            _rect.width  *= rate;
            _rect.height *= rate;
            _rect.x = centerX_ - _rect.width  * 0.5;
            _rect.y = centerY_ - _rect.height * 0.5;

            calcStart();    // 走査開始座標
            calcStep();        // 走査加算値
        }
        
        
        // 計算クラス
        private var _calculator:ICalculator;
        
        // 表示領域サイズ
        private var displayWidth_:int;        // 幅
        private var displayHeight_:int;        // 高
        

        // 複素平面の中心座標
        private var centerX_:Number;        // 実数座標
        private var centerY_:Number;        // 虚数座標
        // 平面走査の計算加算値
        private var stepX_:Number;            // 実数軸
        private var stepY_:Number;            // 虚数軸
        // 走査開始座標
        private var startX_:Number;            // 実数座標
        private var startY_:Number;            // 虚数座標
        
        // Viewer
        private var canvas_:BitmapData;        // 表示 BitmapData
        private var rect_:Rectangle;        // canvas_ 用 Rectangle
        private var FADE:ColorTransform = new ColorTransform(0.95, 0.95, 0.95);

        // 現在描画中の行
        private var currentY_:int;

        // Viewer へ渡すデータ
        private var data_:Vector.<uint>;
        private var idx_:int;                // data_ のインデックス
        
        
        /**
         * コンストラクタ
         * @param    width    表示幅
         * @param    height    表示高
         * @param    calculator    計算クラス
         */
        public function Scan(width:int, height:int, calculator:ICalculator) {
            // 表示領域
            displayWidth_  = width;
            displayHeight_ = height;
            
            // 計算クラス
            _calculator = calculator;
            reset();
        
            // data_ の生成
            data_ = new Vector.<uint>(width, true);
            // rect_ の生成
            rect_ = new Rectangle(0, 0, width, 1);
        }

        /**
         * 複素数平面を走査し、その値を計算クラスに渡す
         * @return    計算クラスから返ってきた値を格納した Vector
         */
        public function update(canvas:BitmapData, parent:Sprite):void {
            canvas_ = canvas;
            canvas_.colorTransform(canvas_.rect, FADE);
            currentY_ = 0;
            parent.addEventListener(Event.ENTER_FRAME, draw);
        }
        private function draw(e:Event):void {
            var posY:Number = startY_ + currentY_ * stepY_;
            idx_ = 0;
            for (var x:int = 0; x < displayWidth_; x++) {
                data_[idx_++] = _calculator.calculate(startX_ + x * stepX_, posY);
            }
            rect_.y = currentY_;
            canvas_.lock();
            canvas_.setVector(rect_, data_);
            currentY_++;
            canvas_.unlock();
            if (currentY_ >= displayHeight_) e.target.removeEventListener(Event.ENTER_FRAME, arguments.callee);
        }
        
        /**
         * 描画領域のリセット
         */
        public function reset():void {
            rect = _calculator.rect;
            scale = 1;
        }


        // 複素平面の走査開始座標を計算する
        private function calcStart():void {
            startX_ = _rect.x;
            startY_ = _rect.y;
        }
        // 複素平面の走査加算値を計算する
        private function calcStep():void {
            stepX_ = _rect.width  / (displayWidth_  - 1);
            stepY_ = _rect.height / (displayHeight_ - 1);
        }
    }
//}


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

        // コンストラクタ
        public function Complex(rl:Number = 0, im:Number = 0) {
            _rl = rl;
            _im = im;
        }
        
        // 複製
        public function clone():Complex {
            return new Complex(_rl, _im);
        }
        
        public function toString():String {
            return _rl + " + " + _im + "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);
        }
    }
//}