プログラミング工房
TOP
Flex基本
開発環境 JavaScript連携 PHP連携1 PHP連携2(AMFPHP) ウィンドウ ボタンスキン 矩形スキン ローカルファイル(テキスト) F5等の対策 ローカルファイル(イメージ) ダウンロード、アップロード 1枚の画像のカラーを変更 時間のかかる計算処理 外部SWFの読込み
Flexで3D
Flexだけで3D Papervision3Dを使ってみる 3Dオブジェクト カメラ、前後判定の工夫しました 自由な形状を作成
Flexでクラス
Class1(白黒ゲームの盤) Class2(白黒ゲームのプレイヤー) Class3(プレイヤーを外部SWF)
PHP
共通関数1
Flexの作品
お問合せの説明 サンプルのソース表示の説明 分子構造の表示(PDBファイル) マンデルブロ集合の画像作成 swf参加型白黒ゲーム(Reversi) ストップウォッチ WEB素材
AIR
AIRを使ってみる ソースファイルのHTML変換を作る
etc.
マンデルブロ集合のギャラリー ジュリア集合のギャラリー wonderflを使ってみました お問合せ

Flexの作品-分子構造の表示(PDBファイル)

■2011.03.28:作成
■2011.04.04:原子をクリックしたときの処理を変更
サンプル サンプルのソース

分子構造の表示に関して

Flex4-SDKとPapervision3D(全て無料)を使ってPDBファイル(Protein Data Bank)を表示するプログラムを作成しました。
PDBとはhttp://www.pdb.org/pdb/home/home.do等にある分子構造のデータのようです。
但し、同HPのデータは大きすぎるのと「CONECT」データがない?のでこのプログラムで表示するのは不向きです。 他のHPにあるデータで試してみました。

対応可能なデータは
 原子数は200くらい?(1000個でも表示できますが、回転が重くなります。)
 原子(青いボール)は「ATOM」と「HETATM」を対応しています。
 結合線は「CONECT」を対応しています。
 一応、複数のHPのデータを読み込んでそれなりに表示しています。
 (分子に関しては素人の人間が作っているのでおかしところがあると思います、
 サンプルのベンゼン?は適当に作ったものです)
操作方法は
 スライダ:拡大、回転(2方向)を行います。
 原子(青い玉):マウスでピックするとその原子が中心に移動します。
 「ローカルPDB読み込み」ボタン:ローカルPC上のPDBファイルを読み込みます。

今回はサンプルのソース のZIPファイルにMolecule.swfを入れてありますので、使いたい人がいた場合、自由に使ってください。 (使ってくれる人がいるのかな?、出来れば「お問い合わせ」で連絡をくれるとうれしいです。)

HTML上での記述サンプルです。(IEとFirefox等の為に同じ記述が2回出ています。)
 width="560" height="300":表示エリアのサイズ
 ../sampl/Molecule/Molecule.swf:swfの相対パス
 pdbFile=test.pdb:立ち上げ時に読み込むサーバ上のPDBファイル名(省略可)
    HTMLファイルのあるフォルダに「data」フォルダを作ってその中に入れてください。
    省略時:「test.pdb」をファイル名とします。
 LocalLoad=ON:「ローカルPDB読み込み」ボタンの表示のON/OFF(省略可)
    省略時、ON以外は非表示
    <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
     width="560" height="300">
       <param name="movie" value="../sampl/Molecule/Molecule.swf" />
       <param name="FlashVars" value="pdbFile=test.pdb&LocalLoad=ON" />
       <!--[if !IE]>-->
       <object type="application/x-shockwave-flash" 
                 data="../sampl/Molecule/Molecule.swf"
                 width="560" height="300">
       <!--<![endif]-->
       <param name="FlashVars" value="pdbFile=test.pdb&LocalLoad=ON" />
       <!--[if !IE]>-->
       </object>
       <!--<![endif]-->
    </object>

プログラムの説明

プログラムの全体的構造
・全体はFlexで3D-カメラ、前後判定の工夫しました を参考にして下さい
 (但しbvG,bvBに分けてはいません。)
・Flex立ち上げ時のパラメータはFlex4基本-JavaScript連携
 -Flexの立ち上げ時のパラメータを参考にして下さい
・PDBファイル(サーバ)初期読み込みは Flex4基本-ダウンロード、アップロード
 -URLLoader - FileReference.saveでダウンロードのURLLoaderを参考にして下さい。
・ローカルPDBファイルの読み込みはFlex4基本-ローカルファイル(テキスト) を参考にして
 下さい。

読み込んだPDBファイルは以下の様にして表示用配列にセットします。
 Windowsの改行を考慮して行を分解します。
 行の頭が"ATOM"と"HETATM"を原子として判断して配列「arrAtom」にセット
 行の頭が"CONECT"を結合線として配列「arrConect」にセット
 (同じ結合線が始点側、終点側の2組あるようなので原子のIDでチェック)
 最大最小座標から表示の中心を求めます。
//読み込みデータ処理、表示処理==============================================
private function fncChkData(bytArra:ByteArray):void {
    var minX:Number = 0, maxX:Number = 0, minY:Number = 0;
    var maxY:Number = 0, minZ:Number = 0, maxZ:Number = 0;
    fncLoadRun(false);
    arrConect = new Array();
    arrAtom = new Array();
    arrObj = new Array();

    try {
        //改行の変更
        var strData:String = bytArra.toString();
        var arrDat:Array = strData.split("\r\n"); //Windowsの改行対応
        strData = arrDat.join("\n");
        arrDat = strData.split("\n"); //行の分解
        
        for (var i:int = 0; i < arrDat.length; i++) { //1行ごとの処理
            //原子
            if (arrDat[i].indexOf("ATOM") == 0 || 
                          arrDat[i].indexOf("HETATM") == 0) {
                arrAtom.push( {
                    Typ:StringUtil.trim(arrDat[i].substr( 0, 6 )),
                    nam:StringUtil.trim(arrDat[i].substr( 11, 5 )),
                    id:int(arrDat[i].substr( 6, 5 )),
                    x:Number(arrDat[i].substr( 30, 8 ))*100.0,
                    y:Number(arrDat[i].substr( 38, 8 ))*100.0,
                    z:Number(arrDat[i].substr( 46 ,8 ))*100.0
                });
                //原子の配置領域を求める
                var ii:Number = arrAtom.length - 1;
                if (arrAtom.length == 1) {
                    minX = arrAtom[ii].x;
                    maxX = arrAtom[ii].x;
                    minY = arrAtom[ii].y;
                    maxY = arrAtom[ii].y;
                    minZ = arrAtom[ii].z;
                    maxZ = arrAtom[ii].z;
                } else {
                    if(minX > arrAtom[ii].x) {minX = arrAtom[ii].x;}
                    if(maxX < arrAtom[ii].x) {maxX = arrAtom[ii].x;}
                    if(minY > arrAtom[ii].y) {minY = arrAtom[ii].y;}
                    if(maxY < arrAtom[ii].y) {maxY = arrAtom[ii].y;}
                    if(minZ > arrAtom[ii].z) {minZ = arrAtom[ii].z;}
                    if(maxZ < arrAtom[ii].z) {maxZ = arrAtom[ii].z;}
                }
            }
            //結合線
            else if (arrDat[i].indexOf("CONECT") == 0) {
                var s:String = arrDat[i].substr( 6, 5 );
                var s0:String = arrDat[i];
                var iStrCon:int = int(arrDat[i].substr( 6, 5 ));
                for (var j:int = 11; j < arrDat[i].length; j += 5) {
                    var s2:String = arrDat[i].substr( j, 5 );
                    var iEndCon:int = int(arrDat[i].substr( j, 5 ));
                    //結合のデータは始点・終点を逆にして2本あるので片方をセット
                    if(iStrCon<iEndCon) { 
                        arrConect.push( { StrCon:iStrCon, EndCon:iEndCon } );
                    }
                }
            }
        }
    }
    catch(e:*) {
        Alert.show("PBDファイルがおかしい","Molecule");
        return;
    }
    //中心座標
    var dx:Number = (minX + maxX) / 2.0;
    var dy:Number = (minY + maxY) / 2.0;
    var dz:Number = (minZ + maxZ) / 2.0;
    //表示
    fncInitSld();
    fncDispAtom(dx,dy,dz);
}

原子結合線表示は以下の様におこないます。
 既存イメージの破棄
 配列「arrAtom」のループで原子を表示します。
 配列「arrConect」のループで結合線を表示します。
  getAtomIDで結合線の始終点の原子(座標)を求めます。
//原子結合線表示
//dx,dy,dz:中心座標
private function fncDispAtom(dx:Number, dy:Number, dz:Number):void {
    gdx = dx; //表示中の原点の座標
    gdy = dy;
    gdz = dz;

    // 既存イメージの破棄
    for (var p:String in rootNode.children) {
        rootNode.removeChild(rootNode.children[p]);
    }
    //原子の表示
    for (var k:int = 0; k < arrAtom.length; k++) {
        rootNode.addChild(mkAtom(arrAtom[k].nam,
                (arrAtom[k].x-dx), (arrAtom[k].y-dy), (arrAtom[k].z-dz),
                            20, 0x0000ff,0xff00ff));
    }
    //結合線の表示
    for (var l:int = 0; l < arrConect.length; l++) {
        var id1:int = getAtomID(arrConect[l].StrCon);
        var id2:int = getAtomID(arrConect[l].EndCon);
        if (id1 >= 0 && id2 >= 0) {
            rootNode.addChild(mkLine(
                (arrAtom[id1].x-dx) , (arrAtom[id1].y-dy) , (arrAtom[id1].z-dz) ,
                (arrAtom[id2].x - dx) , (arrAtom[id2].y - dy) ,
                 (arrAtom[id2].z - dz),
                20, 20, 0x888888));
        }
    }
    onSlChmg();
}
//結合線の両端に付く原子を求める
// iCon:原子のID
// 戻り値:arrAtomの位置(-1:対応する原子なし)
private function getAtomID(iCon:int):int {
    for (var i:int; i < arrAtom.length; i++) {
        if (iCon == arrAtom[i].id) { return i;}
    }
    return -1;
}

原子の表示に関して
 原子は平面(Plane)に球のイメージ(bmAtom)を貼る事で表現する。
 原子を球(Sphere)であらわすより処理が軽くなる。
 (球の場合分割数が多くなり、数が多くなった場合処理が重くなる。)
 onSlChmg関数で平面が必ず正面を向くようにする。
 球イメージはFlex4基本-1枚の画像のカラーを変更 を利用して色を付ける。
 (現在は青1色だが機能として付けておく)
 また、球イメージの手前に原子の名称(Text3D)を表示する。

原子のクリックで指定原子を中央に表示する
 クリックを有効にする
  bv.viewport.interactive = true; BasicViewのクリック有効(忘れないように)
  matrAtom.interactive = true; 原子自体のクリック有効
 クリックイベントに関数(onClickAtom)を指定
  指定原子を中心に再表示
bv.viewport.interactive = true;   //クリック可能にする。
        :
//原子の表示
// nam:表示する名称
// x,y,z:表示位置
// r:半径
// uiAtomCol:原子の色
// uiNameCol:表示名称の色
// 戻り値:オブジェクト
private function mkAtom(nam:String, x:Number, y:Number, z:Number, r:Number, 
        uiAtomCol:uint,uiNameCol:uint):DisplayObject3D {
    var objNode:DisplayObject3D=new DisplayObject3D();
    //原子
    var objPlane:Plane;
    var bmAtom:Bitmap = Bitmap(new imgAtom);
    var matrAtom:BitmapMaterial = new BitmapMaterial(bmAtom.bitmapData,true);
    matrAtom.smooth = true;
    matrAtom.oneSide = true;
    matrAtom.interactive = true;    //クリック有効
    //material.doubleSided = true;  //裏表示
    objPlane = new Plane(matrAtom, r * 2, r * 2, 1, 1);
    objNode.addChild(objPlane);
    //クリックイベント追加
    objPlane.addEventListener(InteractiveScene3DEvent.OBJECT_CLICK, onClickAtom);
    var ir:uint = uiAtomCol / (256 * 256);  //カラーをRGBに分解
    var ig:uint = (uiAtomCol / (256))-ir*256;
    var ib:uint = uiAtomCol % 256;
    //イメージの黒色部分を指定カラーに変更
    objPlane.material.bitmap.colorTransform(objPlane.material.bitmap.rect, 
            new ColorTransform(1.0,1.0,1.0,1.0, ir, ig, ib,0));
    
    //名称の表示
    var LMaterial:Letter3DMaterial = new Letter3DMaterial(uiNameCol , 1);
    //LMaterial.doubleSided = true;  //裏表示
    var fnt:HelveticaBold = new HelveticaBold();
    var word:Text3D = new Text3D(nam, fnt , LMaterial);
    word.z = -10.0;     //手前に表示
    word.x = 10.0+nam.length*10.0;
    word.y = 20.0;
    word.scale = 0.5;
    objNode.addChild(word);

    //(原子+名称)の位置
    objNode.x = x;
    objNode.y = y;
    objNode.z = z;
    //表示処理用の配列にセット
    arrObj.push(objNode);
    return objNode;
}
//原子をクリック
private function onClickAtom(even:InteractiveScene3DEvent):void {
    //クリックした原子を中心に表示
    fncDispAtom(even.target.parent.x + gdx, even.target.parent.y + gdy,
                    even.target.parent.z+gdz);
}
(2011.04.04:原子をクリックの処理を変更)

結合線は「Line3D」で引きます。原子の半径分線分を短くします。
//結合線
// x1,y1,z1,x2,y2,z3:結合線の始終点
// r1,r2:付く原子の半径(半径分表示線分を短くする)
// ic:カラー
private function mkLine(x1:Number, y1:Number, z1:Number,
                        x2:Number, y2:Number, z2:Number,
                        r1:Number, r2:Number, ic:uint):Lines3D {
    //ラインの単位ベクトルを求める
    var dx:Number = x2 - x1;
    var dy:Number = y2 - y1;
    var dz:Number = z2 - z1;
    var dd:Number = Math.sqrt(dx * dx + dy * dy + dz * dz);
    if (dd > 0.0) {
        dx = dx / dd;
        dy = dy / dd;
        dz = dz / dd;
    }
    //線を引く
    var lm:LineMaterial = new LineMaterial(ic);  
    var lines3D:Lines3D = new Lines3D();
    var startV:Vertex3D = new Vertex3D(x1+r1*dx, y1+r1*dy, z1+r1*dz);  
    var endV:Vertex3D = new Vertex3D(x2-r2*dx, y2-r2*dy, z2-r2*dz);  
    var line:Line3D = new Line3D(lines3D, lm, 5, startV, endV);  
    lines3D.addLine(line);  
    return lines3D;
}

カメラに関しては距離でなくZoomを指定するようにしました。
距離よりZoom方がこの例ではきれいに表示されます。

平面のイメージを原子(球)に見せるため必ず正面を向くように「rotationX」、「rotationY」で調整します。
平面を正面を向かせるには「lookAt」と「yaw」を使う方法がありますが文字が回転してしまうのでこの方法はやめました。
//Sliderの値変更:カメラの位置セット、形状表示
private function onSlChmg():void {
    //カメラ位置
    var kyori:Number = 1000;//原点・カメラの距離
    //高さ方向の角度(ラジアン)
    var ang1:Number =  sldAng1.value * Math.PI / 180.0;   
    //方向(ラジアン)
    var ang2:Number = (sldAng2.value-180) * Math.PI / 180.0;    
    var x:Number=kyori*Math.cos(ang1)*Math.sin(ang2);
    var y:Number=kyori*Math.cos(ang1)*Math.cos(ang2);
    var z:Number = kyori*Math.sin(ang1);
    bv.camera.x = x;
    bv.camera.z = y;
    bv.camera.y = z;
    bv.camera.zoom=sldZoom.value*40.0;
    
    //原子(Plenで表示)を正面を向かせる。
    for (var i:int = 0; i < arrObj.length; i++) {
        //lookAtで原子をカメラに向けると文字等がうまく表示されない
        //arrObj[i].lookAt( bv.camera );
        //arrObj[i].yaw(180);
        arrObj[i].rotationX = sldAng1.value;
        arrObj[i].rotationY = sldAng2.value;
    }
    //描画処理
    bv.renderer.renderScene( bv.scene , bv.camera , bv.viewport );
}

原子をクリックの処理を変更(2011.04.04)

クリックした原子を中心に表示する為には「rootNode」の座標にクリックした原子の中心座標を
セットすればよい事がわかりました。(コメントアウト部分)

この修正だけでな面白くないので、「Tweener」を利用して滑らかに動くようにしました。
まずhttp://code.google.com/p/tweener/ からTweener.swcをダウンロードしlibの下に置きます。
(Papervision3Dと同じようにしてください)
Tweener.addTweenで対象「rootNode」と移動後の値「 x: -x, y: -y, z: -z,」、
更新時にコールされる関数「fncReDisp」、時間1秒を指定します。
「transition:"easeInOutCubic"」は移動スピードの指定です。
import caurina.transitions.Tweener;
          :
//原子をクリック
private function onClickAtom(even:InteractiveScene3DEvent):void {
    //クリックした原子を中心に表示
    var x:Number = even.target.parent.x ;
    var y:Number = even.target.parent.y ;
    var z:Number = even.target.parent.z ;
    //rootNode.x = -x;
    //rootNode.y = -y;
    //rootNode.z = -z;
    //描画処理
    //bv.renderer.renderScene( bv.scene , bv.camera , bv.viewport );
    Tweener.addTween(rootNode, { x: -x, y: -y, z: -z,
                delay:0,onUpdate:fncReDisp,
                time:1, transition:"easeInOutCubic"});
}
          :
//描画処理
private function fncReDisp():void {
    bv.renderer.renderScene( bv.scene , bv.camera , bv.viewport );
}


プログラミング工房