蜘蛛のフラクタル(ジュリア集合)
/**
* Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/uTQq
*/
package {
// import aquioux.display.colorUtil.CycleRGB;
// import aquioux.geom.Complex;
import com.bit101.components.HSlider;
import com.bit101.components.VSlider;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
[SWF(width = "465", height = "465", frameRate = "60", backgroundColor = "#000000")]
/**
* 蜘蛛のジュリア集合の描画
* @see http://aquioux.net/blog/?p=1890
* @author Aquioux(Yoshida, Akio)
*/
public class Main extends Sprite {
// ステージサイズ
private const WIDTH:int = 450;
private const HEIGHT:int = 450;
// スライダー関連
private const SLIDER_LONG:int = WIDTH;
private const SLIDER_SHORT:int = stage.stageWidth - WIDTH;
private const SLIDER_VALUE_MIN:Number = Julia.MIN_X;
private const SLIDER_VALUE_MAX:Number = Julia.MAX_X;
// 初期値
private const REAL_NUMBER:Number = 0.0; // c の実数部
private const IMAGINARY_NUMBER:Number = 0.0; // c の虚数部
// ジュリア集合でない部分の色
private var colors_:Vector.<uint>;
// 表示用 BitmapData
private var bmd_:BitmapData;
// 走査クラス
private var scan_:Scan;
// 計算クラス
private var julia_:Julia;
// スライダー
private var hslider_:HSlider;
private var vslider_:VSlider;
// 現在の c の値を表示するテキストフィールド
private var textField1_:TextField;
private var textField2_:TextField;
private var isDown_:Boolean = true;
public function Main():void {
// カラーセット
colors_ = new Vector.<uint>();
var degree:int = 45;
var step:Number = 360 / degree;
for (var i:int = 0; i < degree; i++) colors_[i] = CycleRGB.getColor(i * step + 180);
colors_.reverse();
colors_.fixed = true;
// Viewer の作成
bmd_ = new BitmapData(WIDTH, HEIGHT, false, 0x0);
addChild(new Bitmap(bmd_));
// スライダーの作成
// 横スライダー(複素数 c の実数部)
hslider_ = new HSlider(this, 0, HEIGHT);
hslider_.width = SLIDER_LONG;
hslider_.height = SLIDER_SHORT;
hslider_.setSliderParams(SLIDER_VALUE_MIN, SLIDER_VALUE_MAX, REAL_NUMBER);
hslider_.tick = 0.001;
// 縦スライダー(複素数 c の虚数部)
vslider_ = new VSlider(this, WIDTH, 0);
vslider_.width = SLIDER_SHORT;
vslider_.height = SLIDER_LONG;
vslider_.setSliderParams(SLIDER_VALUE_MIN, SLIDER_VALUE_MAX, IMAGINARY_NUMBER);
vslider_.tick = 0.001;
// テキストフィールドの作成
textField1_ = new TextField();
textField2_ = new TextField();
textField1_.autoSize = TextFieldAutoSize.LEFT;
textField2_.autoSize = TextFieldAutoSize.LEFT;
var fontSize:int = 12;
var textFormat:TextFormat = new TextFormat("_typewriter", fontSize, 0x000000);
textField1_.defaultTextFormat = textFormat;
textField2_.defaultTextFormat = textFormat;
textField1_.y = 0;
textField2_.y = fontSize * 1.25 >> 0;
addChild(textField1_);
addChild(textField2_);
// マウスハンドラの設定
// スライダーに直接ハンドラを設定すると ENTER_FRAME のタイミングで更新がかかり、
// 処理負荷が大きくなるため、マウスアップ時にスライダハンドラを実行させる
stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
stage.addEventListener(Event.MOUSE_LEAVE, mouseLeaveHandler);
// Julia クラスのセットアップ
julia_ = new Julia();
julia_.colors = colors_;
// Scan クラスのセットアップ
scan_ = new Scan();
scan_.minX = Julia.MIN_X;
scan_.maxX = Julia.MAX_X;
scan_.minY = Julia.MIN_Y;
scan_.maxY = Julia.MAX_Y;
scan_.calculator = julia_;
scan_.setup(WIDTH, HEIGHT);
// 初回状態の表示
mouseUpHandler(null);
}
// 描画
private function draw():void {
bmd_.lock();
bmd_.setVector(bmd_.rect, scan_.update());
bmd_.unlock();
}
// マウスハンドラ
private function mouseDownHandler(e:MouseEvent):void {
isDown_ = true;
}
private function mouseUpHandler(e:MouseEvent):void {
if (isDown_) {
hsliderHandler();
vsliderHandler();
isDown_ = false;
}
}
private function mouseLeaveHandler(e:Event):void {
mouseUpHandler(null);
}
// スライダーハンドラ
private function hsliderHandler():void {
var value:Number = hslider_.value;
textField1_.text = "real : " + String(value);
julia_.c.real = value;
draw();
}
private function vsliderHandler():void {
var value:Number = vslider_.value;
textField2_.text = "imag : " + String(value);
julia_.c.imag = value;
draw();
}
}
}
//package {
/**
* 二次元走査クラス
* @author Aquioux(Yoshida, Akio)
*/
/*public*/ class Scan {
/**
* 計算クラス
*/
private var _calculator:ICalculator;
public function set calculator(value:ICalculator):void { _calculator = value; }
// ----- 描画領域範囲 -----
/**
* 描画領域のX軸最小値(_scale = 1.0 における値)
*/
private var _minX:Number;
public function set minX(value:Number):void {
_minX = value;
calcWidth();
}
/**
* 描画領域のX軸最大値(_scale = 1.0 における値)
*/
private var _maxX:Number;
public function set maxX(value:Number):void {
_maxX = value;
calcWidth();
}
/**
* 描画領域のY軸最小値(_scale = 1.0 における値)
*/
private var _minY:Number;
public function set minY(value:Number):void {
_minY = value;
calcHeight();
}
/**
* 描画領域のY軸最大値(_scale = 1.0 における値)
*/
private var _maxY:Number;
public function set maxY(value:Number):void {
_maxY = value;
calcHeight();
}
/**
* スケール値
*/
public function set scale(value:Number):void {
_scale = value;
calcValue();
}
private var _scale:Number = 1.0;
/**
* 表示位置オフセットX座標値
*/
public function set offsetX(value:Number):void {
_offsetX = value;
calcStartX();
}
private var _offsetX:Number = 0.0;
/**
* 表示位置オフセットY座標値
*/
public function set offsetY(value:Number):void {
_offsetY = value;
calcStartY();
}
private var _offsetY:Number = 0.0;
// ----- その他変数 -----
// 計算領域
private var calculateWidth_:Number; // 幅
private var calculateHeight_:Number;// 高
// 計算領域の中心
private var centerX_:Number; // X座標
private var centerY_:Number; // Y座標
// 計算加算値
private var stepX_:Number; // X軸
private var stepY_:Number; // Y軸
// 走査開始座標
private var startX_:Number; // X座標
private var startY_:Number; // Y座標
// 表示領域
private var displayWidth_:int; // 幅
private var displayHeight_:int; // 高
// ビューアへ渡すデータ
private var data_:Vector.<uint>;
// data_ のインデックス
private var idx_:int;
/**
* 初期化
* @param width 表示幅
* @param height 表示高
*/
public function setup(width:int, height:int):void {
// 表示サイズ
displayWidth_ = width;
displayHeight_ = height;
// data_ の生成
data_ = new Vector.<uint>(width * height, true);
// 複素数平面走査用の各変数を計算する
calcValue();
// 計算クラスのセットアップ
if (!_calculator) new Error("setup 前に計算クラスを指定してください。");
_calculator.setup();
}
/**
* 複素数平面を走査し、その値を計算クラスの渡す
* @return 計算クラスから返ってきた値を格納した Vector
*/
public function update():Vector.<uint> {
idx_ = 0;
for (var y:int = 0; y < displayHeight_; y++) {
for (var x:int = 0; x < displayWidth_; x++) {
data_[idx_++] = _calculator.calculate(startX_ + x * stepX_, startY_ + y * stepY_);
}
}
return data_;
}
// 複素数平面走査用の変数を計算する
private function calcValue():void {
// 走査領域を計算する
calcWidth();
calcHeight();
// 複素数平面走査のインクリメントステップを計算する
calcStep();
// 複素数平面の走査開始座標を計算する
calcStartX(); // 実数座標
calcStartY(); // 虚数座標
}
// _minX, _maxX から走査範囲幅を計算
private function calcWidth():void {
calculateWidth_ = _maxX - _minX;
centerX_ = _minX + calculateWidth_ / 2;
}
// _minY, _maxY から走査範囲高を計算
private function calcHeight():void {
calculateHeight_ = _maxY - _minY;
centerY_ = _minY + calculateHeight_ / 2;
}
// 複素数平面走査の計算加算値を計算する
private function calcStep():void {
stepX_ = calculateWidth_ / (_scale * (displayWidth_ - 1));
stepY_ = calculateHeight_ / (_scale * (displayHeight_ - 1));
}
// 複素数平面の走査開始座標を計算する(実数部座標)
private function calcStartX():void {
startX_ = centerX_ - calculateWidth_ * 0.5 / _scale + _offsetX;
}
// 複素数平面の走査開始座標を計算する(虚数部座標)
private function calcStartY():void {
startY_ = centerY_ - calculateHeight_ * 0.5 / _scale - _offsetY;
}
}
//}
//package {
// import aquioux.geom.Complex;
// import aquioux.geom.MathComplex;
/**
* 蜘蛛のジュリア集合描画クラス
* _scale = 1.0 のとき (-2, -2) ~ (2, 2) の領域を対象に計算する
* @author Aquioux(Yoshida, Akio)
*/
/*public*/ class Julia implements ICalculator {
// ----- 描画領域範囲 -----
/**
* 描画領域のX軸最小値
*/
static public const MIN_X:Number = -2.0;
/**
* 描画領域のY軸最小値
*/
static public const MIN_Y:Number = -2.0;
/**
* 描画領域のX軸最大値
*/
static public const MAX_X:Number = 2.0;
/**
* 描画領域のY軸最大値
*/
static public const MAX_Y:Number = 2.0;
// ----- 色 -----
/**
* ジュリア集合に該当する部分の色(一般的には色なし=黒)
*/
public function set color(value:uint):void { _color = value; }
private var _color:uint = 0x000000;
/**
* 発散部分の色階調
*/
public function set colors(value:Vector.<uint>):void {
_colors = value;
degree_ = value.length;
}
private var _colors:Vector.<uint>;
// ----- 外部から与えられる複素数 -----
/**
* 漸化式の c の実数
*/
public function get c():Complex { return _c; }
//public function set c(value:Complex):void { _c = value; }
private var _c:Complex = MathComplex.COMPLEX_0;
// 発散チェックループ回数(_colors.length の値)
private var degree_:int;
/**
* 初期化
*/
public function setup():void {
// 発散時の色が外部から指定されていなければ設定する
if (!_colors) {
var len:int = 256;
_colors = new Vector.<uint>(len, true);
for (var i:int = 0; i < len; i++) _colors[i] = i << 16 | i << 8 | i;
degree_ = len;
}
}
/**
* Scan クラスからの走査データを受け、計算をおこなう
* @param x X座標値
* @param y Y座標値
* @return 計算結果
*/
public function calculate(x:Number, y:Number):uint {
var r:int = checkDivergence(x, y, _c);
return (r >= 0) ? _colors[r] : _color;
}
/**
* 漸化式 z ← z * z + c の評価
* @param zRl 複素数 z の実数部
* @param zIm 複素数 z の虚数部
* @param c 複素数 c
* @return 発散評価値
* @private
*/
private function checkDivergence(zRl:Number, zIm:Number, c:Complex):int {
// 漸化式の計算要素の複素数
var zRlSqr:Number; // 実数部の2乗
var zImSqr:Number; // 虚数部の2乗
// 漸化式の計算結果の複素数
var zRlNxt:Number; // 実数部
var zImNxt:Number; // 虚数部
// 漸化式の c
var cRl:Number = c.real;
var cIm:Number = c.imag;
var i:int = degree_;
while (i--) {
// 発散の評価(|z| > 2 = |z|^2 > 4)
zRlSqr = zRl * zRl;
zImSqr = zIm * zIm;
if (zRlSqr + zImSqr > 4) break;
// 発散していなかった場合、漸化式実行
zRlNxt = zRlSqr - zImSqr + cRl;
zImNxt = 2 * zRl * zIm + cIm;
zRl = zRlNxt;
zIm = zImNxt;
// 複素数 c の値を更新(c_n+1 ← c_n / 2 + z_n+1)
cRl /= 2;
cIm /= 2;
cRl += zRl;
cIm += zIm;
}
return i;
// break で脱しなかった(発散しなかった)場合、while を回りきるので -1 になる
}
}
//}
//package {
/**
* interface
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ interface ICalculator {
function setup():void;
function calculate(x:Number, y:Number):uint;
}
//}
//package aquioux.display.colorUtil {
/**
* コサインカーブで色相環的な RGB を計算
* @author Aquioux(YOSHIDA, Akio)
*/
/*public*/ class CycleRGB {
/**
* 32bit カラーのためのアルファ値(0~255)
*/
static public function get alpha():uint { return _alpha; }
static public function set alpha(value:uint):void {
_alpha = (value > 0xFF) ? 0xFF : value;
}
private static var _alpha:uint = 0xFF;
private static const PI:Number = Math.PI; // 円周率
private static const DEGREE120:Number = PI * 2 / 3; // 120度(弧度法形式)
/**
* 角度に応じた RGB を得る
* @param angle HSV のように角度(度数法)を指定
* @return 色(0xNNNNNN)
*/
public static function getColor(angle:Number):uint {
var radian:Number = angle * PI / 180;
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 r << 16 | g << 8 | b;
}
/**
* 角度に応じた RGB を得る(32bit カラー)
* @param angle HSV のように角度(度数法)を指定
* @return 色(0xNNNNNNNN)
*/
public static function getColor32(angle:Number):uint {
return _alpha << 24 | getColor(angle);
}
}
//}
//package aquioux.geom {
/**
* 複素数
* @author Aquioux(Yoshida, Akio)
*/
/*public*/ final class Complex {
// 実数部
public function get real():Number { return _rl; }
public function set real(value:Number):void { _rl = value; }
private var _rl:Number;
// 虚数部
public function get imag():Number { return _im; }
public function set imag(value:Number):void { _im = value; }
private var _im:Number;
// コンストラクタ
public function Complex(rl:Number, im:Number) {
_rl = rl;
_im = im;
}
// 複製
public function clone():Complex {
return new Complex(_rl, _im);
}
public function toString():String {
return _rl + " + " + _im + "i";
}
}
//}
//package aquioux.geom {
/**
* 複素数の演算
* @author Aquioux(Yoshida, Akio)
*/
/*public*/ final class MathComplex {
// 定数
static public const COMPLEX_1:Complex = new Complex(1.0, 0.0);
static public const COMPLEX_0:Complex = new Complex(0.0, 0.0);
static public const PURELY_IMAGINARY:Complex = new Complex(0.0, 1.0);
// 加算
static public function add(a:Complex, b:Complex):Complex {
return new Complex(
a.real + b.real,
a.imag + b.imag
);
}
// 減算
static public function subtract(a:Complex, b:Complex):Complex {
return new Complex(
a.real - b.real,
a.imag - b.imag
);
}
// 乗算
static public function multiply(a:Complex, b:Complex):Complex {
return new Complex(
a.real * b.real - a.imag * b.imag,
a.real * b.imag + a.imag * b.real
);
}
// 除算
static public function divide(a:Complex, b:Complex):Complex {
var val:Number = abs2(b);
return new Complex(
(a.real * b.real + a.imag * b.imag) / val,
(a.imag * b.real - a.real * b.imag) / val
);
}
// 共役複素数を求める
static public function conjugate(c:Complex):Complex {
return new Complex(
c.real,
-c.imag
);
}
// 絶対値
static public function abs(c:Complex):Number {
return Math.sqrt(abs2(c));
// |c| = √(c * c~) = √(c.real^2 + c.imag^2)
}
// 絶対値の二乗
static public function abs2(c:Complex):Number {
return c.real * c.real + c.imag * c.imag;
}
// スケーリング(第1引数の実数部、虚数部をそれぞれ第2引数倍する)
static public function scale(c:Complex, n:Number):Complex {
return new Complex(
c.real * n,
c.imag * n
);
}
// 整数化(引数の実数部、虚数部それぞれの小数点以下を切り捨てる)
static public function integer(c:Complex):Complex {
return new Complex(
c.real >> 0,
c.imag >> 0
);
}
// べき乗
static public function pow(c:Complex, n:int):Complex {
var z:Complex = c.clone();
while(--n) z = multiply(z, c);
return z;
}
// 平方根
static public function sqrt(c:Complex):Complex {
var val1:Number;
if (Math.abs(c.real) < 0.000001) {
val1 = c.imag * Math.PI / (2 * Math.abs(c.imag));
} else {
val1 = Math.atan2(c.imag, c.real);
}
val1 /= 2;
var val2:Number = Math.pow(abs2(c), 1 / 4);
return new Complex(
Math.cos(val1) * val2,
Math.sin(val1) * val2
);
}
// 指数関数
static public function exp(c:Complex):Complex {
var val:Number = Math.exp(c.real);
return new Complex(
Math.cos(c.imag) * val,
Math.sin(c.imag) * val
);
}
// 三角関数(サイン)
static public function sin(c:Complex):Complex {
return new Complex(
Math.sin(c.real) * (Math.exp(c.imag) + Math.exp(-c.imag)) / 2,
Math.cos(c.real) * (Math.exp(c.imag) - Math.exp(-c.imag)) / 2
);
}
// 三角関数(コサイン)
static public function cos(c:Complex):Complex {
return new Complex(
Math.cos(c.real) * (Math.exp(c.imag) + Math.exp(-c.imag)) / 2,
-Math.sin(c.real) * (Math.exp(c.imag) - Math.exp(-c.imag)) / 2
);
}
// 三角関数(タンジェント)
static public function tan(c:Complex):Complex {
return divide(sin(c), cos(c));
}
}
//}