四角い地球の歩き方 その1「地箱を作ろう」

前回の記事の内容がめでたくシリーズモノに昇格いたしました。パチパチ。

きっかけは1年越しに唐突に…

前回、唐突に3Dの話をし始めたのには理由がありまして…もう1年以上も前なんですが、2chまとめサイトで、"もしも地球が正方形だったら"っていうタイトル*1のスレを見たのが、そもそものきっかけでした。
http://fsokuvip.blog101.fc2.com/blog-entry-368.html
これが非常に想像力を掻き立てられて、自分なりに「どうなるんだろうか?」と色々と想像を膨らませておりました。*2

この時は「面白いなぁ。」だけで終わったんですが、最近急に「これって、Flashとかで3D処理すれば、実際に立方体の地球の上を歩くシミュレーションが作れるんじゃね?」となぜだか急に思ってしまい、Flashの3D処理について唐突に調べ始めた…というのが事の顛末だったりします。

このシリーズの趣旨

Flashの3D処理の勉強も兼ねて、立方体の地球の上を自由に歩き回れるシミュレーションを作る事を目標に、備忘録として記事を書きながら、少しずつ実装を進めていきたいと思います。

教科書代わりのサイト

前回の記事のリンクにも入れたんですが、rectさんという方のnote.xというブログが激しく参考になるので、こちらを教科書代わりに利用させて頂こうと思っています。

note.xさんの記事を一通りざっと読ませて頂いて、PaperVision3D 2.0を使った方が良さそう、という事と、あとAway3DっていうPaperVision3Dからの派生プロダクトも良さげ、という事が分かりました。

今回のシリーズ記事では、基本的にはPaperVision3D 2.0を使って、場合によってAway3Dを使ってみたり、あとは物理エンジンにも手を出せたらいいなと思っています。

今回の目標

という訳で、ここからが本題。今回は、立方体の地球を表示させるところまでをやってみます。

ちなみに、"立方体の地球"といちいち書くのも面倒なので、以降は"地箱"と書いていこうと思います。

世界地図のテクスチャを用意する

地箱を作るには、基本的には世界地図の絵を立方体に貼り付ければいいだけなので、まずは世界地図のテクスチャを探します。

幸い、note.xさんでフリーで使える地球テクスチャについて言及されている記事があって、それによると、以下のサイトでダウンロードできるとの事。
Planet Earth Texture Maps
「サイズが1KBytesのものはフリー」と書いてあるので、ありがたく使わせて頂く事にします。

テクスチャを立方体に貼り付ける

テクスチャが手に入ったので、続いて、note.xさんの以下の記事を参考に、テクスチャを立方体にペタペタと貼り付けていきます。
http://blog.r3c7.net/?p=156
ただ、立方体の展開図は当然長方形ではない訳で、そのままじゃうまく貼れないので、以下のような展開図で貼り付ける事にしました。

当然、上面と下面はおかしな事になるけど、本筋ではないので今回はこれで妥協…。

図に示した前後左右上下は、camera.zの値を負に設定した時に手前に見えている面を前とした時の方向です。三角形ごとに付いている番号は、cube.geometry.faces[no]として面を取得した時に、どの番号がどの三角形に対応するかを記したものです。

ちなみに、note.xさんの記事で"Planeの頂点とUVとの関連づけ"の説明図通りにUVを設定すると向きがずれてしまったんですが、以下のような対応にしてやったらうまくいきました。

Planeでやると確かにnote.xさんの記事の通りになるので、どうやらPlaneとCubeで生成される三角形面の頂点の定義が異なるようですな。

今回の成果物

という事で、ひとまず地箱のベースの完成です。

直リンク→http://www.stellaqua.com/swf/EarthCube1.swf

せっかくなので、地軸を見えるようにして、地軸が傾いた状態で自転させてみました。

ソースコードはこの記事の最後に載せておきたいと思います。

次回は?

最終的には一人称視点で自由に歩き回れるようにしたいんですが、その前にまずは地箱の面上を歩き回った時に、面上の自分の座標から三次元座標への変換ができないといけないので、次回はその辺りに挑戦してみたいと思います。

それにしても、3Dの世界は、一歩足を踏み入れると泥沼の楽しさでヤバ過ぎる…。(笑)

ソースコード

package
{
    import flash.display.*;
    import flash.events.*;

    import org.papervision3d.view.*;
    import org.papervision3d.events.*;
    import org.papervision3d.core.math.*;
    import org.papervision3d.objects.*;
    import org.papervision3d.objects.primitives.*;
    import org.papervision3d.materials.*;
    import org.papervision3d.materials.utils.*;

    [SWF(backgroundColor=0x000000)]

    public class EarthCube1 extends BasicView
    {
        private var rootNode   : DisplayObject3D;
        private var objCube    : Cube
        private var ang        : Number = 0;

        [Embed(source='earthmap1k.jpg')] public var texBitmap:Class;

        public function EarthCube1()
        {
            stage.frameRate = 30;
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.quality = StageQuality.MEDIUM;

            //viewportの定義とカメラタイプ定義
            super (0,0,true,true,"Target");
            init3D();
        }

        public function init3D():void
        {
            rootNode = new DisplayObject3D();
            scene.addChild(rootNode);

            //カメラ設定
            camera.z = -1200;
            camera.y = 500;
            camera.fov = 30;

            //マテリアル設定
            var material:BitmapMaterial = new BitmapMaterial(new texBitmap().bitmapData, true);
            material.interactive = true;
            material.precise = true;
            var materials:MaterialsList = new MaterialsList({all : material});

            //Cubeオブジェクト生成
            objCube = new Cube(materials, 250, 250, 250, 1, 1, 1);
            rootNode.addChild(objCube);

            //テクスチャ変更(前面)
            objCube.geometry.faces[ 2].uv0 = new NumberUV(0.75, 0.25);
            objCube.geometry.faces[ 2].uv1 = new NumberUV(1.00, 0.25);
            objCube.geometry.faces[ 2].uv2 = new NumberUV(0.75, 0.75);
            objCube.geometry.faces[ 3].uv0 = new NumberUV(1.00, 0.25);
            objCube.geometry.faces[ 3].uv1 = new NumberUV(1.00, 0.75);
            objCube.geometry.faces[ 3].uv2 = new NumberUV(0.75, 0.75);

            //テクスチャ変更(後面)
            objCube.geometry.faces[ 0].uv0 = new NumberUV(0.25, 0.25);
            objCube.geometry.faces[ 0].uv1 = new NumberUV(0.50, 0.25);
            objCube.geometry.faces[ 0].uv2 = new NumberUV(0.25, 0.75);
            objCube.geometry.faces[ 1].uv0 = new NumberUV(0.50, 0.25);
            objCube.geometry.faces[ 1].uv1 = new NumberUV(0.50, 0.75);
            objCube.geometry.faces[ 1].uv2 = new NumberUV(0.25, 0.75);

            //テクスチャ変更(左面)
            objCube.geometry.faces[ 4].uv0 = new NumberUV(0.00, 0.25);
            objCube.geometry.faces[ 4].uv1 = new NumberUV(0.25, 0.25);
            objCube.geometry.faces[ 4].uv2 = new NumberUV(0.00, 0.75);
            objCube.geometry.faces[ 5].uv0 = new NumberUV(0.25, 0.25);
            objCube.geometry.faces[ 5].uv1 = new NumberUV(0.25, 0.75);
            objCube.geometry.faces[ 5].uv2 = new NumberUV(0.00, 0.75);

            //テクスチャ変更(右面)
            objCube.geometry.faces[ 6].uv0 = new NumberUV(0.50, 0.25);
            objCube.geometry.faces[ 6].uv1 = new NumberUV(0.75, 0.25);
            objCube.geometry.faces[ 6].uv2 = new NumberUV(0.50, 0.75);
            objCube.geometry.faces[ 7].uv0 = new NumberUV(0.75, 0.25);
            objCube.geometry.faces[ 7].uv1 = new NumberUV(0.75, 0.75);
            objCube.geometry.faces[ 7].uv2 = new NumberUV(0.50, 0.75);

            //テクスチャ変更(上面)
            objCube.geometry.faces[ 8].uv0 = new NumberUV(0.75, 0.75);
            objCube.geometry.faces[ 8].uv1 = new NumberUV(1.00, 0.75);
            objCube.geometry.faces[ 8].uv2 = new NumberUV(0.75, 1.00);
            objCube.geometry.faces[ 9].uv0 = new NumberUV(0.25, 1.00);
            objCube.geometry.faces[ 9].uv1 = new NumberUV(0.25, 0.75);
            objCube.geometry.faces[ 9].uv2 = new NumberUV(0.50, 0.75);

            //テクスチャ変更(下面)
            objCube.geometry.faces[10].uv0 = new NumberUV(1.00, 0.25);
            objCube.geometry.faces[10].uv1 = new NumberUV(0.75, 0.25);
            objCube.geometry.faces[10].uv2 = new NumberUV(1.00, 0.00);
            objCube.geometry.faces[11].uv0 = new NumberUV(0.50, 0.00);
            objCube.geometry.faces[11].uv1 = new NumberUV(0.50, 0.25);
            objCube.geometry.faces[11].uv2 = new NumberUV(0.25, 0.25);

            //地軸オブジェクト生成
            var objAxis:DisplayObject3D = drawAxis(0xff3333);
            rootNode.addChild(objAxis);

            //地軸を傾ける
            rootNode.rotationZ = -23.4;

            //レンダリング開始
            startRendering();
        }

        override protected function onRenderTick(event:Event=null):void
        {
            ang = ( ang - 1 ) % 360;
            objCube.rotationY = ang;

            super.onRenderTick(event);
        }

        private function drawAxis(color:uint):DisplayObject3D {
            //色設定
            var colorMaterial:ColorMaterial = new ColorMaterial(color, 1);
            colorMaterial.doubleSided = true;
            
            //円柱を作る
            var axis:DisplayObject3D = new Cylinder(colorMaterial, 2, 400);
            
            //円柱の先に球を作る
            var sphere1:DisplayObject3D = new Sphere(colorMaterial, 10);
            var sphere2:DisplayObject3D = new Sphere(colorMaterial, 10);
            sphere1.y = 200;
            sphere2.y = -200;
            
            var node:DisplayObject3D = new DisplayObject3D();
            node.addChild(axis);
            node.addChild(sphere1);
            node.addChild(sphere2);
            
            return node;
        }
    }
}

*1:スレを立てた人は立方体の事を言いたかったようなので、そもそもタイトルが変ではありますが…。

*2:こういう科学系の思考実験は大好きなんです。