LOOSELIPS
Amebaでブログを始めよう!

ActionScript: グラフを描く

点が打てたので、それを応用してグラフを描く。
ついでにActionScriptも少しずつ勉強したいので、
ちょっと数学的なグラフにしてみた。

参考にしたのは、最近手に入れた 「新版基礎からの力学系」 小室元政 著 サイエンス社。
複雑系に出てくるグラフは美しいので。

作成したクラスは、前回の点を打つものを多少洗練させたものと
弾道計算なんかの数値解析で使ったりする、ルンゲ・クッタ法の実装。
作成したクラスは以下のとおり。(一番最後に添付)

looselips.chaos.dbs.DBS クラス:Double Scroll アトラクタの数式モデル
looselips.chaos.RKFunc インタフェース:DBSクラスで実装しているルンゲ・クッタ法計算エンジン用インタフェース。他の数式もこのインタフェースを実装していれば、すぐに計算できるように
looselips.chaos.RK クラス:ルンゲ・クッタ法の実装。RKFuncインタフェースを介して数式モデルを実行
looselips.chaos.Controllerクラス:RKクラスの呼び出しと、現在の計算結果の保持および計算結果のステージへのマッピングを行なう
looselips.control.Axis2Dクラス:仮想2次元グラフの表示
looselips.control.LooseMovieClipクラス:MovieClipのラッパークラス(継承したときの生成方法がわからなかった)
looselips.shape.Pointクラス:点を表現するクラス


タイムラインには、以下の実装

import looselips.chaos.Controller;
import looselips.control.LooseMovieClip;
import looselips.chaos.dbs.DBS;

var mc: LooseMovieClip = new LooseMovieClip(this);
mc.setColor(0xff0000);
var ia:Array = [0.1, 0.1, 0.1]; // 初期値
var ctrl:Controller = new Controller (mc, new DBS(), 8.0, 8.0, [0.1, 0.1, 0.1], 0.04, 150.0);
function exec () {
ctrl.execute();
}
mc.getMC().onEnterFrame = exec;

実は一番調査に時間がかかったのが、MovieClip.onEnterFrame というやつ。
当初、これが見つからなかった段階では、グラフがすべて描き終わってから
表示されていたんだけど、せっかくだから、弾道計算のように一つ一つの点の
動きを見たいと。これを実現するのが、MovieClip.onEnterFrame への関数の
登録だった。(上記コードの最後の部分)

実行すると、こんな感じのグラフをすこしずつ描いていく。
811SH 上でも確認。


start



middle


今回学んだこと:
- 点を打つにしろ、すこしづつ MovieClip の状況を変えていくには、
 onEnterFrame や setInterval 関数を使うなどする必要があるらしい。
onEnterFrame だとフレームレート毎に設定した関数をイベントとして呼び出す。
 今回は、計算された最新の軌道点を Controller クラスで保持することにより、
 onEnterFrame で呼び出された際に、次の軌道点を算出するデータとして取り出せるようにしてある。
- BitMapData というやつを使うともっと効率よく描画ができそうだと感じたが、
 Flash Lite 2.0 ではサポートしていなかった。
- Number は、浮動小数点、整数も扱う。Math.floor()、Math.ceil() などで、整数への切捨て、切上げ処理ができる
- スピードを重視するのであれば、メソッド呼び出しを減らしたほうがよさそう。
- 無償の Flex SDK には ActionScript 3 というやつがついているらしい。(まだインストールしただけ)

次は、電波受信状況を一定の間隔でグラフ表示してみたいと思う。
それができれば、それなりのイメージをつけていろいろできそう。

課題:
- 電波受信状況、電池状況、時間、画面のオンオフなどの情報の取得方法
- 外部ファイル(?)のような外部記憶の利用方法
- onEnterFrame でなく、指定した一定時間で関数なりメソッドを起動させる方法

以下、ソース。すべて、個別の .as ファイル。
パッケージはフォルダに対応。
(ここって、画像以外アップロードできないのか??)

---looselips.chaos.dbs.DBS クラス:Double Scroll アトラクタの数式モデル

// ストレンジアトラクターダブルスクロール方程式

class looselips.chaos.dbs.DBS implements looselips.chaos.RKFunc {
public var G:Number = 0.70;
public var A:Number = -0.8/G;
public var B:Number = -0.5/G;
public var Alpha:Number = 9.0;
public var Beta:Number = 7.0/(G*G);

function f1(dt:Number, px:Number, py:Number, pz:Number):Number {
return Alpha * (py - px - f(px));
}
function f2(dt:Number, px:Number, py:Number, pz:Number):Number {
return px - py + pz;
}
function f3(dt:Number, px:Number, py:Number, pz:Number):Number {
return -1.0 * Beta * py;
}
private function f(px:Number):Number {
if ((px > 1.0)) {
return B*px + A - B;
} else if ((px >= -1.0) && (px <= 1.0)) {
return A*px;
} else if (px < -1.0) {
return B*px - A + B;
}
}
}

------looselips.chaos.RKFunc インタフェース:DBSクラスで実装しているルンゲ・クッタ法計算エンジン用インタフェース。他の数式もこのインタフェースを実装していれば、すぐに計算できるように

interface looselips.chaos.RKFunc {

function f1(dt:Number, px:Number, py:Number, pz:Number):Number;
function f2(dt:Number, px:Number, py:Number, pz:Number):Number;
function f3(dt:Number, px:Number, py:Number, pz:Number):Number;

}

------ looselips.chaos.RK クラス:ルンゲ・クッタ法の実装。RKFuncインタフェースを介して数式モデルを実行

import looselips.chaos.RKFunc;
// DBS 用のルンゲ・クッタ法実装
// 誤ってメモリ使いすぎないよに全部 static
class looselips.chaos.RK {
// new Array(3) と同じか。
private static var k0:Array = [0.0, 0.0, 0.0];
private static var k1:Array = [0.0, 0.0, 0.0];
private static var k2:Array = [0.0, 0.0, 0.0];
private static var k3:Array = [0.0, 0.0, 0.0];

static function nextPoint(sys:RKFunc, pa:Array, dt:Number):Array {
var px:Number = pa[0];
var py:Number = pa[1];
var pz:Number = pa[2];
k0[0] = dt*sys.f1(dt, px, py, pz);
k0[1] = dt*sys.f2(dt, px, py, pz);
k0[2] = dt*sys.f3(dt, px, py, pz);

k1[0] = dt*sys.f1(dt, px+k0[0]/2.0, py+k0[1]/2.0, pz+k0[2]/2.0);
k1[1] = dt*sys.f2(dt, px+k0[0]/2.0, py+k0[1]/2.0, pz+k0[2]/2.0);
k1[2] = dt*sys.f3(dt, px+k0[0]/2.0, py+k0[1]/2.0, pz+k0[2]/2.0);

k2[0] = dt*sys.f1(dt, px+k1[0]/2.0, py+k1[1]/2.0, pz+k1[2]/2.0);
k2[1] = dt*sys.f2(dt, px+k1[0]/2.0, py+k1[1]/2.0, pz+k1[2]/2.0);
k2[2] = dt*sys.f3(dt, px+k1[0]/2.0, py+k1[1]/2.0, pz+k1[2]/2.0);

k3[0] = dt*sys.f1(dt, px+k2[0], py+k2[1], pz+k2[2]);
k3[1] = dt*sys.f2(dt, px+k2[0], py+k2[1], pz+k2[2]);
k3[2] = dt*sys.f3(dt, px+k2[0], py+k2[1], pz+k2[2]);

pa[0] = px+(k0[0]+2.0*k1[0]+2.0*k2[0]+k3[0])/6.0;
pa[1] = py+(k0[1]+2.0*k1[1]+2.0*k2[1]+k3[1])/6.0;
pa[2] = pz+(k0[2]+2.0*k1[2]+2.0*k2[2]+k3[2])/6.0;

return pa;
}
}

-------- looselips.chaos.Controllerクラス:RKクラスの呼び出しと、現在の計算結果の保持および計算結果のステージへのマッピングを行なう

import looselips.chaos.RK;
import looselips.chaos.RKFunc;
import looselips.control.Axis2D;
import looselips.control.LooseMovieClip;

// RK を実行するクラス
// 初期値とループ処理をもつ
class looselips.chaos.Controller {
private var dt:Number = 0.01;
private var tMax:Number = 10.0;
private var pa:Array = [0.0, 0.0, 0.0];
private var t:Number = 0.0;
private var axis:Axis2D;
private var sys:RKFunc;
private var mc:LooseMovieClip;

public function Controller (pmc:LooseMovieClip, rkf:RKFunc, awidth:Number, aheight:Number, ia:Array, idt:Number, itMax:Number) {
this.dt = idt;
this.tMax = itMax;
this.pa[0] = ia[0];
this.pa[1] = ia[1];
this.pa[2] = ia[2];
this.mc = pmc;

this.axis = new Axis2D (awidth, aheight, this.mc);
this.sys = rkf;
}

public function execute () : Void {
if (this.t > this.tMax) {
this.mc.stop();
}

this.pa = RK.nextPoint(this.sys, this.pa, this.dt);
// zx 座標で描画
this.axis.plot(this.pa[2], this.pa[0]);
this.t = this.t + dt;
//trace (this.pa);

}
}

------ looselips.control.Axis2Dクラス:仮想2次元グラフの表示

import looselips.shape.Point;
import looselips.control.LooseMovieClip;

class looselips.control.Axis2D {
private var mWidth:Number;
private var mHeight:Number;
private var cx:Number;
private var cy:Number;
private var mc:LooseMovieClip;
private var point:Point;

function Axis2D(pwidth:Number, pheight:Number, pmc:LooseMovieClip) {
this.mWidth = pmc.getWidth() / pwidth;
this.mHeight = pmc.getHeight() / pheight;
this.cx = pmc.getWidth() / 2.0;
this.cy = pmc.getHeight() / 2.0;
this.mc=pmc;
this.point=new Point(0.0, 0.0);
}

public function plot (px:Number, py:Number) :Void{
var x:Number = this.mWidth * px;
var y:Number = this.mHeight * py;

//trace ("ほんものx: " + px +" " + "ほんものy: " + py );
//trace ("拡大x: " + x +" " + "拡大y: " + y );
//trace ("表示x: " + (x + this.cx)+" " + "表示y: " +(this.cy- y ));
this.point.moveTo(x + this.cx, this.cy - y);
this.mc.drawPoint(this.point);
}
}

-------- looselips.control.LooseMovieClipクラス:MovieClipのラッパークラス(継承したときの生成方法がわからなかった)


import looselips.shape.Shape;
import looselips.shape.Point;

// MovieClip を包含するクラス(継承するとどうなるのかわからんかった。)
class looselips.control.LooseMovieClip {

private var target_mc: MovieClip;
private var target_width: Number;
private var target_height: Number;

function LooseMovieClip (mcParent: MovieClip) {
// ヘルプのサンプルコードより
this.target_mc = mcParent.createEmptyMovieClip ("target_mc", mcParent.getNextHighestDepth());
this.target_mc.lineStyle(0,0xff0000, 100);
}

function stop():Void {
this.target_mc.stop();
}

function getMC() {
return this.target_mc;
}

// 幅を返す
function getWidth():Number {
return Stage.width;
}
// 高さを返す
function getHeight():Number {
return Stage.height;
}
// 点の色を変える
function setColor (offset: Number) : Void {
this.target_mc.lineStyle(1,offset, 100);
}
function drawPoint (p: Point): Void {
// 描画する先の範囲チェックはしなくていいのかな?現状はなし。
this.target_mc.moveTo(p.getX(), p.getY());
// lineTo を使って1ドットX軸をずらして点を描く
this.target_mc.lineTo(p.getX()+1, p.getY()+1);
}

// Shape が持つPath情報をもとにパスを描画する
// とりあえず、インタフェースを使ってみたかったので
function drawShape (shape: Shape): Void {
// パスの取得。パスは Point の集まり。
var path:Array = shape.getPath();
for (var i:Number = 0; i < path.length; i++) {
// 念のためキャスト
var p: Point = Point(path[i]);
if (i == 0) {
this.target_mc.moveTo(p.getX(), p.getY());
} else {
this.target_mc.lineTo(p.getX(), p.getY());
}
}
}
}

------ looselips.shape.Pointクラス:点を表現するクラス
// 点を表現するクラス
class looselips.shape.Point {
// プロパティ
private var x:Number;
private var y:Number;
//コンストラクタは一つだけらしい。。。
// function Point() {
// this.x = 0;
// this.y = 0;
//}
function Point(px:Number, py:Number) {
this.x = px;
this.y = py;
}
function equals(point:Point):Boolean {
if (this.x == point.getX() && this.y == point.getY()) {
return true;
} else {
return false;
}
}
function getX():Number {
return this.x;
}
function getY():Number {
return this.y;
}
function moveTo(px:Number, py:Number):Void {
this.x = px;
this.y = py;
}
}



Action Script で点

本来は、Action Script から Flash を使い始めるんじゃなくて、
Flash をひととり覚えてから必要な箇所で Action Script を使うんだろう。
そのほうが効率的だし。クリエイターのみなさんもそうなんだろう。
でも、自分しかり、いわゆるプログラマーの方々はやっぱり感覚として
逆になってしまうんではないかな。だから、Flash の参考書ってなかなか
理解しきれないというか...。(自分の頭が硬いというか...。)

今日は、画面に点を打ってみる。

まずは、オンラインヘルプ。最初に検索したのは、ファイル。
どういう単位でスクリプトを作るかわからなかったから。
驚きました。ActionScript ってクラスの概念がある!インタフェースも!グッド!
しかもクラスパスまで設定するとこもある。(クラスで検索すると出てきます。)
フォルダもパッケージにマッピングされるらしい。
いやー、ちょっと落ち着きました。とりあえず、Java 知ってる人はなんとかなるかも。

どうやってクラスに対応するファイルを作ればよいかは、ヘルプの
「クラスファイルを作成するには」に発見。(以下ヘルプを要約)
1. ファイル->新規で ActionScript
2. クラスの中身を書いて保存
3. ファイル->新規で Flash ドキュメント
4. タイムラインのフレームにアクションとしてクラスをimportして処理を書く
(Flash ドキュメントを開いたときのレイヤー1って書いてあるレーンで右クリック、アクション)

さて、準備が整ったので、点を描くことを考えてみる。
やはりヘルプで、検索したけど「点」は多すぎてむり。
「線」で検索すると、「線を描画するには」というトピック発見。
いろいろ探したけど、「点」がなさそうなので、「線」を応用することに。
どうやら、こんな感じで描けるらしい。

this.createEmptyMovieClip("line_mc", 10);
line_mc.lineStyle(1, 0x000000, 100);
line_mc.moveTo(0, 0);
line_mc.lineTo(200, 100);


最後の2行で線が引けるみたいだから、ここをちょっと変更すれば点になるかな。
迷ったのは最初の一行。 10 っていうのが何かわからん。
MovieClip ていうやつで、さらに調べてみると、これは深度っていうやつらしい。
現在の設定を取りたい場合は、this.getNextHighestDepth() で取れると書いてあったのでこれを使う。

MovieClip に点を描くメソッドがないから、ちょっと拡張してみようと思う。
まずは、Point クラスの Point.as (looselips.shape という名前のフォルダを作成してその下に作成)
flash.geom.Point クラスってのもあったけど、Flash Lite 2.x ではなさげ。
携帯に出したいのとActionScript のクラスの勉強がてら作成してみた。

// 点を表現するクラス
class looselips.shape.Point {
// プロパティ
private var x:Number;
private var y:Number;
//コンストラクタは一つだけらしい。。。
// function Point() {
// this.x = 0;
// this.y = 0;
//}
function Point(px:Number, py:Number) {
this.x = px;
this.y = py;
}

function equals(point:Point):Boolean {
if (this.x == point.getX() && this.y == point.getY()) {
return true;
} else {
return false;
}
}

function getX():Number {
return this.x;
}

function getY():Number {
return this.y;
}

function moveTo(px:Number, py:Number):Void {
this.x = px;
this.y = py;
}

}

これでとりあえず、座標が表現できる。
このPointクラスを与えることで描画をしてくれる LooseMovieClipクラスはこんな感じ。
(import looselips.shape.Shape は、インタフェースの動きを確認したかったのでついてますが、Point のためには使ってない。)

import looselips.shape.Shape;
import looselips.shape.Point;

// MovieClip を包含するクラス(継承するとどうなるのかわからんかった。)
class looselips.control.LooseMovieClip {
 
private var mc: MovieClip;
 
function LooseMovieClip (mcParent: MovieClip) {
// ヘルプのサンプルコードより
this.mc = mcParent.createEmptyMovieClip ("mc", mcParent.getNextHighestDepth());
this.mc.lineStyle(1,0x000000, 100);
}

// 点の色を変える
function setColor (offset: Number) : Void {
this.mc.lineStyle(1,offset, 100);
}

function drawPoint (p: Point): Void {
// 描画する先の範囲チェックはしなくていいのかな?現状はなし。
this.mc.moveTo(p.getX(), p.getY());
// lineTo を使って1ドットX軸をずらして点を描く
this.mc.lineTo(p.getX()+1, p.getY()+1);
}
 
// Shape が持つPath情報をもとにパスを描画する
// とりあえず、インタフェースを使ってみたかったので
function drawShape (shape: Shape): Void {
// パスの取得。パスは Point の集まり。
var path:Array = shape.getPath();
for (var i:Number = 0; i < path.length; i++) {
// 念のためキャスト
var p: Point = Point(path[i]);
if (i == 0) {
this.mc.moveTo(p.getX(), p.getY());
} else {
this.mc.lineTo(p.getX(), p.getY());
}
}
}
}

念のため、Shape インタフェース。

interface looselips.shape.Shape {
// Array には looselips.shape.Point が入る
function getPath():Array;
}

さて、この3つの as ファイルの置き場所として、作業フォルダの下に
looselips\shape\, looselips\control\フォルダを作成して、
Point.as と Shape.as を shape の下に、LooseMovieClip.as を control の下に置く。

作業フォルダに、dot.fla という名前で空の Flash ドキュメントを作成。
(もちろん、Script は Flash Lite 2.0, デバイスは 一般携帯電話)
メインのタイムフレームで右クリックアクションで、以下のActionScript を記述。
全部で計4つの点を描画。(最後の三つは斜めに1ドットずつ)

import looselips.shape.Shape;
import looselips.shape.Point;
import looselips.control.LooseMovieClip;

var p:Point = new Point(30, 30);
var mc:LooseMovieClip = new LooseMovieClip(this);
mc.setColor(0xff0000);
mc.drawPoint(p);
p.moveTo(50, 50);
mc.drawPoint(p);
p.moveTo(51, 51);
mc.drawPoint(p);
p.moveTo(52, 52);
mc.drawPoint(p);

(そういえば、ステージ(画面)サイズ、勝手に縮小されて携帯には表示されることが判明。)
制御->ムービープレビューはこんな感じでした。(携帯上でもかろうじて赤い点らしきものが...。)


dot


相変わらず、寂しい状況....。
せっかくだから、なんかグラフでも描くやつを作ってみようかな。
次は点でグラフ。
そうか、グラフが描ければ、携帯らしく時間と電波状況とか電池状況を
グラフにできるかな。

わかったこと:
- ほぼ Java と同様な記述ができそう
- ヘルプが結構使えた
- 画面サイズはどうやら勝手に縮小されるらしい

課題:
- 動的に点を描きつづけるようなスクリプトを作成してみる
- 電波、電池の状態をどう取得するのか
- MovieClip を継承した場合どうインスタンスを作る?(プライオリティ低)



Clear as mud ...

ファイルメニューで新規、Flashドキュメント。


名前は helloworld.fla にして、パブリッシュの設定ボタンで、Flashタブ、バージョンを Flash Lite 2.0。
そんで、デバイスの設定ボタンで、一般、Flash Lite 2.0、一般携帯電話を追加。
(ドキュメントどおりにやっております。)

テキストツール?(←これで迷った...。情けない。)で、なんか文字を書けと。
ええ、ええ、白いステージ(どうやら、背景のことらしい)に文字書きました。

で、保存。制御メニューでムービープレビューっと。
あー、なんか出た。今書いた字と携帯の入力ボタンが。



clear as mud

えーっと、それで、元に戻って、ファイルメニュー、書き出し、ムービーの書き出し?
helloworld.swf と。これを携帯のアドレスにメールして、受信で表示。
























はい、確かに出ますね。そりゃ、動きもなんもしないけど。フグ


でもなんかしっくりこないな。最初からプログラミングできないのかな。
たとえば、ステージにテキストを書くんではなくて、プログラムからステージに
書き出すとか、プログラムから画面に点を打つとか....。


そうだなぁ。点打ってみたいな。逆に点を打っておいてそれをプログラムから
動かせるのか?あー、そういう使い方なのか、Flash ってもしかして。

でも、やっぱり、昔のプロッターみたいに、プログラムから点打ってみたいな。

(時代に逆行してる?)


現時点での課題:
- プログラムから点をうつ
- 打ってある点を動かせる?(今回の文字を動かすとか。)
- 画面解像度って 811SH (480x600) に変えられないの?


まずは、点からか...。Action Script 調べます。

いや、とりあえず、今日はもう呑む。