Lorenz Attractor
/**
* Copyright Aquioux ( http://wonderfl.net/user/Aquioux )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/mAN4
*/
package {
import caurina.transitions.Tweener;
import flash.display.Graphics;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
[SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#000000")]
/**
* Lorenz Attractor
* @see http://aquioux.net/blog/?p=2064
* @author YOSHIDA, Akio(Aquioux)
*/
public class Main extends Sprite {
private var viewer_:Viewer; // ビューア
private var buttons_:Buttons; // ボタン
private var sliders_:Sliders; // スライダー
private var mouseStage_:Sprite; // マウスドラッグで動かすため
// マウスをダウンしているか否か
private var isMouseDown_:Boolean = false;
// MOUSW_MOVE による移動量
private var moveX_:Number = 0;
private var moveY_:Number = 0;
// 前回の MOUSW_MOVE 時のマウス座標
private var prevMouseX_:Number = 0;
private var prevMouseY_:Number = 0;
// 摩擦係数
private var friction:Number = 0.98;
public function Main():void {
setup();
addEventListener(Event.ENTER_FRAME, update);
viewer_.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
viewer_.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
viewer_.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);
}
// セットアップ
private function setup():void {
// ステージサイズ
var w:int = stage.stageWidth;
var h:int = stage.stageHeight;
// エンジンの初期化
Engine.setup();
// プロジェクションクラス初期化
Projection.setup(Engine.data);
// ビューアの作成
viewer_ = new Viewer(w, h);
viewer_.buttonMode = true;
addChild(viewer_);
// ボタンの作成
buttons_ = new Buttons();
buttons_.action = drawByButton;
buttons_.y = h - buttons_.height;
addChild(buttons_);
// スライダーの作成
sliders_ = new Sliders();
sliders_.action = drawBySlider;
sliders_.reset();
addChild(sliders_);
}
// アップデート
private function update(e:Event):void {
if (isMouseDown_) {
moveX_ = mouseX - prevMouseX_;
moveY_ = mouseY - prevMouseY_;
prevMouseX_ = mouseX;
prevMouseY_ = mouseY;
} else {
moveX_ *= friction;
moveY_ *= friction;
}
viewer_.update(Projection.update(moveX_, moveY_));
}
// マウスハンドラ
private function mouseDownHandler(e:MouseEvent):void {
isMouseDown_ = true;
}
private function mouseUpHandler(e:MouseEvent):void {
isMouseDown_ = false;
}
private function mouseMoveHandler(e:MouseEvent):void {
if (!isMouseDown_) {
prevMouseX_ = mouseX;
prevMouseY_ = mouseY;
}
}
// ボタンに起因する描画の実行
// エンジンの変数の値をトゥイーン
private function drawByButton(values:Vector.<Number>):void {
Tweener.addTween(Engine, {
"p":values[0],
"r":values[1],
"b":values[2],
time:1.5,
transition:"easeOutCubic",
onStart:viewer_.reset,
onUpdate:sliders_.reset
});
}
// スライダーに起因する描画の実行
private function drawBySlider():void {
buttons_.reset();
}
}
}
//package {
/**
* エンジン(Lorenz Attractor)
* @author YOSHIDA, Akio(Aquioux)
*/
/*public*/ class Engine {
/**
* 計算に使用するパラメータ p
*/
static public function get p():Number { return _p; }
static public function set p(value:Number):void {
_p = value;
update(); // 再計算
}
static private var _p:Number;
/**
* 計算に使用するパラメータ r
*/
static public function get r():Number { return _r; }
static public function set r(value:Number):void {
_r = value;
update(); // 再計算
}
static private var _r:Number;
/**
* 計算に使用するパラメータ b
*/
static public function get b():Number { return _b; }
static public function set b(value:Number):void {
_b = value;
update(); // 再計算
}
static private var _b:Number;
/**
* 計算に使用する各パラメータの最小値、最大値、既定値
*/
static public function get P_MIN():Number { return PARAMS[0]; }
static public function get P_MAX():Number { return PARAMS[1]; }
static public function get P_DEFAULT():Number { return PARAMS[2]; }
static public function get R_MIN():Number { return PARAMS[3]; }
static public function get R_MAX():Number { return PARAMS[4]; }
static public function get R_DEFAULT():Number { return PARAMS[5]; }
static public function get B_MIN():Number { return PARAMS[6]; }
static public function get B_MAX():Number { return PARAMS[7]; }
static public function get B_DEFAULT():Number { return PARAMS[8]; }
static private const PARAMS:Vector.<Number> = Vector.<Number>([
0.0, 100, 10, // p
0.0, 100, 28, // r
0.0, 10, 8 / 3 // b
]);
static private const DT:Number = 0.005;
/**
* 座標データ Vector
*/
static public function get data():Vector.<Number> { return _data; }
static private var _data:Vector.<Number>;
/**
* パーティクル数
*/
static public function get numOfParticle():int { return numOfParticle_; }
static private var numOfParticle_:int = 15000;
// 漸化式用変数
static private var x_:Number; // X座標値
static private var y_:Number; // Y座標値
static private var z_:Number; // Y座標値
// スケール
static private const SCALE:Number = 0.05 / DT;
/**
* セットアップ
*/
static public function setup():void {
_data = new Vector.<Number>(numOfParticle_ * 3, true);
paramDefault();
}
/**
* 計算
* @return 結果の座標を一次元配列で格納した Vector
*/
static public function update():void {
x_ = Math.random() * 20 - 10;
y_ = Math.random() * 20 - 10;
z_ = Math.random() * 20 - 10;
var len:int = numOfParticle_ * 3;
for (var i:int = 0; i < len; i += 3) {
var xn:Number = (_p * (y_ - x_)) * DT;
var yn:Number = (x_ * (_r - z_) - y_) * DT;
var zn:Number = (x_ * y_ - _b * z_) * DT;
x_ += xn;
y_ += yn;
z_ += zn;
_data[i] = x_ * SCALE;
_data[i + 1] = y_ * SCALE;
_data[i + 2] = z_ * SCALE;
}
}
/**
* パラメータを既定値に戻す
*/
static public function paramDefault():void {
_p = P_DEFAULT;
_r = R_DEFAULT;
_b = B_DEFAULT;
update(); // 再計算
}
/**
* パラメータをランダムな値にする
*/
static public function paramRandom():void {
_p = Math.random() * (P_MAX - P_MIN) + P_MIN;
_r = Math.random() * (R_MAX - R_MIN) + R_MIN;
_b = Math.random() * (B_MAX - B_MIN) + B_MIN;
update(); // 再計算
}
}
//}
//package {
import flash.geom.Matrix3D;
import flash.geom.PerspectiveProjection;
import flash.geom.Utils3D;
import flash.geom.Vector3D;
/**
* 三次元座標を二次元座標に投射する
* @author YOSHIDA, Akio(Aquioux)
*/
/*public*/ class Projection {
/**
* X座標オフセット値
*/
static public function get offsetX():Number { return _offsetX; }
static public function set offsetX(value:Number):void { _offsetX = value; }
static private var _offsetX:Number = 0;
/**
* Y座標オフセット値
*/
static public function get offsetY():Number { return _offsetY; }
static public function set offsetY(value:Number):void { _offsetY = value; }
static private var _offsetY:Number = 0;
/**
* Z座標オフセット値
*/
static public function get offsetZ():Number { return _offsetZ; }
static public function set offsetZ(value:Number):void { _offsetZ = value; }
static private var _offsetZ:Number = 500;
// 座標 Vecotr
static private var verts_:Vector.<Number>; // 三次元座標
static private var projectedVerts_:Vector.<Number>; // 二次元投射後
static private var uvts_:Vector.<Number>; // uvts
// パースペクティブ・プロジェクション
static private var projection_:PerspectiveProjection;
static private var projectionMatrix3D_:Matrix3D;
// 回転計算用マトリクス
static private var matrix_:Matrix3D;
// 移動量保持
static private var vx_:Number = 0;
static private var vy_:Number = 0;
/**
* セットアップ
* @param data 三次元座標データ
*/
static public function setup(verts:Vector.<Number>):void {
// 座標 Vecotr
verts_ = verts;
var n:uint = verts_.length;
projectedVerts_ = new Vector.<Number>(n * 2 / 3, true);
uvts_ = new Vector.<Number>(n, true); // 使っていない
// パースペクティブ・プロジェクション
projection_ = new PerspectiveProjection();
projectionMatrix3D_ = projection_.toMatrix3D();
// 回転計算用マトリクス
matrix_ = new Matrix3D();
}
/**
* アップデート
* @param moveX 移動量(X軸方向)
* @param moveY 移動量(Y軸方向)
* @return 三次元座標を投射した二次元座標データ
*/
static public function update(moveX:Number, moveY:Number):Vector.<Number> {
// 外部からの移動量 moveX、moveY を内部の移動量変数 vx_、vy_ に加算
vx_ += moveX;
vy_ -= moveY;
// マトリクス計算
matrix_.identity();
matrix_.appendRotation(vy_, Vector3D.X_AXIS);
matrix_.appendRotation(vx_, Vector3D.Y_AXIS);
matrix_.appendTranslation(_offsetX, _offsetY, _offsetZ);
matrix_.append(projectionMatrix3D_);
// 座標データに回転を適用
Utils3D.projectVectors(matrix_, verts_, projectedVerts_, uvts_);
return projectedVerts_;
}
}
//}
//package {
//import aquioux.display.colorUtil.CycleRGB;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;
import flash.filters.BitmapFilterQuality;
import flash.filters.BlurFilter;
import flash.geom.ColorTransform;
import flash.geom.Point;
import flash.geom.Rectangle;
/**
* ビューア
* @author YOSHIDA, Akio(Aquioux)
*/
/*public*/ class Viewer extends Sprite {
/**
* ColorTransform によるフェードアウトのための定義
*/
public function set fade(value:ColorTransform):void { _fade = value; }
private var _fade:ColorTransform = new ColorTransform(0.95, 0.95, 0.95);
/**
* Blur filter によるフェードアウトのための定義
*/
public function set blur(value:BlurFilter):void { _blur = value; }
private var _blur:BlurFilter = new BlurFilter(8, 8, BitmapFilterQuality.HIGH);
// 描画色用の変数
private var start_:Number; // 開始位置
private var add_:Number; // start_ への増分
private var renge_:Number; // 循環色相の範囲(角度)
// BitmapData 関連
private var bmd_:BitmapData; // 表示 BitmapData
private var bufferBmd_:BitmapData; // バッファ
private var rect_:Rectangle; // ColorTransform, Blur 共用
private const ZERO_POINT:Point = new Point(0, 0);
// 表示オフセット
private var sw_:int; // ステージ幅
private var sh_:int; // ステージ高
private var offsetX_:Number; // X座標オフセット
private var offsetY_:Number; // Y座標オフセット
/**
* コンストラクタ
* @param sw ステージ幅
* @param sh ステージ高
*/
public function Viewer(sw:int, sh:int) {
// ステージサイズ
sw_ = sw;
sh_ = sh;
// BitmapData 関連
bufferBmd_ = new BitmapData(sw, sh, true, 0xFF000000);
bmd_ = bufferBmd_.clone();
rect_ = new Rectangle(0, 0, sw, sh);
addChild(new Bitmap(bmd_));
// 各オフセット
offsetX_ = sw / 2;
offsetY_ = sh / 2;
// 描画色関連
CycleRGB.alpha = 0xCC;
start_ = Math.random() * 360 >> 0;
reset();
}
/**
* アップデート
* @param data 描画座標データ
*/
public function update(data:Vector.<Number>):void {
// bufferBmd_ の更新
bufferBmd_.lock();
bufferBmd_.fillRect(bufferBmd_.rect, 0x00000000);
var len:uint = data.length;
start_ += add_;
for (var i:int = 0; i < len; i += 2) {
var px:Number = (data[i] + offsetX_) >> 0;
var py:Number = (data[i + 1] + offsetY_) >> 0;
var offsetColorX:Number = (px > offsetX_) ? px - sw_ : px;
var offsetColorY:Number = (py > offsetY_) ? py - sh_ : py;
var offsetColor:Number = offsetColorX * offsetColorY / renge_;
if (offsetColor < 0) offsetColor = -offsetColor;
bufferBmd_.setPixel32(px, py, CycleRGB.getColor32(offsetColor + start_));
}
bufferBmd_.unlock();
// bmd_ の更新
bmd_.lock();
bmd_.colorTransform(rect_, _fade);
bmd_.applyFilter(bmd_, rect_, ZERO_POINT, _blur);
bmd_.draw(bufferBmd_);
bmd_.unlock();
}
/**
* 描画色用変数の再設定
*/
public function reset():void {
add_ = ((Math.random() * 50 >> 0) + 50) / 100;
renge_ = ((Math.random() * 150 >> 0) + 120);
}
}
//}
//package {
import com.bit101.components.HUISlider;
import com.bit101.components.Slider;
import flash.display.Sprite;
import flash.events.Event;
import flash.utils.Dictionary;
/**
* コントロール用スライダー
* @author YOSHIDA, Akio(Aquioux)
*/
/*public*/ class Sliders extends Sprite {
/**
* スライダーアクション(外部で定義した処理)
*/
public function set action(value:Function):void { _action = value; }
private var _action:Function;
// スライダーの値を小数第何位まで有効にするか
private const PRECISION:int = 3;
private const TICK:Number = 1 / Math.pow(10, PRECISION);
// スライダーに対応させるエンジン・プロパティ
private const PROPS:Array = ["p", "r", "b"];
private const DELIMITER:String = ":";
// スライダー格納配列
private var sliders:Dictionary;
/**
* コンストラクタ
*/
public function Sliders() {
var params:Vector.<Number> = Vector.<Number>([
Engine.P_MIN, Engine.P_MAX, Engine.p,
Engine.R_MIN, Engine.R_MAX, Engine.r,
Engine.B_MIN, Engine.B_MAX, Engine.b
]);
sliders = new Dictionary();
for (var i:int = 0; i < PROPS.length; i++) {
var prop:String = PROPS[i];
var slider:HUISlider = new HUISlider(this, 25, i * 15, prop + DELIMITER, handler);
slider.width = 430;
slider.labelPrecision = PRECISION;
slider.tick = TICK;
slider.setSliderParams(params[i * 3], params[i * 3 + 1], params[i * 3 + 2]);
sliders[prop] = slider;
}
}
/**
* リセット(Engine 内の数値に伴ったスライダー値にリセット)
*/
public function reset():void {
for each (var prop:String in PROPS) sliders[prop].value = Engine[prop];
}
// ハンドラ
private function handler(e:Event):void {
var target:HUISlider = HUISlider(e.target);
var prop:String = target.label.split(DELIMITER)[0];
Engine[prop] = target.value;
_action();
}
}
//}
//package {
import com.bit101.components.PushButton;
import flash.display.Sprite;
import flash.events.Event;
import flash.utils.Dictionary;
/**
* コントロール用ボタン
* @author YOSHIDA, Akio(Aquioux)
*/
/*public*/ class Buttons extends Sprite {
/**
* ボタンアクション(外部で定義した処理)
*/
public function set action(value:Function):void { _action = value; }
private var _action:Function;
// エンジンのラベルとプロパティ値のペア
private const PAIR:Array = [
["Default", Vector.<Number>([10, 28, 8 / 3])],
["No.1", Vector.<Number>([7.4, 28, 5])],
["No.2", Vector.<Number>([4.6, 50, 4.555])],
["No.3", Vector.<Number>([6, 27, 5])],
["No.4", Vector.<Number>([7.5, 28, 4.8])],
["No.5", Vector.<Number>([4.195, 50, 0])],
["No.6", Vector.<Number>([0.345, 75, 0])],
["No.7", Vector.<Number>([80, 38.5, 1.5])],
["No.8", Vector.<Number>([0.575, 100, 0.215])]
];
private var values:Dictionary;
// 前回押したボタン
private var prevButton_:PushButton;
/**
* インストラクタ
*/
public function Buttons() {
// ボタンの作成
var buttonWidth:int = 52;
var buttonHeight:int = 20;
var idx:int = 0;
values = new Dictionary();
for (var x:int = 0; x < PAIR.length; x++) {
var b:PushButton = new PushButton(this, buttonWidth * x, buttonHeight * y, PAIR[idx][0], handler);
b.width = buttonWidth;
b.height = buttonHeight;
values[b] = PAIR[idx][1];
idx++;
}
}
/**
* リセット(前回押したため無効になっているボタンを有効にする)
*/
public function reset():void {
if (prevButton_) prevButton_.enabled = true;
}
// ハンドラ
private function handler(e:Event):void {
var target:PushButton = PushButton(e.target);
handlerAction(target);
_action(values[target]);
}
/**
* ボタンアクション(内部で完了する処理)
* 押されたボタンを押せなくし、前回押したボタンを押せるようにする
* @param target 押されたボタン
* @private
*/
private function handlerAction(target:PushButton):void {
reset();
target.enabled = false;
prevButton_ = target;
}
}
//}
//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);
}
}
//}