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