Gray-Scott モデルの反応拡散方程式 3
/**
* Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/gYQq
*/
// forked from Aquioux's Gray-Scott モデルの反応拡散方程式
package {
import com.bit101.components.HUISlider;
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 モデルの反応拡散方程式 3
* @author YOSHIDA, Akio
* @see http://aquioux.net/blog/?p=3772
*/
public class Main extends Sprite {
// ビューアサイズ
static private const IMAGE_WIDTH:int = 280;
static private const IMAGE_HEIGHT:int = 280;
// ビューアのピクセル数
static private const NUM_OF_PIXELS:int = IMAGE_WIDTH * IMAGE_HEIGHT;
static private const NUM_OF_PIXELS_HALF:int = NUM_OF_PIXELS / 2 >> 0;
// ビューアサイズからそれぞれ -1(ビューア.getVector で得られたリストを計算するときのため)
static private const IMAGE_WIDTH_FOR_LIST:int = IMAGE_WIDTH - 1;
static private const IMAGE_HEIGHT_FOR_LIST:int = IMAGE_HEIGHT - 1;
// 反応速度(値を大きくすると速くなる)
static private const SPEED:int = 7;
// 反応パラメータ
private var f1_:Number;
private var f2_:Number;
private var k1_:Number;
private var k2_:Number;
// 拡散パラメータ
private var dv1_:Number;
private var dv2_:Number;
private var du1_:Number;
private var du2_:Number;
// 濃度リスト
private var vList_:Vector.<Number>; // 原材料
private var uList_: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 patternLabel_:Label;
// マウスダウン判定
private var isMouseDown_:Boolean;
// パラメータ変更用スライダー
private var f1Slider_:HUISlider; // 反応パラメータ f1 用
private var f2Slider_:HUISlider; // 反応パラメータ f2 用
private var k1Slider_:HUISlider; // 反応パラメータ k1 用
private var k2Slider_:HUISlider; // 反応パラメータ k2 用
private var dv1Slider_:HUISlider; // 拡散パラメータ Dv1 用
private var dv2Slider_:HUISlider; // 拡散パラメータ Dv2 用
private var du1Slider_:HUISlider; // 拡散パラメータ Du1 用
private var du2Slider_:HUISlider; // 拡散パラメータ Du2 用
// コンストラクタ
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;
// スライダー生成(領域上部用)
var shiftX:int = 5;
f1Slider_ = new HUISlider(this, shiftX, 0, "F ", f1SliderHandler);
k1Slider_ = new HUISlider(this, shiftX, 20, "K ", k1SliderHandler);
dv1Slider_ = new HUISlider(this, shiftX, 40, "Dv", dv1SliderHandler);
du1Slider_ = new HUISlider(this, shiftX, 60, "Du", du1SliderHandler);
f1Slider_.width = stageWidth - shiftX * 2;
k1Slider_.width = stageWidth - shiftX * 2;
dv1Slider_.width = stageWidth - shiftX * 2;
du1Slider_.width = stageWidth - shiftX * 2;
var precision:int = 5;
var tick:Number = 1 / Math.pow(10, precision);
f1Slider_.labelPrecision = precision;
k1Slider_.labelPrecision = precision;
dv1Slider_.labelPrecision = precision;
du1Slider_.labelPrecision = precision;
f1Slider_.tick = tick;
k1Slider_.tick = tick;
dv1Slider_.tick = tick;
du1Slider_.tick = tick;
// ビューア生成
viewer_ = new Sprite();
addChild(viewer_);
viewer_.addChild(new Bitmap(viewBmd_));
viewer_.x = (stageWidth - IMAGE_WIDTH) - 20;
viewer_.y = 90;
// パターン名ラベル生成
patternLabel_ = new Label(this, shiftX, 225, "");
// スライダー生成(領域下部用)
f2Slider_ = new HUISlider(this, shiftX, 380, "F ", f2SliderHandler);
k2Slider_ = new HUISlider(this, shiftX, 400, "K ", k2SliderHandler);
dv2Slider_ = new HUISlider(this, shiftX, 420, "Dv", dv2SliderHandler);
du2Slider_ = new HUISlider(this, shiftX, 440, "Du", du2SliderHandler);
f2Slider_.width = stageWidth - shiftX * 2;
k2Slider_.width = stageWidth - shiftX * 2;
dv2Slider_.width = stageWidth - shiftX * 2;
du2Slider_.width = stageWidth - shiftX * 2;
f2Slider_.labelPrecision = precision;
k2Slider_.labelPrecision = precision;
dv2Slider_.labelPrecision = precision;
du2Slider_.labelPrecision = precision;
f2Slider_.tick = tick;
k2Slider_.tick = tick;
dv2Slider_.tick = tick;
du2Slider_.tick = tick;
// パラメータ
Parameters.setup();
// 反応拡散パラメータ値をスライダーとパラメータに設定
setRange(Parameters.range);
changeParameters(Parameters.current());
// 各リスト生成
viewList_ = new Vector.<uint>(NUM_OF_PIXELS, true);
vList_ = new Vector.<Number>(NUM_OF_PIXELS, true);
uList_ = new Vector.<Number>(NUM_OF_PIXELS, 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 {
switch (event.keyCode) {
case Keyboard.SPACE:
init();
break;
case Keyboard.LEFT:
changeParameters(Parameters.prev());
break;
case Keyboard.RIGHT:
changeParameters(Parameters.next());
break;
}
}
// スライダーハンドラ
private function f1SliderHandler(event:Event):void {
f1_ = f1Slider_.value;
}
private function k1SliderHandler(event:Event):void {
k1_ = k1Slider_.value;
}
private function dv1SliderHandler(event:Event):void {
dv1_ = dv1Slider_.value;
}
private function du1SliderHandler(event:Event):void {
du1_ = du1Slider_.value;
}
private function f2SliderHandler(event:Event):void {
f2_ = f2Slider_.value;
}
private function k2SliderHandler(event:Event):void {
k2_ = k2Slider_.value;
}
private function dv2SliderHandler(event:Event):void {
dv2_ = dv2Slider_.value;
}
private function du2SliderHandler(event:Event):void {
du2_ = du2Slider_.value;
}
// パラメータ変更時の処理
private function changeParameters(data:Array):void {
patternLabel_.text = data[0]; // パターン名表示
setParameters(data[1]); // 変更したパラメータのセット
}
// スライダーの最小値と最大値を設定
private function setRange(ranges:Vector.<Number>):void {
f1Slider_.minimum = ranges[0];
f1Slider_.maximum = ranges[1];
k1Slider_.minimum = ranges[2];
k1Slider_.maximum = ranges[3];
dv1Slider_.minimum = ranges[4];
dv1Slider_.maximum = ranges[5];
du1Slider_.minimum = ranges[6];
du1Slider_.maximum = ranges[7];
f2Slider_.minimum = ranges[0];
f2Slider_.maximum = ranges[1];
k2Slider_.minimum = ranges[2];
k2Slider_.maximum = ranges[3];
dv2Slider_.minimum = ranges[4];
dv2Slider_.maximum = ranges[5];
du2Slider_.minimum = ranges[6];
du2Slider_.maximum = ranges[7];
}
// パラメータのプリセット値のセット
private function setParameters(parameters:Vector.<Number>):void {
f1Slider_.value = parameters[0];
k1Slider_.value = parameters[1];
dv1Slider_.value = parameters[2];
du1Slider_.value = parameters[3];
f2Slider_.value = parameters[4];
k2Slider_.value = parameters[5];
dv2Slider_.value = parameters[6];
du2Slider_.value = parameters[7];
f1SliderHandler(null);
k1SliderHandler(null);
dv1SliderHandler(null);
du1SliderHandler(null);
f2SliderHandler(null);
k2SliderHandler(null);
dv2SliderHandler(null);
du2SliderHandler(null);
}
// 原材料および中間生成物の濃度を初期化
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.001 ? 0.5 + 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 = 0.5 + 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 f:Number = f2_;
var k:Number = k2_;
var dv:Number = dv2_;
var du:Number = du2_;
if (i < NUM_OF_PIXELS_HALF) {
f = f1_;
k = k1_;
dv = dv1_;
du = du1_;
}
// 拡散項の計算
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 reaction2V:Number = f * (1 - currentV); // 原材料 V の外部からの供給
var reaction2U:Number = (f + k) * 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();
}
}
}
//package {
/**
* Gray-Scott モデルの反応拡散方程式のパラメータ・データ
* @author YOSHIDA, Akio
*
* 【方程式】
* ut = duΔu + u^2v - (F+k)u
* vt = dvΔv - u^2v + F(1-v)
* du : U の拡散率
* dv : V の拡散率
* du, dv を変えると、描画パターンの大きさが変化する
* F : 原材料 V の外部からの供給率&中間生成物 U の外部への流出率
* k : 中間生成物 U の最終生成物 P への転換率(U の除去率)
* F, k を変えると、描画パターンの形状が変化する
*/
/*public*/ class Parameters {
// 各パラメータの最小値と最大値
static private var range_:Vector.<Number>;
// 各パラメータのプリセット値
static private var preset_:Vector.<Array>;
// プリセット値リストのカレントインデックス
static private var idx_:int = 0;
/**
* セットアップ
*/
static public function setup():void {
// 各パラメータの最小値と最大値
range_ = Vector.<Number>([
0.01000, 0.07000, // F の最小値, 最大値,
0.01000, 0.07000, // k の最小値, 最大値,
0.01000, 0.20000, // Dv の最小値, 最大値,
0.01000, 0.20000 // Du の最小値, 最大値
]);
// 各パラメータのプリセット値(F1, k1, Dv1, Du1, F2, k2, Dv2, Du2 の順)
preset_ = new Vector.<Array>();
preset_.push([
"F & k different above and below\nSHAPE of the pattern changes",
Vector.<Number>([ // パターンの形が変わる
0.02300, 0.05620, 0.10000, 0.05000,
0.02500, 0.05424, 0.10000, 0.05000
])
]);
preset_.push([
"Dv & Du is different above and below\nSIZE of the pattern changes",
Vector.<Number>([ // パターンの大きさが変わる
0.02500, 0.05424, 0.05000, 0.02500,
0.02500, 0.05424, 0.10000, 0.05000
])
]);
preset_.push([
"same parameters above and below",
Vector.<Number>([
0.02500, 0.05424, 0.10000, 0.05000,
0.02500, 0.05424, 0.10000, 0.05000
])
]);
// プリセット値リストのカレントインデックス
idx_ = 0;
}
/**
* 範囲を返す
*/
static public function get range():Vector.<Number> {
return range_;
}
/**
* 現在のプリセット値を返す
*/
static public function current():Array {
return preset_[idx_];
}
/**
* 前のプリセット値を返す
*/
static public function prev():Array {
idx_--;
if (idx_ < 0) idx_ = preset_.length - 1;
return current();
}
/**
* 次のプリセット値を返す
*/
static public function next():Array {
idx_++;
idx_ %= preset_.length;
return current();
}
}
//}