ブッダブロ Buddhabrot
/**
* Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/laIn
*/
package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.display.Sprite;
import flash.events.Event;
import flash.filters.BitmapFilterQuality;
import flash.filters.BlurFilter;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
[SWF(width = "465", height = "465", frameRate = "60", backgroundColor = "#000000")]
/**
* ブッダブロの描画
* @see jp: http://ja.wikipedia.org/wiki/%E3%83%96%E3%83%83%E3%83%80%E3%83%96%E3%83%AD
* @see en: http://en.wikipedia.org/wiki/Buddhabrot
* @see http://aquioux.net/blog/?p=1306
* @author Aquioux(Yoshida, Akio)
*/
public class Main extends Sprite {
// 表示用 BitmapData
private var bmd_:BitmapData;
private var bufferBmd_:BitmapData;
// bitmapData 用変数・定数
private var rect_:Rectangle;
private const ZERO_POINT:Point = new Point();
private const BLUR:BlurFilter = new BlurFilter(4, 4, BitmapFilterQuality.HIGH);
private var matrix_:Matrix = new Matrix();
private var cnt_:int = 3600; // ブッダブロ描画回数
// ブッダブロを描くための複素数 c
private var rl_:Number; // 実数部
private var im_:Number; // 虚数部
// プロット色に関する変数・定数
private var color_:uint; // プロット色
private var angle_:Number = 0.0; // プロット色を決めるための値
private const STEP:Number = 180 / cnt_; // プロット色のインクリメントステップ
private var textField_:TextField; // cnt_ を表示するテキストフィールド
// ステージサイズ
private var w:int = stage.stageWidth;
private var h:int = stage.stageHeight;
public function Main():void {
// Viewer のバッファ
bufferBmd_ = new BitmapData(w, h, true, 0x00000000);
rect_ = bufferBmd_.rect;
// Viewer の作成
bmd_ = new BitmapData(w, h, true, 0xFF000000);
addChild(new Bitmap(bmd_));
matrix_.rotate(Math.PI / 2);
matrix_.translate(w, 0);
// Buddhabrot クラスのセットアップ
Buddhabrot.setup(w, h);
Buddhabrot.scale = 1.5;
// テキストフィールドの作成
textField_ = new TextField();
textField_.autoSize = TextFieldAutoSize.LEFT;
textField_.defaultTextFormat = new TextFormat("_typewriter", 12, 0xFFFFFF);
addChild(textField_);
angle_ = Math.random() * 360 >> 0; // 開始プロット色
addEventListener(Event.ENTER_FRAME, update);
}
private function update(e:Event):void {
// プロット色の決定
color_ = CycleRGB.getColor(angle_);
color_ = 0x66 << 24 | color_;
angle_ += STEP;
// 複素数 c をランダムで選ぶ
rl_ = Math.random() * 2 - 1.5;
im_ = Math.random() * 2 - 1;
draw(rl_, im_, color_);
// プロットの過程をもう一度
rl_ = Math.random() * 2 - 1.5;
im_ = Math.random() * 2 - 1;
draw(rl_, im_, color_);
// テキストフィールドに残り回数を表示
textField_.text = String(cnt_);
// 終了判定
if (cnt_-- <= 0) {
textField_.text = "Complete!";
removeEventListener(Event.ENTER_FRAME, arguments.callee);
}
}
// 描画
private function draw(val1:Number, val2:Number, color:uint):void {
// 複素数 c を設定
Buddhabrot.rl = val1;
Buddhabrot.im = val2;
var data:Vector.<int> = Buddhabrot.update();
if (!data) return; // 発散していたら終了
// BitmapData にブッダブロを描画
var len:uint = data.length / 2;
bufferBmd_.lock();
bufferBmd_.fillRect(rect_, 0x00000000);
for (var i:int = 0; i < len; i++) {
var idx1:uint = i * 2;
var idx2:uint = idx1 + 1;
var x:int = data[idx1];
var y1:int = data[idx2];
var y2:int = h - data[idx2];
bufferBmd_.setPixel32(x, y1, color);
bufferBmd_.setPixel32(x, y2, color);
}
bufferBmd_.applyFilter(bufferBmd_, rect_, ZERO_POINT, BLUR);
bufferBmd_.unlock();
bmd_.lock();
bmd_.draw(bufferBmd_, matrix_, null, BlendMode.ADD);
bmd_.unlock();
}
}
}
//package {
/**
* ブッダブロ描画クラス
* @author Aquioux(Yoshida, Akio)
*/
/*public*/ class Buddhabrot {
/**
* 複素数 c の実数部
*/
static public function set rl(value:Number):void { _rl = value; }
static private var _rl:Number = 0.0;
/**
* 複素数 c の虚数部
*/
static public function set im(value:Number):void { _im = value; }
static private var _im:Number = 0.0;
/**
* 発散評価のループ回数
*/
static public function set degree(value:int):void { _degree = value; }
static private var _degree:int = 1000;
/**
* スケール値
*/
static public function set scale(value:Number):void {
_scale = value;
calcInterval();
}
static private var _scale:Number = 1.0;
static private var width_:int; // ステージ幅
static private var height_:int; // ステージ高
static private var halfWidth_:Number; // ステージ幅の半分
static private var halfHeight_:Number; // ステージ高の半分
static private var interval_:Number; // 座標間隔
/**
* 初期化
* @param width ステージ幅
* @param height ステージ高
*/
static public function setup(width:int, height:int):void {
// ステージサイズ
width_ = width;
height_ = height;
halfWidth_ = width_ / 2;
halfHeight_ = height_ / 2;
// 座標間隔を計算する
calcInterval();
}
// 座標間隔を計算する
static private function calcInterval():void {
var shorter:int = (width_ < height_) ? width_ : height_;
interval_ = (_scale * shorter) / 4; // 定数 4 はスケール 1.0 のとき表示領域が -2 ~ 2 であるため
}
/**
* ブッダブロ描画
* @return プロットする2次元座標値を1次元配列で格納した Vector
*/
static public function update():Vector.<int> {
// 返り値となる Vector
var data:Vector.<int> = new Vector.<int>(_degree * 2, true);
// z_n
var zRl:Number = _rl; // 実数部
var zIm:Number = _im; // 虚数部
// z_n の各要素の二乗
var zRlSqr:Number; // 実数部
var zImSqr:Number; // 虚数部
// z_n+1
var zRlNxt:Number; // 実数部
var zImNxt:Number; // 虚数部
for (var i:int = 0; i < _degree; i++) {
// 発散の評価
zRlSqr = zRl * zRl;
zImSqr = zIm * zIm;
if (zRlSqr + zImSqr > 4) break;
// 発散していなかった場合、再評価へ向けて z_n の値を更新する
zRlNxt = zRlSqr - zImSqr + _rl;
zImNxt = 2 * zRl * zIm + _im;
zRl = zRlNxt;
zIm = zImNxt;
// z_n をプロット座標にして Vecter に待避
var idx1:uint = i * 2;
var idx2:uint = idx1 + 1;
data[idx1] = ((zRlNxt + 0.5) * interval_ + halfWidth_) >> 0;
data[idx2] = (zImNxt * interval_ + halfHeight_) >> 0;
}
if (i < _degree) data = null;
return data;
}
}
//}
//package aquioux.display.colorUtil {
/**
* コサインカーブで色相環的な RGB を計算
* @author Aquioux(Yoshida, Akio)
*/
/*public*/ class CycleRGB {
private static const PI:Number = Math.PI; // 円周率
private static const DEGREE90:Number = PI / 2; // 90度(弧度法形式)
private static const DEGREE180:Number = PI; // 180度(弧度法形式)
/**
* 角度に応じた RGB を得る
* @param angle HSV のように角度(度数法)を指定
* @return 色(0xNNNNNN)
*/
public static function getColor(angle:Number):uint {
var radian:Number = angle * PI / 180;
var valR:uint = (Math.cos(radian) + 1) * 0xFF >> 1;
var valG:uint = (Math.cos(radian + DEGREE90) + 1) * 0xFF >> 1;
var valB:uint = (Math.cos(radian + DEGREE180) + 1) * 0xFF >> 1;
return valR << 16 | valG << 8 | valB;
}
}
//}