Gray-Scott モデルの反応拡散方程式 4
import aquioux.display.bitmapDataEffector.PseudoColor;
Gray-Scott モデルの反応拡散方程式 4
マウスインタラクティブ
@see http://aquioux.net/blog/?p=3793
@author YOSHIDA, Akio
/**
* 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;
}
}
//}