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 モデルの反応拡散方程式 4

import aquioux.display.bitmapDataEffector.PseudoColor;

Gray-Scott モデルの反応拡散方程式 4
マウスインタラクティブ
@see http://aquioux.net/blog/?p=3793
@author YOSHIDA, Akio
Get Adobe Flash player
by Aquioux 19 Aug 2013
/**
 * Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/kxq4
 */

package {
    //import aquioux.display.bitmapDataEffector.PseudoColor;
    import com.bit101.components.Label;
    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.events.KeyboardEvent;
    import flash.events.MouseEvent;
    import flash.geom.Point;
    import flash.geom.Rectangle;
    import flash.ui.Keyboard;
    [SWF(width = "465", height = "465", frameRate = "120", backgroundColor = "#ffffff")]
    /**
     * Gray-Scott モデルの反応拡散方程式 4
     * マウスインタラクティブ
     * @see http://aquioux.net/blog/?p=3793
     * @author YOSHIDA, Akio
     */
    public class Main extends Sprite {
        // ビューアサイズ
        static private const IMAGE_WIDTH:int  = 320;
        static private const IMAGE_HEIGHT:int = 320;
        // ビューアのピクセル数
        static private const NUM_OF_PIXELS:int = IMAGE_WIDTH * IMAGE_HEIGHT;
        // ビューアサイズからそれぞれ -1(getVector で得られたリストを計算するときのため)
        static private const IMAGE_WIDTH_FOR_LIST:int  = IMAGE_WIDTH  - 1;
        static private const IMAGE_HEIGHT_FOR_LIST:int = IMAGE_HEIGHT - 1;

        // 255 での除算を乗算の形にするためのもの
        static private const DIVIDE255:Number = 0.003921569;

        // 拡散パラメータ
        static private const DV:Number = 0.13;
        static private const DU:Number = DV * 0.5;
        // 反応パラメータ
        static private const F0:Number = 0.0185;    // 粒状
        static private const F1:Number = 0.0250;    // 紐状
        private var fList_:Vector.<Number>;
            // fList_[0]:線、fList_[1]:地
        static private const K:Number = 0.05424;

        // 濃度リスト
        private var vList_:Vector.<Number>;            // 原材料
        private var uList_:Vector.<Number>;            // 中間生成物

        // ビューア
        private var viewer_:Sprite;
        private var viewBmd_:BitmapData;
        private var viewBmdList_:Vector.<uint>;
        // 擬似カラーフィルター
        private var pseudoColor_:PseudoColor;

        // フォースマップ
        private var mapCanvas_:Shape;                // マウスの軌跡を描く場所
        private var mapCanvasGraphics_:Graphics;
        private var mapMomentBmd_:BitmapData;        // 瞬間 BitmapData(1回の MOUSE_MOVE の描画)
        private var mapAppendBmd_:BitmapData;        // 累積 BitmapData(mapMomentBmd_ の累積)
        private var mapMomentList_:Vector.<uint>;
        private var mapAppendList_:Vector.<uint>;
        static private const MAP_FILL_COLOR:uint = 0x000000;
        static private const MAP_DRAW_COLOR:uint = 0xffffff;

        // ビューア、フォースマップ兼用
        private var rect_:Rectangle;

        // マウスダウン判定
        private var isMouseDown_:Boolean;
        // 前回のマウスカーソル位置座標
        private var prevMouseX_:Number = 0.0;
        private var prevMouseY_:Number = 0.0;


        // コンストラクタ
        public function Main() {
            setup();
            addEventListener(Event.ENTER_FRAME, update);
            stage.addEventListener(MouseEvent.MOUSE_DOWN,  function():void { isMouseDown_ = true; } );
            stage.addEventListener(MouseEvent.MOUSE_UP,    function():void { isMouseDown_ = false; } );
            stage.addEventListener(MouseEvent.MOUSE_MOVE,  mouseMoveHandler);
            stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
        }


        // セットアップ
        private function setup():void {
            // ビューア生成
            viewer_ = new Sprite();
            addChild(viewer_);
            viewBmd_ = new BitmapData(IMAGE_WIDTH, IMAGE_HEIGHT, false, MAP_FILL_COLOR);
            viewer_.addChild(new Bitmap(viewBmd_));
            viewer_.x = (stage.stageWidth  - IMAGE_WIDTH)  >> 1;
            viewer_.y = (stage.stageHeight - IMAGE_HEIGHT) >> 1;
            // 擬似カラーフィルター
            pseudoColor_ = new PseudoColor();

            // フォースマップ生成
            mapMomentBmd_ = viewBmd_.clone();
            mapAppendBmd_ = viewBmd_.clone();
            mapCanvas_ = new Shape();
            mapCanvasGraphics_ = mapCanvas_.graphics;
            initCanvas();

            // Usage 生成
            var usage:Label = new Label(this, viewer_.x, viewer_.y + viewer_.height, "Please drag the COLORED AREA.\nKeyboard.UP : Erase the LINE YOU DRAG.\nKeyboard.DOWN : Erase ALL.");
            
            // 各リスト生成
            rect_ = viewBmd_.rect;
            viewBmdList_   = viewBmd_.getVector(rect_);
            mapMomentList_ = mapMomentBmd_.getVector(rect_);
            mapAppendList_ = mapAppendBmd_.getVector(rect_);
            vList_ = new Vector.<Number>(NUM_OF_PIXELS, true);
            uList_ = new Vector.<Number>(NUM_OF_PIXELS, true);

            // パラメータ
            fList_ = Vector.<Number>([F0, F1]);
            fList_.fixed = true;

            // 初期化
            init();
        }
        // アップデート
        private function update(event:Event):void {
            calc();            // 濃度計算
            draw();            // 描画
        }
        // マウスイベントハンドラ
        private function mouseMoveHandler(event:MouseEvent):void {
            if (isMouseDown_) {
                // フォースマップ各要素更新
                updateCanvas(prevMouseX_, prevMouseY_, viewer_.mouseX, viewer_.mouseY);
                updateMomentBmd();
                updateAppendBmd();
                // フォースマップから中間生成物の濃度へ干渉
                interference();
                // フォースマップ各要素初期化
                initCanvas();
                initMomentBmd();
            }
            prevMouseX_ = viewer_.mouseX;
            prevMouseY_ = viewer_.mouseY;
        }
        // キーボードイベントハンドラ
        private function keyDownHandler(event:KeyboardEvent):void {
            switch (event.keyCode) {
                case Keyboard.DOWN:    // 全てを消去
                    fList_.reverse();
                    init();
                    // 以下のステップも実行するため break しない
                case Keyboard.UP:    // 描いた軌跡のみ消去
                    initCanvas();
                    initMomentBmd();
                    initAppendBmd();
                    break;
            }
        }


        // --------------------
        //   反応拡散
        // --------------------

        // 原材料および中間生成物の濃度の初期化
        private function init():void {
            for (var i:int = 0; i < NUM_OF_PIXELS; i++) {
                vList_[i] = 1.0;
                uList_[i] = 0.0;
            }
        }
        // 原材料および中間生成物の濃度の更新(反応拡散計算)
        public function calc():void {
            for (var i:int = 0; i < NUM_OF_PIXELS; i++) {
                // カレントの濃度値
                var currentV:Number = vList_[i];
                var currentU:Number = uList_[i];

                // 拡散項の計算
                var posX:int  = i % IMAGE_WIDTH;
                var posY:int  = i / IMAGE_WIDTH >> 0;
                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 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;
                // 反応項の計算(2)
                var idx:int = 1 - (mapAppendList_[i] & 0xff) * DIVIDE255;
                    // MAP_DRAW_COLOR:uint = 0xffffffff なので、マウスドラッグ部分の mapAppendList_[i] & 0xff は 0xff、地の部分は 0x00 になる
                    // よって、マウスドラッグ部分の idx は 0 となり、地の部分の idx は 1 になる
                var f:Number = fList_[idx];
                var reaction2V:Number = f * (1 - currentV);
                var reaction2U:Number = (f + K) * currentU;

                // 反応拡散の計算
                currentV += (diffusionV - reaction1 + reaction2V);
                currentU += (diffusionU + 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;

                // 描画用リストに原材料濃度の値を格納
                var gray:uint = currentV * 255 >> 0;
                viewBmdList_[i] = gray << 16 | gray << 8 | gray;
            }
        }
        // 反応拡散の描画
        private function draw():void {
            // BitmapData に反映
            viewBmd_.lock();
            viewBmd_.setVector(rect_, viewBmdList_);
            viewBmd_.unlock();
            // 擬似カラー着色
            pseudoColor_.applyEffect(viewBmd_);
            pseudoColor_.shift += 0.05;
        }
        // 中間生成物の濃度への干渉
        private function interference():void {
            for (var i:int = 0; i < NUM_OF_PIXELS; i++) {
                uList_[i] += (mapMomentList_[i] & 0xff) * DIVIDE255;
            }
        }


        // --------------------
        //   フォースマップ
        // --------------------

        // キャンバス部の初期化
        private function initCanvas():void {
            mapCanvasGraphics_.clear();
            mapCanvasGraphics_.lineStyle(30, MAP_DRAW_COLOR);
        }
        // 瞬間 BitmapData の初期化
        private function initMomentBmd():void {
            mapMomentBmd_.lock();
            mapMomentBmd_.fillRect(rect_, MAP_FILL_COLOR);
            mapMomentList_ = mapMomentBmd_.getVector(rect_);
            mapMomentBmd_.unlock();
        }
        // 累積 BitmapData の初期化
        private function initAppendBmd():void {
            mapAppendBmd_.lock();
            mapAppendBmd_.fillRect(rect_, MAP_FILL_COLOR);
            mapAppendList_ = mapAppendBmd_.getVector(rect_);
            mapAppendBmd_.unlock();
        }
        // キャンバス部の更新
        private function updateCanvas(startX:Number, startY:Number, endX:Number, endY:Number):void {
            mapCanvasGraphics_.moveTo(startX, startY);
            mapCanvasGraphics_.lineTo(endX,   endY);
        }
        // 瞬間 BitmapData の更新
        private function updateMomentBmd():void {
            mapMomentBmd_.lock();
            mapMomentBmd_.draw(mapCanvas_);
            mapMomentList_ = mapMomentBmd_.getVector(rect_);
            mapMomentBmd_.unlock();
        }
        // 累積 BitmapData の更新
        private function updateAppendBmd():void {
            mapAppendBmd_.lock();
            mapAppendBmd_.draw(mapMomentBmd_, null, null, BlendMode.ADD);
            mapAppendList_ = mapAppendBmd_.getVector(rect_);
            mapAppendBmd_.unlock();
        }
    }
}


//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();
        }
        public function get shift():Number { return _shift; }
        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 = [];

        // グレイスケールエフェクタ
        private var grayscale_:GrayScale = new GrayScale();

        
        /*
         * コンストラクタ
         */
        public function PseudoColor() {
            createMap();
        }
        
        /*
         * 効果適用
         * @param    value    効果対象 BitmapData
         */
        public function applyEffect(value:BitmapData):void {
            grayscale_.applyEffect(value);
            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.bitmapDataEffector {
    import flash.display.BitmapData;
    import flash.filters.ColorMatrixFilter;
    /**
     * ColorMatrixFilter による BitmapData のグレイスケール化(NTSC 系加重平均による)
     * 参考:Foundation ActionScript 3.0 Image Effects(P106)
     *         http://www.amazon.co.jp/gp/product/1430218711?ie=UTF8&tag=laxcomplex-22
     * @author YOSHIDA, Akio
     */
    /*public*/ class GrayScale implements IEffector {
        // RGB 比率
        private const R:Number = EffectorUtils.NTSC_R;
        private const G:Number = EffectorUtils.NTSC_G;
        private const B:Number = EffectorUtils.NTSC_B;

        // ColorMatrixFilter 用マトリックス
        private const MATRIX:Array = [
            R, G, B, 0, 0,
            R, G, B, 0, 0,
            R, G, B, 0, 0,
            0, 0, 0, 1, 0
        ];

        // ColorMatrixFilter
        private const FILTER:ColorMatrixFilter = new ColorMatrixFilter(MATRIX);


        /*
         * 効果適用
         * @param    value    効果対象 BitmapData
         */
        public function applyEffect(value:BitmapData):void {
            value.applyFilter(value, value.rect, EffectorUtils.ZERO_POINT, FILTER);
        }
    }
//}

//package aquioux.display.bitmapDataEffector {
    import flash.geom.Point;
    /**
     * bitmapDataEffector パッケージ内のクラスで共通に使う定数など
     * @author YOSHIDA, Akio
     */
    /*public*/ class EffectorUtils {
        // BitmapData#applyFilter 第3引数など
        static public const ZERO_POINT:Point = new Point(0, 0);
        
        // NTSC系加重平均法(グレイスケール、YIQ, YCbCr も同じ)
        static public const NTSC_R:Number = 0.298912;
        static public const NTSC_G:Number = 0.586611;
        static public const NTSC_B:Number = 0.114478;
    }
//}

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