スポンサーサイト

上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

--.--.-- | | スポンサー広告

水槽を横から見たような描画

TOTOのトップページにあるフラッシュが面白いのでさっそくマネしてみました。だいぶ貧相なカンジになりましたケド。






水面を横切るようにマウスカーソルを上下させると波形を変えられマス(ステージを一回クリックする必要があるかもしれません)。

空気を通して見た画像に水を通して見た画像をちょっとずらして重ねておいて、水を通して見た画像にマスクをかけて、フレーム毎にマスクを変形(再描画)することでそれっぽく見せています。

水面の揺れは、バネで連結した(っぽい)点を16個並べてソイツラを曲線でつないで(実際は各点を制御点に、各点の中点を端点とする二次ベジェ曲線)います。各点は時間がたつと元の位置に戻るようにもとの位置ともバネ(っぽいナニカ)で接続しています。ただし、各点はy方向にのみ移動します。

今回のサンプルでは、上記の水面をあらわす点をWaterPointクラスとして定義しました。WaterPointクラスのソースコードはこんなカンジです。

package {
/**
* 水面上の点クラス。
*/
public class WaterPoint {
/** 接続オブジェクトの配列 */
private var _jointToArray:Array;
/** ばね定数 */
private var _k:Number;
/** 減衰定数 */
private var _a:Number;
/** x座標 */
private var _x:Number = 0;
/** y座標 */
private var _y:Number = 0;
/** y方向の速度 */
private var _vy:Number = 0;
/**
* コンストラクタ。
* @param k ばね定数
* @param a 減衰定数
* @param ix 初期x座標
* @param iy 初期y座標
*/
public function WaterPoint(k:Number, a:Number, ix:Number, iy:Number) {
_jointToArray = new Array();
_k = k;
_a = a;
_x = ix;
_y = iy;
}
/**
* 指定した物体とバネで接続します。
* @param target 接続する対象
*/
public function joint(target:Object):void {
_jointToArray.push(target);
}
/**
* 状態を更新します。
*/
public function update():void {
// かかっている力
var tensionY:Number = 0;
for each (var t:Object in _jointToArray) {
tensionY += _k * (t.y - _y);
}
// 減衰
_vy *= _a;
// 速度
_vy += tensionY;
}
/**
* 現在の状態に従って移動します。
*/
public function move():void {
// 速度ベクトルにしたがって移動
_y += _vy;
}
/**
* x座標を戻します。
* @return x座標
*/
public function get x():Number {
return _x;
}
/**
* y座標を戻します。
* @return y座標
*/
public function get y():Number {
return _y;
}
/**
* y座標を設定します。
* @param value y座標
*/
public function set y(value:Number):void {
_y = value;
}
}
}
WaterPointクラスは、コンストラクタでばね定数/減衰定数/初期座標をうけとります。またjointメソッドでばね接続する対象を追加します。jointメソッドに渡すパラメータはObject型で定義してますが、実際はx/yプロパティを持っている必要があります。

updateメソッドを実行すると、接続されたオブジェクトの距離やばね定数からその点の速度ベクトルを算出します。その後、moveメソッドを実行すると速度ベクトルにしたがって位置が更新されます。

で、サンプルのflaファイルのほーはというと、フレームアクションはこんなカンジです。
// 水面ポイント数
var pointCnt:uint = 16;
// 水槽
var wt:Sprite = in_water_mc;
// ポイント間の距離
var pointDist:Number = in_water_mc.width / (pointCnt - 1);
// ばね定数
var k:Number = 0.04;
// 減衰定数
var a:Number = 0.95;
// 水面ポイントの配列
var pointArray:Array = new Array();
// マスク
var msk:Sprite = new Sprite();
msk.x = wt.x;
msk.y = wt.y;
addChild(msk);
wt.mask = msk;
// 初期座標
var ix:Number = 0;
var iy:Number = wt.height / 2;
// 初期処理
init();
function init():void {
// ポイントの生成
for (var i:uint = 0; i < pointCnt; i++) {
var p:WaterPoint = new WaterPoint(k, a, ix + i * pointDist, iy);
if (i > 0) {
// ひとつ前の水面ポイントと連結
pointArray[i - 1].joint(p);
p.joint(pointArray[i - 1]);
}
// 初期位置と連結
p.joint({x:p.x, y:p.y});
// ポイントを保存
pointArray.push(p);
}
// 真ん中付近のポイントを動かす
var idx:uint = Math.floor(pointCnt / 2);
pointArray[idx].y = 0;
}
// フレームイベント
wt.addEventListener(Event.ENTER_FRAME, doEnterFrame);
function doEnterFrame(e:Event):void {
var nearest:WaterPoint = null;
if (mouseX > wt.x && mouseX < wt.x + wt.width &&
mouseY > wt.y && mouseY < wt.y + wt.height) {
nearest = getNearest(wt.mouseX, wt.mouseY);
}
var p:WaterPoint;
// ポイントの状態を更新します。
for each (p in pointArray) {
if (!nearest || p != nearest) {
p.update();
}
}
// ポイントを移動します。
for each (p in pointArray) {
if (nearest && p == nearest) {
p.y = wt.mouseY;
} else {
p.move();
}
}
// マスクを描画
drawMask();
}
// マスクを描画します。
function drawMask():void {
var g:Graphics = msk.graphics;
g.clear();
g.beginFill(0x0000FF);
g.moveTo(ix, wt.height);
var p:WaterPoint = pointArray[0];
var np:WaterPoint = pointArray[1];
g.lineTo(p.x, p.y);
for (var i:uint = 1; i < pointCnt - 1; i++) {
p = np;
np = pointArray[i + 1];
g.curveTo(p.x, p.y, (p.x + np.x) / 2, (p.y + np.y) / 2);
}
g.lineTo(np.x, np.y);
g.lineTo(np.x, wt.height);
g.lineTo(ix, wt.height);
g.endFill();
}
// 指定した座標に最も近い水面ポイントを戻します。
function getNearest(x:Number, y:Number):WaterPoint {
var minD2:Number = getDist2(pointArray[0], x, y);
var nearest:WaterPoint = pointArray[0];
for (var i:uint = 1; i < pointCnt; i++) {
var p:WaterPoint = pointArray[i];
var d2:Number = getDist2(p, x, y);
if (d2 < minD2) {
minD2 = d2;
nearest = p;
}
}
if (minD2 > pointDist * pointDist) {
nearest = null;
}
return nearest;
}
// 指定したポイントと座標との距離の二乗を戻します。
function getDist2(p:WaterPoint, x:Number, y:Number):Number {
var dx:Number = p.x - x;
var dy:Number = p.y - y;
return dx * dx + dy * dy;
}

in_water_mcは、水を通して見た画像をムービークリップにしたモノです。mskは、ソイツにかけるマスクを描画するためのSpriteオブジェクトです。

最初に、画像の半分の高さに等間隔にWaterPointオブジェクトを配置します。この際、ひとつ前に作ったオブジェクト(左隣のオブジェクト)と接続します。初期位置とも接続します。マスクを描画する際にこのオブジェクトのx/yプロパティを使用するので、生成したオブジェクトは配列に入れてます。

ENTER_FRAMEイベントでは、配列に保存したWaterPointオブジェクトの状態を更新(速度ベクトルを算出)して、移動します。そして、移動後の座標を使ってdrawMaskメソッドでマスクを再描画します。

ただし、マウスカーソルがいずれかのWaterPointオブジェクトに近くにあるときは、そのWaterPointオブジェクトはマウスカーソルにくっついて移動するようにしています。

スポンサーサイト

テーマ:Flash - ジャンル:コンピュータ

2008.03.24 | | Comments(0) | Trackback(0) | Flash CS3

グループにわける

ランダムに配置した点を7つのグループにわけるサンプルです。






各点の色が、その点が所属するグループをあらわしています。

「再配置」ボタンを押すと、ランダムな座標に点が300個配置されます。この時点では所属するグループはバラバラで、近くにある点が別のグループに属していてもおかまいなしデス。「グループ化」ボタンを押すと、点は位置が近いグループに配属しなおされます。グループ分けがすんだら背景色が各グループの領域として塗り分けられます。

グループ分けの仕方は、まず各グループごとに所属する点の重心位置を求めます。すべての点について、各グループの重心座標との距離を求めて、もし自分が所属するグループの重心座標よりも別のグループの重心座標の方が近かったらそっちのグループに異動します。コレを、異動する点がなくなるか、100回くりかえすまでやります。K平均法というらしいデス。たぶん。

グループ分けが終わったら、背景のビットマップの各点について一番近くの重心を持つグループの色を設定してます。

今回のサンプルでは、グループをあらわすClusterクラスを作成してフレームアクションでソレを利用するようにしました。
Clusterクラスのソースコードはこんなカンジです。

package {
/**
* クラスタ。
*/
public class Cluster {
/** 点の色 */
private var _color:uint;
/** 背景色 */
private var _backgroundColor:uint = 0;
/** クラスタに含まれる点のx座標の合計 */
private var _sumX:Number = 0;
/** クラスタに含まれる点のy座標の合計 */
private var _sumY:Number = 0;
/** クラスタに含まれる点の数 */
private var _cnt:uint = 0;
/**
* コンストラクタ。
* @param color 点の色
* @param backgroundColor 背景の色
*/
public function Cluster(color:uint, backgroundColor:uint) {
_color = color;
_backgroundColor = backgroundColor;
}
/**
* クラスタに点を追加します。
* @param x 追加する点のx座標
* @param y 追加する点のy座標
*/
public function addPoint(x:Number, y:Number):void {
_sumX += x;
_sumY += y;
_cnt++;
}
/**
* クラスタから指定した座標の点を削除します。
* @param x 削除する点のx座標
* @param y 削除する点のy座標
*/
public function removePoint(x:Number, y:Number):void {
_sumX -= x;
_sumY -= y;
_cnt--;
}
/**
* クラスタ内の点の重心座標を戻します。
* @return 重心の座標をプロパティに持つオブジェクト
*/
public function getCenter():Object {
if (_cnt == 0) {
return {x:0, y:0};
}
return {x:_sumX / _cnt, y:_sumY / _cnt};
}
/**
* 点の色を戻します。
* @return 点の色
*/
public function get color():uint {
return _color;
}
/**
* 背景色を戻します。
* @return 背景色
*/
public function get backgroundColor():uint {
return _backgroundColor;
}
/**
* 状態をリセットします。
*/
public function reset():void {
_sumX = 0;
_sumY = 0;
_cnt = 0;
}
}
}
Clusterクラスは、点の色と背景色以外に所属する点のx座標の合計値/y座標の合計値/点の数を持っています。各合計値を点の数で割ってやれば重心座標が算出できるという寸法デス。

コイツを利用するフレームアクションはこんなカンジです。
// 点の数
var cnt:uint = 300;
// 点の半径
var r:Number = 2;
// 計算回数の最大値
var threshold:uint = 100;
// グループの配列
var clusterArray:Array = [new Cluster(0xFF0000, 0xFFCCCC),
new Cluster(0x00FF00, 0xCCFFCC),
new Cluster(0x0000FF, 0xCCCCFF),
new Cluster(0xFFFF00, 0xFFFFCC),
new Cluster(0x00FFFF, 0xCCFFFF),
new Cluster(0xFF00FF, 0xFFCCFF),
new Cluster(0x000000, 0xCCCCCC)];
// グループ数
var cCnt:uint = clusterArray.length;
// 点の配列
var pointArray:Array;
// 計算回数
var t:uint = 0;
// 背景を描画する領域
var bd:BitmapData = new BitmapData(stage.stageWidth, stage.stageHeight);
var bg:Bitmap = new Bitmap(bd);
addChild(bg);
// 点を描画する領域
var pScreen:Sprite = new Sprite();
addChild(pScreen);
// 再配置ボタン
restart_mc.addEventListener(MouseEvent.CLICK,
function (e:MouseEvent):void {
init();
});
addChild(restart_mc);
// グループ化ボタン
cluster_mc.addEventListener(MouseEvent.CLICK,
function (e:MouseEvent):void {
t = 0;
pScreen.addEventListener(Event.ENTER_FRAME, doEnterFrame);
});
addChild(cluster_mc);
// 初期化処理
init();
function init():void {
// グループをリセット
for each (var c:Cluster in clusterArray) {
c.reset();
}
// 点を生成
pointArray = new Array();
for (var i:uint = 0; i < cnt; i++) {
var p:Object = new Object();
p.x = Math.random() * stage.stageWidth;
p.y = Math.random() * stage.stageHeight;
p.cluster = clusterArray[i % cCnt];
p.cluster.addPoint(p.x, p.y);
pointArray.push(p);
}
// 描画
drawAllPoints();
}

// すべての点を描画する
function drawAllPoints():void {
var g:Graphics = pScreen.graphics;
g.clear();
for each (var p:Object in pointArray) {
g.beginFill(p.cluster.color);
g.drawCircle(p.x, p.y, r);
g.endFill();
}
}

// 背景を描画する
function drawBackground():void {
var cArray:Array = getCenterArray();
bd.lock();
for (var iy:uint = 0; iy < bd.height; iy++) {
for (var ix:uint = 0; ix < bd.width; ix++) {
var c:Cluster = getNearCluster({x:ix, y:iy}, cArray);
bd.setPixel(ix, iy, c.backgroundColor);
}
}
bd.unlock();
}

// フレームイベントハンドラ
function doEnterFrame(e:Event):void {
var moved:Boolean = false;
// 重心の配列
var cArray = getCenterArray();
// 最も近い重心も持つグループへ点を移動
for (var i:uint = 0; i < cnt; i++) {
var p:Object = pointArray[i];
var c:Cluster = getNearCluster(p, cArray);
if (p.cluster != c) {
p.cluster.removePoint(p.x, p.y);
p.cluster = c;
p.cluster.addPoint(p.x, p.y);
moved = true;
}
}
// 再描画
drawAllPoints();
// 最大計算回数を越えたか、グループを移動した点がなければ終了
if (!moved || t > threshold) {
pScreen.removeEventListener(Event.ENTER_FRAME, doEnterFrame);
drawBackground();
return;
}
t++;
}

// 重心の配列を戻します。
function getCenterArray():Array {
var cArray = new Array();
for (var i:uint = 0; i < cCnt; i++) {
cArray.push(clusterArray[i].getCenter());
}
return cArray;
}

// 最も重心が近いグループを戻します。
function getNearCluster(p:Object, cArray:Array):Cluster {
var cIdx:uint = 0;
var min:Number = getDist2(p, cArray[0]);
for (var i:uint = 1; i < cCnt; i++) {
var d2:Number = getDist2(p, cArray[i]);
if (d2 < min) {
cIdx = i;
min = d2;
}
}
return clusterArray[cIdx];
}

// 2点間の距離の2乗を戻します。
function getDist2(p1:Object, p2:Object):Number {
var dx:Number = p1.x - p2.x;
var dy:Number = p1.y - p2.y;
return dx * dx + dy * dy;
}
clusterArrayは、Clusterオブジェクトを保持する配列デス。今回のサンプルでは、色の設定が違う7つのClusterオブジェクトを用意しています。

initメソッドでは、点をあらわすオブジェクトを生成しています。座標はランダムです。また、このオブジェクトは点が所属するグループもプロパティで持っています。ココではグループは均等に割り当てています。点の生成が終わったらdrawAllPointsメソッドで、点を描画しています。サンプルでは、半径2の円デス。

doEnterFrameメソッドは、グループ化ボタンが押されたときにEvent#ENTER_FRAMEとひもづけられるイベントハンドラです。initメソッドで生成したすべての点について、一番近い重心座標を探してもしヨソのグループの方が近ければソッチに異動させてます。異動する点がなくなったor設定された計算回数を越えたらイベントリスナを解除して、drawBackgroundメソッドで背景を描画しています。

drawBackgroundメソッドは、背景ビットマップの各点について一番近い重心座標をもつグループを探して、ソイツに設定された背景色でピクセルを塗っています。



【“グループにわける”の続きを読む】

テーマ:Flash - ジャンル:コンピュータ

2008.03.02 | | Comments(0) | Trackback(0) | Flash CS3

«  | HOME |  »

プロフィール

HundredthMonkey

Author:HundredthMonkey
プログラマ。

ブロとも申請フォーム

この人とブロともになる

メールフォーム

名前:
メール:
件名:
本文:

ブログ内検索


上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。