note.x

Hemisphere(半球)クラスを使う

ついこの間Torus.asが公開されたばっかりなのに、PV3D Wikiに半球を生成するクラスHemisphere.asが掲載されてた。なんだか気合い入ってんなー。Sphere.asと同じパラメータ指定で半球が生成されるってだけなので、扱いはとても簡単だった。

obj_Hemisphere.swf(要:Flashplayer9)

3D空間内で空とかを表現することを想定してると思う(地味に便利)んだけど、そのせいかデフォルトだとポリゴンの法線が半球の内側を向いているので、状況によっては

hemisphereObj.material.doubleSided = true;

みたいな感じで、リャンメンポリゴンにするか、

hemisphereObj.material.opposite = true;

で、法線を逆向きに指定してやる必要があると。


Torus(ドーナツ形状)クラスを使う

PV3D Wikiにドーナツ形状を生成するクラスTorus.asが掲載されていたので使ってみる。

クラスを使えるようにするには、SorcePathを通したフォルダ内に「com」-「suite75」-「papervision3d」-「objects」という階層でフォルダを新規に作成し、そこにTorus.asを設置すればいいんだけど、面倒なので、Torus.asの

package com.suite75.papervision3d.objects

というパッケージ宣言を

package org.papervision3d.objects

に書き換えて、他の純正クラスと同じ場所に置いた。

var torus:Torus = new Torus( マテリアル, 外側半径, 内側半径, 旋回の分割数, 旋回する平面の頂点数 );

ってな感じでドーナツ型のオブジェクトが生成できると。

obj_Torus.swf(要:Flashplayer9)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
package
{
    import flash.display.*;
    import flash.events.*;
 
    import org.papervision3d.scenes.*;
    import org.papervision3d.objects.*;
    import org.papervision3d.cameras.*;
    import org.papervision3d.materials.*;
 
    [SWF(backgroundColor=0x000000)]
 
    public class obj_Torus extends Sprite
    {
        // _______________________________________________________________________
        //                                                                  vars3D
        private var container : Sprite;
        private var scene     : Scene3D;
        private var camera    : Camera3D;
        private var rootNode  : DisplayObject3D;
 
        private var torusObj  : Torus;
        private var largeRadius : int = 150;    //外周半径
        private var smallRadius : int = 50;     //内周半径
        private var segmentW  : int = 12;       //旋回の分割数
        private var segmentH  : int = 6;        //断面の頂点数
 
        private var valx      : Number = 0;
        private var valy      : Number = 0;
 
        // _______________________________________________________________________
        //                                                             Constructor
        public function obj_Torus():void
        {
            stage.frameRate = 60;
            stage.quality   = "MEDIUM";
            stage.scaleMode = "noScale";
            stage.align = StageAlign.TOP_LEFT;
            this.addEventListener(Event.ENTER_FRAME, loop3D);
            this.stage.addEventListener(Event.RESIZE, onStageResize);
 
            init3D();
        }
 
        // _______________________________________________________________________
        //                                                                  Init3D
        private function init3D():void
        {
            //コンテナ生成
            this.container = new Sprite();
            addChild(this.container);
            this.container.x = this.stage.stageWidth  / 2;
            this.container.y = this.stage.stageHeight / 2;
 
            //シーン生成
            scene = new Scene3D( container );
 
            //カメラ設定
            camera = new Camera3D();
            camera.z = largeRadius*2;
            camera.focus = 500;
            camera.zoom = 1;
 
            //rootNode生成
            rootNode = scene.addChild( new DisplayObject3D( "rootNode" ) );
 
            //マテリアル設定
            var colorMaterial:ColorMaterial = new ColorMaterial( 0x0099cc, 1);
            var wireMaterial:WireframeMaterial = new WireframeMaterial( 0x00ccff );
            var compoMaterial:CompositeMaterial = new CompositeMaterial();
            compoMaterial.addMaterial(colorMaterial);
            compoMaterial.addMaterial(wireMaterial);
 
            //Torusオブジェクト生成
            torusObj = new Torus( compoMaterial, largeRadius, smallRadius, segmentW, segmentH)
            rootNode.addChild( torusObj );
        }
 
        // _______________________________________________________________________
        //                                                                    Loop
        private function loop3D( event:Event ):void
        {
            //マウス座標でオブジェクトを回転
            valx += this.container.mouseX / 50;
            valy += this.container.mouseY / 50;
            torusObj.rotationY = valx;
            torusObj.rotationX = valy;
 
            //レンダリング
            this.scene.renderCamera( camera );
        }
 
        // _______________________________________________________________________
        //                                                           onStageResize
        private function onStageResize(event:Event):void
        {
            this.container.x = this.stage.stageWidth  / 2;
            this.container.y = this.stage.stageHeight / 2;
        }
    }
}

これを参考にすれば3Dアプリでワイングラスとかを作るのに使う、いわゆる「旋回」による形状生成を可能にするクラスが作れそう。時間があるとき作ってみよう。


法線マップ(NormalMap)に挑んでみる

FACEsの法線マッピングに関するエントリを見て唸らされたので、PV3Dでも出来ないもんかなと思ってトライしてみた。いつでも無謀。

まずは、法線マップそのものを用意しなくちゃならないのでBlenderで作ろうと調べてみたら、こんなチュートリアルを発見。けど、モデリングするの面倒だし、Blender内蔵の猿のオブジェクト使うなら結局同じものができ上がるので、チュートリアル記事の完成画像を拝借した。Windows環境ならNvidia’s normal map toolsっていうPhotoshopプラグインがある(PaintShop Proでもイケる模様)ので、モデリングしなくても法線マップが作れる。ガックリなことにOSX版は無いので、GIMPのNormalMapプラグインを使うのがいいのかなぁ、試してないけど。< br>

用意した法線マップを前述のエントリにあるColorMatrixFilterで変換(PV3Dには光源が無いので、カメラのベクトルを光源替わりに使った)して、テクスチャ用のビットマップに乗算(MULTIPLY)し、さらにOVERLAYで重ねてBitmapMaterialに割り当ててあるビットマップオブジェクトに毎フレームdraw()したのがこれ。

normalmap.swf(要:Flashplayer9)

なんか違和感があるな、膨らんでんのか凹んでんのかどっち付かずな感じ。適用したColorMatrixFilterは、光源に垂直な面が真っ黒になるということだったんで、適当にイジくり回したんだけどそれが原因だろうな。光源に垂直なほど(露出オーバーしない程度に)明るくなるにはどうすりゃいいのかなぁ。道は険しいのぉ。


ベクトルと行列の勉強の為に、PV3Dにおいてベクトルを扱うクラス「Number3D」と行列を扱うクラス「Matrix3D」を使って球体(Sphere)の頂点をperlinNoiseで歪ませてみたら、全然全くこれっぽっちも狙い通りの結果にはならなかったんだけど(ダメじゃん)なんだか面白い形が生成されるので失敗作だけど晒してみる。

Terrain_sphere.swf(要:Flashplayer9)
※ヘンなオブジェをクリックすると、再計算してヘンなオブジェを再生成。

ちょっとデコボコした地球儀みたいになるはずだったんだけどなぁ(笑)
Number3DとMatrix3Dについては、Codin’In The Free Worldさんの、このエントリこのエントリが大変参考になりました。ゴチになりました。


いわゆるIK(インバースキネマティクス)ってどのくらい難易度高いのかなぁと思って、無謀は承知でトライしてみた。インバースキネマティクスのアルゴリズムに関しては、これっぽっちも解ってないので、とりあえずLightWave触ってた時の手順と印象で探りを入れてみる。

形状のペアレント
LightWaveでIKするには、目的のオブジェクトをペアレント(親子関係)する必要があるので、ひとまずこれをPV3Dでやってみる…といっても単に階層上にaddChild()すればペアレント終了。例えば、Arm01、Arm02、Arm03という3つの形状で階層を作るなら、

rootNode = scene.addChild( new DisplayObject3D( "rootNode" ) );
rootNode.addChild( Arm01,"Arm01" );
Arm01.addChild( Arm02,"Arm02" );
Arm02.addChild( Arm03,"Arm03" );

ってな感じで、入れ子にaddChild()しておけば親オブジェクトの移動や変形に子オブジェクトが追随するペアレント指定が完了。ここまではムービークリップの概念そのまんまなので、さすがのオレでもすぐに出来た。

IKっぽいことをしてみる
「逆運動学」とかで検索して、それっぽいサイトをいくつか見て回った結果、要は「ゴールオブジェクトの位置で、他のオブジェクトの姿勢が決まる」ってことが満たされればいいんじゃね?いいんでしょ?イイ!という凡人なんてこんなもんですよ的発想を下敷きに、なんとかしてベクトルだの行列だのを駆使しなくてもできる方法が無いもんかと考えた揚げ句に閃いたのが、ゴールオブジェクト以外のオブジェクトを全部lookAt()使ってゴールオブジェクトの方向向ければいいんじゃね?というアホみたいな発想。さっきの例でいうと「Arm03」をゴールオブジェクトにするとして、

Arm02.lookAt(Arm03);
Arm01.lookAt(Arm03);

こんな感じ。

んで、実際にやってみたのがこれ。※要:Flashplayer9
黄色いパーツがゴールオブジェクト。

び、微妙。なんかソレっぽく動いたけど、どうにもインチキくさい。ていうかほぼ間違いなくインチキIKだろこれ。どっちにしてもこれじゃ関節ごとの動きの制限とかどうすればいいか全然わかんないし、やっぱりちゃんと自分でベクトルとか使ってやらないとダメっぽいなぁ。なんとなく、lookAt()メソッドがやってる事にヒントがありそうな気がするので、そのへんから攻めてみよう。


Terrain.swf(要:flashPlayer9)

頂点の制御方法がわかったんで、右のようなハイトマップから地形を生成させてみた。perlinNoise使ってハイトマップ自体を生成するのも手だけど、それだと狙った地形にならないので、あらかじめ用意したハイトマップを使いたい場合の手段ということで。にしても重いなぁ。やっぱしこういう使い方はまだまだ無茶なのか。


カメラ制御の練習がてらに迷路を作ってみた。(要:flashplayer9)
意外と簡単で面白かった。が、もう少し高速化したいところ。


オブジェクト単位で変形したり移動したりは出来るようになったけど、頂点単位での変形とかはできないもんかなーと思って調べてみた。

Planeとかで作った形状の頂点データは、Vertex3DオブジェクトとしてDisplayObject3Dのgeometryプロパティに、要はGeometryObject3Dオブジェクトのvertices:Arrayとして格納されるみたいだ。なので、

for each(obj_vartices in planeObj.geometry.vertices){
    obj_vartices.x = 1;
    obj_vartices.y = 1;
    obj_vartices.z = 1;
}

とかやれば、形状を構成する全部の頂点を制御できると。よくわかんないけど、試しに作ってみた。

trans_plane.swf(要:flashPlayer9)

これを応用すれば、ディスプレースメントマップみたいなことになるのかしら。とりあえず頂点の制御方法がわかったので満足。


オブジェクトの大きさが変わる動きを付けようとしたら、scale系のプロパティが全部write-onlyだった。オドロキの仕様であります。んだったら、extraプロパティに現在のscaleを保持するユーザー定義プロパティ持たせて、scaleX,Y,Zにはひたすら書き込みだけするようにしたら動くんじゃね?という仮説を立てて実際やってみた。

Planeオブジェクトを拡大縮小(要:flashPlayer9)
※マウスボタン押しっ放しで縮小、放すと元に戻る

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package
{
    import flash.display.*;
    import flash.events.*;
 
    import org.papervision3d.scenes.*;
    import org.papervision3d.objects.*;
    import org.papervision3d.cameras.*;
    import org.papervision3d.materials.*;
 
    [SWF(backgroundColor=0x000000)]
 
    public class Scaleplane extends Sprite
    {
        // _______________________________________________________________________
        //                                                                  vars3D
        private var container : Sprite;
        private var scene     : Scene3D;
        private var camera    : Camera3D;
        private var rootNode  : DisplayObject3D;
 
        private var planeObj  : DisplayObject3D;
        private var planeObj2 : DisplayObject3D;
 
        private var planeSize : int = 300;      //Planeオブジェクト1辺の長さ
        private var segment   : int = 1;        //面の分割数
        private var material  : ColorMaterial;
 
        private var valx      : Number = 0;
        private var valy      : Number = 0;
 
        // _______________________________________________________________________
        //                                                             Constructor
        public function Scaleplane():void
        {
            stage.frameRate = 60;
            stage.quality   = "LOW";
            stage.scaleMode = "noScale";
            stage.align = StageAlign.TOP_LEFT;
 
            stage.addEventListener(Event.RESIZE, onStageResize);
            stage.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
            stage.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
            this.addEventListener(Event.ENTER_FRAME, loop3D);
            init3D();
        }
 
        // _______________________________________________________________________
        //                                                                  Init3D
        private function init3D():void
        {
            //コンテナ生成
            this.container = new Sprite();
            addChild(this.container);
            this.container.x = this.stage.stageWidth  / 2;
            this.container.y = this.stage.stageHeight / 2;
 
            //シーン生成
            scene = new Scene3D( container );
 
            //カメラ設定
            camera = new Camera3D();
            camera.z = -planeSize;
            camera.x = 200;
            camera.y = 400;
            camera.focus = 1000;
            camera.zoom = 0.8;
 
            //rootNode生成
            rootNode = scene.addChild( new DisplayObject3D( "rootNode" ) );
 
            //マテリアル設定
            var colorMaterial:ColorMaterial = new ColorMaterial( 0x0099cc, 1);
            var wireMaterial:WireframeMaterial = new WireframeMaterial( 0x00ccff );
            var compoMaterial:CompositeMaterial = new CompositeMaterial();
            compoMaterial.addMaterial(colorMaterial);
            compoMaterial.addMaterial(wireMaterial);
            compoMaterial.doubleSided = true;
 
            var colorMaterial2:ColorMaterial = new ColorMaterial( 0x003366, 1);
            var wireMaterial2:WireframeMaterial = new WireframeMaterial( 0x006699 );
            var compoMaterial2:CompositeMaterial = new CompositeMaterial();
            compoMaterial2.addMaterial(colorMaterial2);
            compoMaterial2.addMaterial(wireMaterial2);
            compoMaterial2.doubleSided = true;
 
            //Planeオブジェクト生成
            planeObj = rootNode.addChild( new Plane( compoMaterial, planeSize, planeSize, segment, segment) );
            planeObj.extra = {nscaleX:1,nscaleY:1,tscaleX:1,tscaleY:1}
 
            //Planeオブジェクト生成
            planeObj2 = rootNode.addChild( new Plane( compoMaterial2, 2000, 2000, 6, 6) );
            planeObj2.pitch(90);
            planeObj2.y = -300;
        }
 
        // _______________________________________________________________________
        //                                                                    Loop
        private function loop3D( event:Event ):void
        {
            //マウス座標でオブジェクトを回転
            valx += this.container.mouseX / 50;
            valy += this.container.mouseY / 50;
            planeObj.rotationY = valx;
            planeObj.rotationX = valy;
 
            planeObj.extra.nscaleX += (planeObj.extra.tscaleX - planeObj.extra.nscaleX) / 10;
            planeObj.extra.nscaleY += (planeObj.extra.tscaleY - planeObj.extra.nscaleY) / 10;
 
            //scaleをセット
            planeObj.scaleX = planeObj.extra.nscaleX;
            planeObj.scaleY = planeObj.extra.nscaleY;
 
            //レンダリング
            this.scene.renderCamera( camera );
        }
 
        //____________________________________________________________________
        //                                                    mouseDownHandler
        public function mouseDownHandler(event:MouseEvent):void
        {
            planeObj.extra.tscaleX = 0.1;
            planeObj.extra.tscaleY = 0.1;
        }
 
        private function mouseUpHandler(event:MouseEvent):void
        {
            planeObj.extra.tscaleX = 1;
            planeObj.extra.tscaleY = 1;
        }
 
        // _______________________________________________________________________
        //                                                           onStageResize
        private function onStageResize(event:Event):void
        {
            this.container.x = this.stage.stageWidth  / 2;
            this.container.y = this.stage.stageHeight / 2;
        }
    }
}

わーい出来た。
これでTweenerでも制御できるぞということでこれも実際やってみた。

オブジェクトをTweener経由で拡大縮小(要:flashPlayer9)
※マウスボタン押しっ放しで縮小、放すと元に戻る

そのうち仕様変更になりそうだけど、当面はこれで対処できそうだな。


現時点でのPV3Dは3Dエンジンとしてはいくつか問題を抱えてて、典型的なのがZ-sortの問題。2枚の板ポリゴンを交差するように配置した場合、前後関係がきちんと認識されなくて表示が破綻する。

ノーマルPV3D(Rev62)で板ポリ直交 ※要:flashPlayer9

板ポリゴンの分割数を細かくすればある程度ごまかせるけど、それでもやっぱり交差部分がおかしい。この辺りは速度とのトレードオフもあるだろうし個人的には納得してる。(SandyWireEngine3Dはどうなんだろ?)

ところが、この問題をなんとかしちゃってる強者Alexander Zadorozhny氏が、独自のパッチを当てたソースを公開してくれてるので試してみた。

Alexander版PV3Dで板ポリ直交 ※要:flashPlayer9
ソース(コンパイルにはAlexander Zadorozhny氏による拡張PV3Dが必要)

おー!ちゃんと表示されるじゃーん。と喜んだと同時に弱点も判明、doubleSidedは補正できないみたい。(ひょっとしたら出来るのかもしれないけど)なので、上のデモは合計4枚の板ポリを使ってそれっぽく見せかけてみた。これなら透過テクスチャ貼った板ポリで木が作れそうだ。

このAlexander板PV3D、古いリビジョンをベースに拡張してるっぽくて、これまでに作ったソースはコンパイルすら通らない。デモのソースを読みつつ脳が沸騰しそうになりながらどうにか動かしたものの、オレ程度のヘタレが踏み込んではいけない領域な気がした。…とか思ってたら、Alexander Zadorozhny氏がPV3Dの開発に参加するようなので手軽に使える形で上手くノウハウが組み込まれるといいな。