Glynn Fractal
/**
* Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/8Btj
*/
package {
//import aquioux.display.colorUtil.CycleRGB;
import com.bit101.components.PushButton;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.Rectangle;
[SWF(width = "465", height = "465", frameRate = "60", backgroundColor = "#000000")]
/**
* Glynn Fractal の描画
* @see http://aquioux.net/blog/?p=2146
* @author Aquioux(Yoshida, Akio)
*/
public class Main extends Sprite {
// Glynn Fractal でない部分の色
private var colors0_:Vector.<uint>;
private var colors1_:Vector.<uint>;
// 計算クラス
private var julia_:Julia;
// 走査クラス
private var scan_:Scan;
// 表示用 BitmapData
private var bmd_:BitmapData;
// 前回押したボタン
private var prevButtonColor_:PushButton;
private var prevButtonScale_:PushButton;
// コンストラクタ
public function Main():void {
// カラーセットパターン1
colors0_ = new Vector.<uint>();
var degree:int = 128;
var step:Number = 0xFF / degree;
for (var i:int = 0; i < degree; i++) {
var c:uint = step * i;
colors0_[i] = c << 16 | c << 8 | c;
}
colors0_.fixed = true;
// カラーセットパターン2
colors1_ = new Vector.<uint>();
degree = 120;
step = 360 / degree;
for (i = 0; i < degree; i++) colors1_[i] = CycleRGB.getColor(i * step + 180);
colors1_.reverse();
colors1_.fixed = true;
// ステージサイズ
var sw:int = stage.stageWidth;
var sh:int = stage.stageHeight;
// Glynn Fractal クラスの生成
julia_ = new Julia();
julia_.colors = colors0_;
// Scan クラスの生成
scan_ = new Scan(sw, sh, julia_);
// Viewer の作成
bmd_ = new BitmapData(sw, sh, false, 0x0);
addChild(new Bitmap(bmd_));
// ボタンの作成
var buttonWidth:int = 50;
var buttonColor0:PushButton = new PushButton(this, 0, sh - 40, "monochome", buttonColor0Handler);
var buttonColor1:PushButton = new PushButton(this, buttonWidth, sh - 40, "colorful", buttonColor1Handler);
var buttonScale0:PushButton = new PushButton(this, 0, sh - 20, "* 1", buttonScale0Handler);
var buttonScale1:PushButton = new PushButton(this, buttonWidth, sh - 20, "* 2", buttonScale1Handler);
var buttonScale2:PushButton = new PushButton(this, buttonWidth * 2, sh - 20, "* 10", buttonScale2Handler);
var buttonScale3:PushButton = new PushButton(this, buttonWidth * 3, sh - 20, "* 25", buttonScale3Handler);
var buttonScale4:PushButton = new PushButton(this, buttonWidth * 4, sh - 20, "* 50", buttonScale4Handler);
buttonColor0.width = buttonWidth;
buttonColor1.width = buttonWidth;
buttonScale0.width = buttonWidth;
buttonScale1.width = buttonWidth;
buttonScale2.width = buttonWidth;
buttonScale3.width = buttonWidth;
buttonScale4.width = buttonWidth;
// 初回状態の表示
buttonColor0Handler(null);
buttonColor0.selected = true;
changeButtonColor(buttonColor0);
buttonScale2Handler(null);
buttonScale2.selected = true;
changeButtonScale(buttonScale2);
}
// 描画
private function draw():void {
scan_.update(bmd_, this);
}
// ボタンハンドラ(色)
private function buttonColor0Handler(e:Event):void {
if (e) changeButtonColor(PushButton(e.target));
julia_.colors = colors0_;
draw();
}
private function buttonColor1Handler(e:Event):void {
if (e) changeButtonColor(PushButton(e.target));
julia_.colors = colors1_;
draw();
}
// ボタンハンドラ(スケール)
private function buttonScale0Handler(e:Event):void {
if (e) changeButtonScale(PushButton(e.target));
scan_.reset();
draw();
}
private function buttonScale1Handler(e:Event):void {
if (e) changeButtonScale(PushButton(e.target));
scan_.rect = new Rectangle( -0.68, -1, 2, 2);
draw();
}
private function buttonScale2Handler(e:Event):void {
if (e) changeButtonScale(PushButton(e.target));
scan_.rect = new Rectangle( -0.43, -0.6, 0.4, 0.4);
draw();
}
private function buttonScale3Handler(e:Event):void {
if (e) changeButtonScale(PushButton(e.target));
scan_.rect = new Rectangle( -0.37, -0.405, 0.168, 0.168);
draw();
}
private function buttonScale4Handler(e:Event):void {
if (e) changeButtonScale(PushButton(e.target));
scan_.rect = new Rectangle( -0.35, -0.475, 0.08, 0.08);
draw();
}
// カレントボタンの変更
private function changeButtonColor(currentButton:PushButton):void {
if (prevButtonColor_) prevButtonColor_.enabled = true;
currentButton.enabled = false;
prevButtonColor_ = currentButton;
}
private function changeButtonScale(currentButton:PushButton):void {
if (prevButtonScale_) prevButtonScale_.enabled = true;
currentButton.enabled = false;
prevButtonScale_ = currentButton;
}
}
}
//package {
//import aquioux.math.Complex;
import flash.geom.Rectangle;
/**
* Glynn Fractal 描画クラス
* _scale = 1.0 のとき (-2, -2) ~ (2, 2) の領域を対象に計算する
* @author Aquioux(Yoshida, Akio)
*/
/*public*/ class Julia implements ICalculator {
/**
* 描画範囲
*/
public function get rect():Rectangle { return RECTANGLE; }
private const RECTANGLE:Rectangle = new Rectangle(MIN_X, MIN_Y, (MAX_X - MIN_X), (MAX_Y - MIN_Y));
// 個別の開始座標、終了座標
private const MIN_X:Number = -2.0; // X軸最小値
private const MIN_Y:Number = -2.0; // Y軸最小値
private const MAX_X:Number = 2.0; // X軸最大値
private const MAX_Y:Number = 2.0; // Y軸最大値
/**
* 集合に該当する部分の色(一般的には色なし=黒)
*/
private var _color:uint = 0x000000;
public function set color(value:uint):void { _color = value; }
/**
* 発散部分の色階調
*/
private var _colors:Vector.<uint>;
public function set colors(value:Vector.<uint>):void {
_colors = value;
degree_ = value.length;
}
/**
* 漸化式の c
*/
public function get c():Complex { return _c; }
private var _c:Complex = new Complex( -0.2, 0);
// 発散チェックループ回数(_colors.length の値)
private var degree_:int;
/**
* Scan クラスからの走査データを受け、計算をおこなう
* @param x X座標値
* @param y Y座標値
* @return 計算結果
*/
public function calculate(x:Number, y:Number):uint {
var r:int = formula(x, y, _c.real, _c.imag);
return (r >= 0) ? _colors[r] : _color;
}
/**
* 漸化式 z ← z^1.5 + c
* @param zRl 複素数 z の実数部
* @param zIm 複素数 z の虚数部
* @param cRl 複素数 c の実数部
* @param cIm 複素数 c の虚数部
* @return 発散評価値
* @private
*/
private function formula(zRl:Number, zIm:Number, cRl:Number, cIm:Number):int {
// 漸化式の計算要素の複素数
var zRlSqr:Number; // 実数部の2乗
var zImSqr:Number; // 虚数部の2乗
var zSqr:Number; // zRlSqr + zImSqr
// ド・モアブルの定理
var r1:Number; // 局座標形式複素数の距離(計算前)
var t1:Number; // 局座標形式複素数の偏角(計算前)
var r2:Number; // 局座標形式複素数の距離(計算後)
var t2:Number; // 局座標形式複素数の偏角(計算後)
var i:int = degree_;
while (i--) {
// 発散の評価
zRlSqr = zRl * zRl;
zImSqr = zIm * zIm;
zSqr = zRlSqr + zImSqr;
if (zSqr > 4) break;
// 発散していなかった場合、再評価へ向けて z_n の値を更新する
// 複素数を局座標化
r1 = Math.sqrt(zRlSqr + zImSqr);
t1 = Math.atan2(zIm, zRl);
// 冪乗計算
r2 = Math.sqrt(r1 * r1 * r1); // 距離
t2 = t1 * 1.5; // 偏角
zRl = Math.cos(t2) * r2 + cRl;
zIm = Math.sin(t2) * r2 + cIm;
}
return i;
// break で脱しなかった(発散しなかった)場合、while を回りきるので -1 になる
}
}
//}
//package {
import flash.geom.Rectangle;
/**
* 計算クラスの interface
* @author YOSHIDA, Akio (Aquioux)
*/
/*public*/ interface ICalculator {
function get rect():Rectangle;
function calculate(x:Number, y:Number):uint;
}
//}
//package {
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.events.Event;
import flash.geom.ColorTransform;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* 二次元走査クラス
* @author Aquioux(Yoshida, Akio)
*/
/*public*/ class Scan {
/**
* 描画領域
*/
private var _rect:Rectangle;
public function set rect(value:Rectangle):void {
_rect = value;
// 複素平面の中心座標を計算
centerX_ = _rect.x + _rect.width * 0.5;
centerY_ = _rect.y + _rect.height * 0.5;
calcStart(); // 走査開始座標
calcStep(); // 走査加算値
}
/**
* スケール値
*/
private var _scale:Number = 1.0;
public function set scale(value:Number):void {
var prevScale:Number = _scale; // 現在の _scale を退避
_scale = value; // 新たな _scale を代入
// _scale 変更に伴う _rect の更新
var rate:Number = prevScale / _scale;
_rect.width *= rate;
_rect.height *= rate;
_rect.x = centerX_ - _rect.width * 0.5;
_rect.y = centerY_ - _rect.height * 0.5;
calcStart(); // 走査開始座標
calcStep(); // 走査加算値
}
// 計算クラス
private var _calculator:ICalculator;
// 表示領域サイズ
private var displayWidth_:int; // 幅
private var displayHeight_:int; // 高
// 複素平面の中心座標
private var centerX_:Number; // 実数座標
private var centerY_:Number; // 虚数座標
// 平面走査の計算加算値
private var stepX_:Number; // 実数軸
private var stepY_:Number; // 虚数軸
// 走査開始座標
private var startX_:Number; // 実数座標
private var startY_:Number; // 虚数座標
// Viewer
private var canvas_:BitmapData; // 表示 BitmapData
private var rect_:Rectangle; // canvas_ 用 Rectangle
private var FADE:ColorTransform = new ColorTransform(0.95, 0.95, 0.95);
// 現在描画中の行
private var currentY_:int;
// Viewer へ渡すデータ
private var data_:Vector.<uint>;
private var idx_:int; // data_ のインデックス
/**
* コンストラクタ
* @param width 表示幅
* @param height 表示高
* @param calculator 計算クラス
*/
public function Scan(width:int, height:int, calculator:ICalculator) {
// 表示領域
displayWidth_ = width;
displayHeight_ = height;
// 計算クラス
_calculator = calculator;
reset();
// data_ の生成
data_ = new Vector.<uint>(width, true);
// rect_ の生成
rect_ = new Rectangle(0, 0, width, 1);
}
/**
* 複素数平面を走査し、その値を計算クラスに渡す
* @return 計算クラスから返ってきた値を格納した Vector
*/
public function update(canvas:BitmapData, parent:Sprite):void {
canvas_ = canvas;
canvas_.colorTransform(canvas_.rect, FADE);
currentY_ = 0;
parent.addEventListener(Event.ENTER_FRAME, draw);
}
private function draw(e:Event):void {
var posY:Number = startY_ + currentY_ * stepY_;
idx_ = 0;
for (var x:int = 0; x < displayWidth_; x++) {
data_[idx_++] = _calculator.calculate(startX_ + x * stepX_, posY);
}
rect_.y = currentY_;
canvas_.lock();
canvas_.setVector(rect_, data_);
currentY_++;
canvas_.unlock();
if (currentY_ >= displayHeight_) e.target.removeEventListener(Event.ENTER_FRAME, arguments.callee);
}
/**
* 描画領域のリセット
*/
public function reset():void {
rect = _calculator.rect;
scale = 1;
}
// 複素平面の走査開始座標を計算する
private function calcStart():void {
startX_ = _rect.x;
startY_ = _rect.y;
}
// 複素平面の走査加算値を計算する
private function calcStep():void {
stepX_ = _rect.width / (displayWidth_ - 1);
stepY_ = _rect.height / (displayHeight_ - 1);
}
}
//}
//package aquioux.math {
/**
* 複素数
* @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 = 0, im:Number = 0) {
_rl = rl;
_im = im;
}
// 複製
public function clone():Complex {
return new Complex(_rl, _im);
}
public function toString():String {
return _rl + " + " + _im + "i";
}
}
//}
//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);
}
}
//}