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)); } } //} 蜘蛛のフラクタル(ジュリア集合)