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

Gray-Scott モデルの反応拡散方程式 2

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

// forked from Aquioux's Gray-Scott モデルの反応拡散方程式
package {
//    import aquioux.display.bitmapDataEffector.PseudoColor;
    import com.bit101.components.Label;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.geom.Rectangle;
    import flash.ui.Keyboard;
    [SWF(width="465", height="465", frameRate="60", backgroundColor="#000000")]
    /**
     * Gray-Scott モデルの反応拡散方程式 2
     * @author YOSHIDA, Akio
     * @see http://aquioux.net/blog/?p=3744
     */
    public class Main extends Sprite {
        // ビューアサイズ
        private const IMAGE_WIDTH:int  = 400;
        private const IMAGE_HEIGHT:int = 400;
        // ビューアのピクセル数
        private const NUM_OF_PIXELS:int = IMAGE_WIDTH * IMAGE_HEIGHT;

        // ビューアサイズからそれぞれ -1(ビューア.getVector で得られたリストを計算するときのため)
        private const IMAGE_WIDTH_FOR_LIST:int  = IMAGE_WIDTH  - 1;
        private const IMAGE_HEIGHT_FOR_LIST:int = IMAGE_HEIGHT - 1;

        // 反応速度(値を大きくすると速くなる)
        private const SPEED:int = 7;

        // 拡散パラメータ
        private var DV:Number = 0.1;
        private var DU:Number = 0.05;
        // 反応パラメータ範囲
        private const MIN_F:Number = 0.01;
        private const MAX_F:Number = 0.08;
        private const MIN_K:Number = 0.03;
        private const MAX_K:Number = 0.08;

        // 濃度リスト
        private var vList_:Vector.<Number>;    // 原材料
        private var uList_:Vector.<Number>;    // 中間生成物
        
        // 反応パラメータリスト
        private var fList_:Vector.<Number>;
        private var kList_:Vector.<Number>;

        // ビューア
        private var viewer_:Sprite;
        private var viewBmd_:BitmapData = new BitmapData(IMAGE_WIDTH, IMAGE_HEIGHT, false, 0xffffff);
        private var viewList_:Vector.<uint>;
        private const RECT:Rectangle = new Rectangle(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
        
        // 疑似カラーフィルター
        private var psudoColor_:PseudoColor;

        // マウスダウン判定
        private var isMouseDown_:Boolean;


        // コンストラクタ
        public function Main() {
            setup();
            addEventListener(Event.ENTER_FRAME, update);
            viewer_.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
            viewer_.addEventListener(MouseEvent.MOUSE_UP,   mouseUpHandler);
            viewer_.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
        }

        // セットアップ
        private function setup():void {
            // ステージサイズ
            var stageWidth:int = stage.stageWidth;
       
            // ビューア生成
            viewer_ = new Sprite();
            addChild(viewer_);
            viewer_.addChild(new Bitmap(viewBmd_));
            viewer_.x = stageWidth - IMAGE_WIDTH;
            viewer_.y = 0;
            
            // 変数 F に関するラベル
            // 縦軸が F
            var fNameLabel:Label = new Label(this, viewer_.x - 15, IMAGE_HEIGHT / 2, "F");
            var fMaxLabel:Label  = new Label(this, viewer_.x - 30, 0, String(MAX_F));
            var fMinLabel:Label  = new Label(this, viewer_.x - 30, IMAGE_HEIGHT - 16, String(MIN_F));
            
            // 変数 k に関するラベル
            // 横軸が k
            var kNameLabel:Label = new Label(this, viewer_.x + IMAGE_WIDTH / 2, IMAGE_HEIGHT, "k");
            var kMinLabel:Label  = new Label(this, viewer_.x, IMAGE_HEIGHT, String(MIN_K));
            var kMaxLabel:Label  = new Label(this, stage.stageWidth - 25, IMAGE_HEIGHT, String(MAX_K));
            
            // Usage 生成
            var usage:Label = new Label(this, viewer_.x, IMAGE_HEIGHT + 20, "[USAGE]\nSPACE Key : init\nMouse DRAG : change, concentration of reactant on Mouse cursor");
            
            // 疑似カラーフィルター
            psudoColor_ = new PseudoColor();
            
            // 各リスト生成
            viewList_ = new Vector.<uint>(NUM_OF_PIXELS, true);
            vList_    = new Vector.<Number>(NUM_OF_PIXELS, true);
            uList_    = new Vector.<Number>(NUM_OF_PIXELS, true);

            // 縦軸が F
            fList_ = new Vector.<Number>();
            var stepF:Number = (MAX_F - MIN_F) / IMAGE_HEIGHT_FOR_LIST;
            for (var y:int = 0; y < IMAGE_HEIGHT; y++) {
                for (var x:int = 0; x < IMAGE_WIDTH; x++) {
                    fList_.push(MAX_F - stepF * y);
                }
            }
            fList_.fixed = true;
            // 横軸が k
            kList_ = new Vector.<Number>();
            var stepK:Number = (MAX_K - MIN_K) / IMAGE_WIDTH_FOR_LIST;
            for (y = 0; y < IMAGE_HEIGHT; y++) {
                for (x = 0; x < IMAGE_WIDTH; x++) {
                    kList_.push(MIN_K + stepK * x);
                }
            }
            kList_.fixed = true;
            
            // 濃度初期化
            init();
        }

        // アップデート
        private function update(event:Event):void {
            var c:int = SPEED;
            while (--c) calc();
            draw();
        }

        // マウスイベントハンドラ
        private function mouseDownHandler(event:MouseEvent):void {
            isMouseDown_ = true;
        }
        private function mouseUpHandler(event:MouseEvent):void {
            isMouseDown_ = false;
        }
        private function mouseMoveHandler(event:MouseEvent):void {
            interference(event.localX >> 0, event.localY >> 0, isMouseDown_);
        }

        // キーボードイベントハンドラ
        private function keyDownHandler(event:KeyboardEvent):void {
            if (event.keyCode == Keyboard.SPACE) init();
        }


        // 原材料および中間生成物の濃度を初期化
        private function init():void {
            var len:int = NUM_OF_PIXELS;
            for (var i:int = 0; i < len; i++) {
                // 最初の原材料濃度はすべて 1.0 にする
                vList_[i] = 1.0;
                uList_[i] = Math.random() < 0.05 ? Math.random() * 0.5 : 0.0;
                // 中間生成物濃度が 0.0 のときは何も表示されず、そうでない場合は黒い点となる
                // 原材料濃度を視覚化するということは、原材料濃度が 1.0 に近づくほど白くなり、0.0 に近づくほど黒くなる
                // 中間生成物が存在するということは、原材料が消費されるということなので、0.0 に近づくことになり、そのため黒くなる
            }
        }

        // 原材料と中間生成物の濃度への干渉
        private function interference(posX:int, posY:int, flg:Boolean):void {
            // 当該ピクセルと近傍4ピクセルのリスト上の位置を計算
            var current:int = posY * IMAGE_WIDTH + posX;
            var west:int    = posX == 0 ? current : current - 1;                                // 左
            var east:int    = posX == IMAGE_WIDTH_FOR_LIST  ? current : current + 1;            // 右
            var north:int   = posY == 0 ? current : current - IMAGE_WIDTH;                        // 上
            var south:int   = posY == IMAGE_HEIGHT_FOR_LIST ? current : current + IMAGE_WIDTH;    // 下
            // 当該ピクセルと近傍4ピクセルにのみ init() と同じ処理
            if (flg) {
                var vVal:Number = 1.0
                vList_[current] = vVal;
                vList_[west]    = vVal;
                vList_[east]    = vVal;
                vList_[north]   = vVal;
                vList_[south]   = vVal;
                var uVal:Number = Math.random() * 0.5;
                uList_[current] = uVal;
                uList_[west]    = uVal;
                uList_[east]    = uVal;
                uList_[north]   = uVal;
                uList_[south]   = uVal;
            }
        }

        // 原材料および中間生成物の濃度の更新(反応拡散計算)
        public function calc():void {
            var len:int = NUM_OF_PIXELS;
            for (var i:int = 0; i < len; i++) {
                // カレントピクセルの座標
                var posX:int  = i % IMAGE_WIDTH;
                var posY:int  = i / IMAGE_WIDTH >> 0;
                // 近傍4ピクセルのリスト上の位置を計算
                var west:int  = posX == 0 ? i : i - 1;                                    // 左
                var east:int  = posX == IMAGE_WIDTH_FOR_LIST  ? i : i + 1;                // 右
                var north:int = posY == 0 ? i : i - IMAGE_WIDTH;                        // 上
                var south:int = posY == IMAGE_HEIGHT_FOR_LIST ? i : i + IMAGE_WIDTH;    // 下

                // カレントの濃度値
                var currentV:Number = vList_[i];
                var currentU:Number = uList_[i];

                // 拡散項の計算
                var diffusionV:Number = DV * (vList_[west] + vList_[east] + vList_[north] + vList_[south] - 4 * currentV);
                var diffusionU:Number = DU * (uList_[west] + uList_[east] + uList_[north] + uList_[south] - 4 * currentU);

                // 反応項の計算(1)
                var reaction1:Number = currentV * currentU * currentU;    // 2U + V -> 3U
                // 反応項の計算(2)
                var f:Number = fList_[i];
                var reaction2V:Number = f * (1 - currentV);            // 原材料 V の外部からの供給
                var reaction2U:Number = (f + kList_[i]) * currentU;    // U -> P (U の除去)

                // 反応拡散の計算
                currentV += (diffusionV - reaction1 + reaction2V);    // reaction1 は除去、reaction2V は供給:原材料
                currentU += (diffusionU + reaction1 - reaction2U);    // reaction1 は供給、reaction2U は除去:中間生成物
                if (currentV < 0.0) currentV = 0.0;
                if (currentV > 1.0) currentV = 1.0;
                if (currentU < 0.0) currentU = 0.0;
                if (currentU > 1.0) currentU = 1.0;
                vList_[i] = currentV;
                uList_[i] = currentU;
            }
        }

        // 原材料の濃度を視覚化(反応拡散の描画)
        private function draw():void {
            // 原材料の濃度を色に変換し、リストに格納
            var len:int = NUM_OF_PIXELS;
            for (var i:int = 0; i < len; i++) {
                var gray:int = vList_[i] * 255 >> 0;
                viewList_[i] = gray << 16 | gray << 8 | gray;
            }
            // BitmapData に反映
            viewBmd_.lock();
            viewBmd_.setVector(RECT, viewList_);
            viewBmd_.unlock();
            // 疑似カラー
            psudoColor_.applyEffect(viewBmd_);
        }
    }
}


//package aquioux.display.bitmapDataEffector {
    //import aquioux.display.colorUtil.ColorMath;
    import flash.display.BitmapData;
    /**
     * 疑似カラー
     * 参考:「ディジタル画像処理」 CG-ARTS協会 P97 「5-3-2 疑似カラー」
     * http://www.amazon.co.jp/gp/product/4903474011?ie=UTF8&tag=laxcomplex-22
     * 参考:疑似カラー(Pseudo-color)@画像処理ソリューション
     * http://imagingsolution.blog107.fc2.com/blog-entry-171.html
     * @author YOSHIDA, Akio
     */
    /*public*/ class PseudoColor implements IEffector {
        /**
         * 開始位置のシフト値(0~360)
         */
        public function set shift(value:Number):void {
            _shift = value;
            createMap();
        }
        private var _shift:Number = 0;

        public function set frequensy(value:Number):void {
            _freq = value;
            createMap();
        }
        private var _freq:Number = 360 / 255;
        

        // paletteMap の引数となるチャンネル用の Array
        private var mapList_:Array = [];

        
        /*
         * コンストラクタ
         */
        public function PseudoColor() {
            createMap();
        }
        
        /*
         * 効果適用
         * @param    value    効果対象 BitmapData
         */
        public function applyEffect(value:BitmapData):void {
            value.paletteMap(value, value.rect, EffectorUtils.ZERO_POINT, mapList_, [], []);
        }
        
        // paletteMap 用 Array 定義
        private function createMap():void {
            for (var i:int = 0; i < 256; i++) mapList_[i] = ColorMath.angleToHex(_freq * i + _shift);
        }
    }
//}
//package aquioux.display.bitmapDataEffector {
    import flash.display.BitmapData;
    /**
     * BitmapDataEffector 用 interface
     * @author YOSHIDA, Akio
     */
    /*public*/ interface IEffector {
        function applyEffect(value:BitmapData):void;
    }
//}


//package aquioux.display.colorUtil {
    /**
     * 色にまつわる各種計算クラス(static クラス)
     * 使用用語について
     * "HEX"   : 16進数表記RGB
     * "HEX32" : 32進数表記ARGB
     * "RGB"   : Number3型RGB
     * "RGBA"  : Number4型RGBA
     * "HSV"   : Number3型HSV
     * @author YOSHIDA, Akio (Aquioux)
     */
    /*public*/ class ColorMath {
        /**
         * コンストラクタ
         */
        public function ColorMath() {
            Error("ColorMath クラスは static クラスです。");
        }

        
        // ////////// "HEX" と "RGB" との相互変換 //////////
        /**
         * "HEX" を "RGB" に変換する
         * @param    hex    "HEX"
         * @return    "RGB"
         */
        public static function hexToRgb(hex:uint):Vector.<uint> {
            var r:uint = (hex >> 16) & 0xFF;
            var g:uint = (hex >>  8) & 0xFF;
            var b:uint =  hex        & 0xFF;
            return Vector.<uint>([r, g, b]);
        }
        /**
         * "RGB" を "HEX" に変換する
         * @param    rgb    "RGB"
         * @return    "HEX"
         */
        public static function rgbToHex(rgb:Vector.<uint>):uint {
            var r:uint = adjustRGB(rgb[0]);
            var g:uint = adjustRGB(rgb[1]);
            var b:uint = adjustRGB(rgb[2]);
            return r << 16 | g << 8 | b;
        }
        

        // ////////// "HEX32" と "RGB" との相互変換 //////////
        /**
         * "HEX32" を "RGBA" に変換する
         * @param    hex    "ARGB"
         * @return    "RGBA"
         */
        public static function hex32ToRgb(hex:uint):Vector.<uint> {
            var a:uint = (hex >> 24) & 0xFF;
            var r:uint = (hex >> 16) & 0xFF;
            var g:uint = (hex >>  8) & 0xFF;
            var b:uint =  hex        & 0xFF;
            return Vector.<uint>([r, g, b, a]);
        }
        /**
         * "RGBA" を "HEX32" に変換する
         * @param    rgba    "RGBA"
         * @return    "ARGB"
         */
        public static function rgbToHex32(rgba:Vector.<uint>):uint {
            var a:uint = adjustRGB(rgba[3]);
            var r:uint = adjustRGB(rgba[0]);
            var g:uint = adjustRGB(rgba[1]);
            var b:uint = adjustRGB(rgba[2]);
            return a << 24 | r << 16 | g << 8 | b;
        }
        

        // ////////// "RGB" と "HSV" との相互変換 //////////
        /**
         * "RGB" を "HSV" に変換する
         * @param    rgb    "RGB"
         * @return    "HSV"
         * @see    http://ja.wikipedia.org/wiki/HSV%E8%89%B2%E7%A9%BA%E9%96%93
         */
        public static function rgbToHsv(rgb:Vector.<uint>):Vector.<Number> {
            // R,G,B 正規化(各値を 0.0 ~ 1.0 の範囲にする)
            var r:Number = adjustRGB(rgb[0]);
            var g:Number = adjustRGB(rgb[1]);
            var b:Number = adjustRGB(rgb[2]);
            
            // R,G,B の最大値を最小値を求める
            var cMin:Number = Math.min(r, Math.min(g, b));
            var cMax:Number = Math.max(r, Math.max(g, b));
            var diff:Number = cMax - cMin;
            
            // H,S,V 計算
            if(diff == 0) {
                var h:Number = 0;
                var s:Number = 0;
            } else {
                if (r == cMax) {
                    h = 60 * (g - b) / diff;
                } else if (g == cMax) {
                    h = 60 * (b - r) / diff + 120;
                } else {
                    h = 60 * (r - g) / diff + 240;
                }
                s = diff / cMax;
            }
            var v:Number = cMax / 0xff;
            return Vector.<Number>([h, s, v]);
        }
        /**
         * "HSV" を "RGB" に変換する
         * @param    rgb    "HSV"
         * @return    "RGB"
         * @see    http://ja.wikipedia.org/wiki/HSV%E8%89%B2%E7%A9%BA%E9%96%93
         */
        public static function hsvToRgb(hsv:Vector.<Number>):Vector.<uint> {
            var h:Number = hsv[0];
            var s:Number = hsv[1];
            var v:Number = hsv[2];
            // H,S,V 適正化チェック
            h %= 360;                            // H : 0.0 ~ 359.0
            if (h < 0) h += 360;
            s = Math.max(0, Math.min(s, 1.0));    // s : 0.0 ~ 1.0
            v = Math.max(0, Math.min(v, 1.0));    // v : 0.0 ~ 1.0
            
            // R,G,B 計算
            var r:Number = 0;
            var g:Number = 0;
            var b:Number = 0;
            if (s == 0) {
                r = g = b = v;
            } else {
                var hi:Number = h / 60;
                var i:int = Math.floor(hi % 6);
                var f:Number = hi - i;
                var p:Number = v * (1 - s);
                var q:Number = v * (1 - s * f);
                var t:Number = v * (1 - s * (1 - f));
                switch(i) {
                    case 0: r = v; g = t; b = p; break;
                    case 1: r = q; g = v; b = p; break;
                    case 2: r = p; g = v; b = t; break;
                    case 3: r = p; g = q; b = v; break;
                    case 4: r = t; g = p; b = v; break;
                    case 5: r = v; g = p; b = q; break;
                }
            }
            return Vector.<uint>([(r * 0xFF) >> 0, (g * 0xFF) >> 0, (b * 0xFF) >> 0]);
        }
        

        // ////////// "HEX" と "HSV" との相互変換 //////////
        /**
         * "HEX" を "HSV" に変換する
         * @param    hex    "HEX"
         * @return    "HSV"
         */
        public static function hexToHsv(hex:uint):Vector.<Number> {
            return rgbToHsv(hexToRgb(hex));
        }
        /**
         * "HSV" を "HEX" に変換する
         * @param    hsv    "HSV"
         * @return    "HEX"
         */
        public static function hsvToHex(hsv:Vector.<Number>):uint {
            return rgbToHex(hsvToRgb(hsv));
        }


        private static const DEGREE120:Number = Math.PI * 2 / 3;    // 120度(弧度法形式)
        private static const TO_RADIAN:Number = Math.PI / 180;    // 度数法値を弧度法値に変換する値
        /**
         * コサインカーブによる循環RGBカラーリストから指定した角度の色を"RGB"として取得
         * @param    angle    角度(度数法による)
         * @return    "RGB"
         */
        public static function angleToRgb(angle:Number):Vector.<uint> {
            var radian:Number = angle * TO_RADIAN;
            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 Vector.<uint>([r, g, b]);
        }
        /**
         * コサインカーブによる循環RGBカラーから指定した角度の色を"HEX"として取得
         * @param    angle    角度(度数法による)
         * @return    "HEX"
         */
        public static function angleToHex(angle:Number):uint {
            return rgbToHex(angleToRgb(angle));
        }


        // ////////// public メソッドを補助する private メソッド //////////
        /**
         * R,G,B 個別数値を 0 ~ 0xFF の範囲に適正化する
         * @param    val    Number 型の数値
         * @return    適性化後の値
         * @private
         */
        private static function adjustRGB(val:Number):uint {
            if (val < 0x00) val = 0x00;
            if (val > 0xFF) val = 0xFF;
            return val >> 0;
        }
    }
//}