note.x

attitude Control(要:FlashPlayer9)

haramakotoさんのエントリー「方向ベクトルからMatrix回転への変換」を読んで。

ある方向に形状の任意軸ベクトルを揃えることで、姿勢を制御するというお題。
haramakotoさんはクォータニオンで挑戦されているようなので、オレは行列でやってみた。正直言ってオレもよくわかってないんだけど、なんとなく上手くいったので書いておく。

例えば、ある形状のY軸を適当な方向に向けたい場合の手順は

  1. 姿勢をコントロールしたいオブジェクトのY軸ベクトルをゲット — [1]元の姿勢
  2. ゲットしたY軸のベクトルに目標座標のベクトルを加算して新しいベクトルをゲット — [2]変形後の姿勢
  3. [1]と[2]の外積をとって、法線ベクトル(回転軸)をゲット — [3]
  4. 二つのベクトルの角度をゲット — [4]
  5. [3]のベクトルと、[4]の角度でもって回転行列を作る
  6. 姿勢をコントロールしたいオブジェクトのtransformと合成

って感じで上手くいった。

姿勢をコントロールしたいオブジェクトのY軸ベクトルは、DisplayObject3Dのtransformプロパティを参照すれば、調べることができる。

n11 n12 n13 n14
n21 n22 n23 n24
n31 n32 n33 n34
n41 n42 n43 n44

transformプロパティ(Matrix3D)の各列、
|n11,n21,n31|、|n12,n22,n32|、|n13,n23,n33|は、形状のX,Y,Z軸の単位ベクトルを示してる。なので、ある形状Obj(DisplayObject3D)の各軸のベクトルは、

X軸:Xvec:Number3D = new Number3D(Obj.transform.n11, Obj.transform.n21, Obj.transform.n31);
Y軸:Yvec:Number3D = new Number3D(Obj.transform.n12, Obj.transform.n22, Obj.transform.n32);
Z軸:Zvec:Number3D = new Number3D(Obj.transform.n13, Obj.transform.n23, Obj.transform.n33);

として取り出すことができる。これで、ある時点での形状の姿勢がわかる。ここまではyamasvさんの「回転行列の列ベクトル」の解説が詳しい。

「ゲットしたY軸のベクトルに目標座標のベクトルを加算」ってのは、自分も理解できてない。感覚的にこうじゃねーの?とかいうドンブリ勘定実装したら上手くいっちゃったので説明できない。わっはっは。…申し訳ございません。最初は目標座標のベクトルを加算せず、目標座標のベクトルをそのまま使ったんだけど、それだとリボン状にPlaneが並ばなかった。

残りの手順は、[Papervision3D2.0] Quaternionと同じ要領で、最終的に姿勢をコントロールするコードは以下のような感じになった。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//ある方向に向けたい軸
var StartVec:Number3D = new Number3D( obj.transform.n12, obj.transform.n22, obj.transform.n32 );
 
//目標になる座標
var TargetPoint:Number3D = new Number3D( 目標のX座標, 目標のY座標, 目標のZ座標 );
 
//元のベクトルを揃わせたい最終的なベクトル
var EndVec:Number3D = Number3D.add( StartVec, TargetPoint );
StartVec.normalize();
EndVec.normalize();
 
//2つのベクトルの法線ベクトル(回転軸)
var vcross:Number3D = Number3D.cross( EndVec, StartVec );
vcross.normalize();
//2つのベクトルから回転角度(ラジアン)算出
var rot:Number = Math.acos(Number3D.dot( StartVec, EndVec));
 
//ベクトルを合わせるための回転軸と角度から回転行列作成
var mat:Matrix3D = Matrix3D.rotationMatrix(vcross.x, vcross.y, vcross.z, rot);
 
//行列を適用
obj.transform = Matrix3D.multiply3x3(mat, obj.transform);

あと、wonderflに上がってたharamakotoさんのコードをforkして、行列での姿勢制御版を作ってみた。
ここらへん、ちゃんと理解したいなぁ。


BezierPatch Test(要:FlashPlayer9)

いわゆるサブサーフェス(Subdivision Surface)っぽく、いくつかのコントロールポイントの座標を元にメッシュを定義するクラス BezierPatch 。Away3DのコミッターGreg Caldwell氏のblogで、チュートリアルとデータ定義用簡易エディタ「Patch Explorer」が公開されてて、やっと使い方が解った。
Away3D Bezier Patch Tutorial

エディタ無しじゃ狙った形作るのキビしすぎだと思う。3Dアプリケーションでサブサーフェスをポリゴン化せずにインポートできるようにしようとしてるのかなぁ。

上記のデモは、16個のコントロールポイント(黄色い四角)でメッシュを定義して、それを鏡面コピーしたもので構成。コントロールポイントを適当に動かすことでメッシュが変形する。有機的に変形できるのが強みですな。


Paint to DAE mash(要:FlashPlayer9)

WakaZさんのエントリー「PV3D | DAEファイルの読込み」

DAEのオブジェクトにはVirtualMouseは効かないようで(あたりまえ)・・・。

を読んで。

これは確かに解りにくいなー。
例えば、

var obj_dae:DAE = new DAE();
obj_dae.load("hoge.dae");
scene.addChild(obj_dae);
obj_dae.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, mouseClick);

だとイベントが取れない。なんでか?

PV3Dとかだと、3Dアプリケーションで作った形状データを取り込む目的でCollada形式を使ってるけど、本来、ColladaってシーングラフをまるごとXMLで記述しようっていうフォーマット(だと思う)なので、メッシュ以外にもカメラとかライトなど、3Dアプリ上で定義した全要素を定義できる。いってみれば、

var daeObj:DAE = new DAE();
daeObj.load("hoge.dae");

で生成されたdaeObjってPV3Dのシーンみたいなもんで、ツリー構造なDisplayObject3Dの塊。なので、daeObj内にある目的のメッシュを明示的に参照しないとダメなのですな。あと、InteractiveScene3DEventを有効にする場合のお約束、マテリアルのinteractiveプロパティをtrueにするのを忘れずに。

具体的には、

vMouse = viewport.interactiveSceneManager.virtualMouse;
Mouse3D.enabled = true;

-- 中略 --

obj_dae = new DAE();
obj_dae.load( "hoge.dae" );
obj_dae.addEventListener( FileLoadEvent.LOAD_COMPLETE, compCollada );
scene.addChild(obj_dae);

function compCollada(event:Event):void
{
    //対象となるマテリアルのinteractiveプロパティをtrueに
    obj_dae.materials.getMaterialByName("ターゲットのマテリアル").interactive = true;

    //対象メッシュの参照
    var dae_rootNode:DisplayObject3D = obj_dae.getChildByName("COLLADA_Scene");
    var targetMesh:DisplayObject3D = dae_rootNode.getChildByName("ターゲットの名前");

    //対象メッシュにリスナ設定
    targetMesh.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, mouseClick);
}

private function mouseClick(e:InteractiveScene3DEvent):void
{
    var axis:Point = new Point(vMouse.x, vMouse.y);
    trace(axis.x + " / " + axis.y);
}

って感じで。

上記で言うところの obj_dae内部では「COLLADA_Scene」って名前のDisplayObject3Dをルートとした階層構造(シーングラフ)ができあがってるってのを意識することが重要と。

getChildByNameで指定する名前は、ルートである「COLLADA_Scene」は固定、COLLADA_Scene以下の各メッシュは3Dアプリで付けた名前がエクスポートされてるはず。自分で作ったdaeファイルじゃない場合は、

var dae_rootNode:DisplayObject3D = obj_dae.getChildByName("COLLADA_Scene");
dae_rootNode.childrenList();

で一覧が取れる。

ということで出来上がったのが上記のデモ。
DAEでもプリミティブと同じようにペイントできるよ。



出ました。

4月号に引き続き、AS3用3Dライブラリの記事を担当しました。
今回は、PV3Dを例に基本的な扱い方を解説してます。とりあえず、プリミティブ表示してみようって感じッスね。

今回も興味のある方、手にとってみてください。よろしくですー。


PV3D専用だと思ってあんまりチェックしてなかったJigLibFlashが、PV3D以外のエンジンにも対応(現在、PV3D、Away3D、Sandyに対応)してくれたので、Away3Dでもそのまま使えるようになった。JigLib Flash for Away3D おつかれさま。

これまでのバージョンだと、表示用のメッシュにDisplayObject3Dをダイレクトに指定する形になっていたのが、ISkin3D っていうインターフェースをカマせることで、組み合わせたいエンジンのメッシュを指定できるようになった。
Away3Dを使うなら、

var box:JBox = new JBox(new Away3dMesh(daeobj),100,200,50);
※daeobjは、外部から読み込んだColladaモデルと仮定

って感じ。ちなみに、Away3dMeshクラスの実装は、Mesh オブジェクトを指定するようになってたけど、オレ的に不便なので、Object3D を指定するようにして使ってる。

あと、

var physics:Away3DPhysics = new Away3DPhysics(viewport, 1);
var sphere:RigidBody = physics.createSphere({radius:30, segmentsW:6, segmentsH:6});

なんて感じで、プリミティブ形状については表示用メッシュと物理演算用メッシュを一度に定義できるメソッドが用意された。引数の与え方がPV3DとAway3Dで異なる部分は吸収してくれないので、それぞれのエンジンのルールに従うと。

このあたり、ある程度の説明が google code のプロジェクトページ内Wikiに記載されているので参考になる。
New_API_Very_Short_Tutorial

また、svnリポジトリにもPV3D、Away3Dそれぞれのサンプルがある。これまでとシンタックスが大分異なる(physics.Integrate(0.1); → physics.step(); とか)ので、早めに乗り換えるが吉かと。
コリジョン用の形状にカプセル型が追加されたのがウレシげだなぁ。元祖C++用のJigLibには、trianglemesh や heightmap なんてのもあるので、だんだん移植されるのかも。

早速インチキドライブシミュレータもどきに組み込んでみたんだけど、パラメータ共通なのに挙動が同じにならない。けど、なんか新しいほうがパラメータが正しく反映されてるような気がする。