jMonkeyEngine 3 Tutorial (2) – Hello Node

このチュートリアルでは、3Dシーンの作成について解説する。
* このチュートリアルでは、 *シーングラフ(scene graph)* とは何か知っていることを前提とする。
* 視覚的な概要については “Scene Graph for Dummies(未翻訳)”:http://hub.jmonkeyengine.org/wiki/doku.php/jme3:scenegraph_for_dummies を参照
3Dゲーム作成時、
# あなたはプレイヤーや建築物などのオブジェクトを作成する
# そのオブジェクトらをシーンに追加する
# 動き、リサイズ、回転、着色やアニメーションをさせる
ここではシーングラフが3Dワールドを表し、 *rootNode* の重要性について、シンプルなオブジェクトの作り方、カスタムデータの運用方法、回転や動作などの *transform* について学ぶ。
そして、シーングラフ内での二つの *Spatials* のタイプ、 *Node* と *Geometry* について理解するだろう。

コードサンプル

 
package jme3test.helloworld;
 
import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.math.ColorRGBA;
import com.jme3.scene.Node;
 
/** Sample 2 - How to use nodes as handles to manipulate objects in the scene.
 * You can rotate, translate, and scale objects by manipulating their parent nodes.
 * The Root Node is special: Only what is attached to the Root Node appears in the scene. */
 
public class HelloNode extends SimpleApplication {
 
    public static void main(String[] args){
        HelloNode app = new HelloNode();
        app.start();
    }
 
    @Override
    public void simpleInitApp() {
 
        /** create a blue box at coordinates (1,-1,1) */
        Box box1 = new Box( Vector3f.ZERO, 1,1,1);
        Geometry blue = new Geometry("Box", box1);
        Material mat1 = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("Color", ColorRGBA.Blue);
        blue.setMaterial(mat1);
        blue.move(1,-1,1);
 
        /** create a red box straight above the blue one at (1,3,1) */
        Box box2 = new Box( Vector3f.ZERO, 1,1,1);
        Geometry red = new Geometry("Box", box2);
        Material mat2 = new Material(assetManager,
                "Common/MatDefs/Misc/Unshaded.j3md");
        mat2.setColor("Color", ColorRGBA.Red);
        red.setMaterial(mat2);
        red.move(1,3,1);
 
        /** Create a pivot node at (0,0,0) and attach it to the root node */
        Node pivot = new Node("pivot");
        rootNode.attachChild(pivot); // put this node in the scene
 
        /** Attach the two boxes to the *pivot* node. */
        pivot.attachChild(blue);
        pivot.attachChild(red);
        /** Rotate the pivot node: Note that both boxes have rotated! */
        pivot.rotate(.4f,.4f,0f);
    }
}
 

このコードを実行すると、同じ角度で傾いた2つのカラーボックスが表示される。

用語を理解する

開発上、英語のままの表現の方がいい場合は、翻訳せずに英単語のままにしています。

やりたいこと JME3での表現
3Dシーンのレイアウト シーングラフ(scene graph)をPopulateする
シーンオブジェクトの作成 Spatial の作成 (例:Geometryの作成)
オブジェクトのシーンへの表示 rootNodeSpatial を加える
シーンからオブジェクトを削除 rootNodeからSpatialを切り離す
オブジェクトの配置/移動、回転、リサイズ オブジェクトのTranslate、rotate、scale = オブジェクトのtransform

jme3のゲームは常にrootNodeを持っている。 rootNode オブジェクトは自動的にSimpleApplicationから継承される。
rootNodeに加えられたものは全て、シーングラフの一部となる。シーングラフの要素はSpatialである。

  • Spatialは、オブジェクトのlocation(位置)、rotation(回転)、scale(大きさ)を包括するものである。
  • Spatialは、save、transform(変形)、loadが可能である。
  • Spatialsには二つのタイプがある。GeometryとNodeである。
Geometry Node
Visibility: Geometryは可視シーンオブジェクトである Nodeは、シーンオブジェクトの見えないハンドルである。
用途 Geometryはオブジェクトの外見について扱う NodeはGeometryや他のノード達をまとめてグループ化する
ボックス、球体、プレイヤー、建築物、地形の一部、乗り物、ミサイル、NPCなど rootNode 、いくつかの地形がグループ化されたフロアのノード、乗客を搭載するなどした乗り物のノード、武器を持ったプレイヤーノード、オーディオノードなど

コードを理解する

コードスニペット内では何が起きているのか? simpleInitApp() メソッドがシーンのイニシャライズをすることは初めのチュートリアルで既に紹介済み。

  • まず一つ目のGeometryを作る。
    • (1,1,1)のBoxを作ると、worldに2×2×2の大きさのユニットができる。
    • (1,-1,1)にsetLocalTranslation()メソッドを使って配置する
    • BoxをGeometryにラップする
    • blue色のmaterialを作る
    • materialをBox Geometryに登録する。
 
    Box box1 = new Box(1,1,1);
    Geometry blue = new Geometry("Box", box1);
    blue.setLocalTranslation(new Vector3f(1,-1,1));
    Material mat1 = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md");
    mat1.setColor("Color", ColorRGBA.Blue);
    blue.setMaterial(mat1);
 
  • 2つ目のGeometryを作る
    • 同じサイズのBoxを作成する
    • ポジションを(1,3,1)に移動する。 これにより先ほどのボックスの直線上に配置される。
    • BoxをGeometryにラップする
    • red色のmaterialを作成する。
    • materialをboxに登録する。
 
    Box box2 = new Box(1,1,1);
    Geometry red = new Geometry("Box", box2);
    red.setLocalTranslation(new Vector3f(1,3,1));
    Material mat2 = new Material(assetManager,
      "Common/MatDefs/Misc/Unshaded.j3md");
    mat2.setColor("Color", ColorRGBA.Red);
    red.setMaterial(mat2);
 
  • pivot Nodeの作成
    • Node名を“pivot“とする
    • Nodeの位置はデフォルトでは (0,0,0)に配置される
    • rootNodeに登録する
    • このノードはシーン内には表示されない。
 
    Node pivot = new Node("pivot");
    rootNode.attachChild(pivot);
 

この状態でコードを実行しても、シーンには何も表示されない。理由はNode自体が見えるものではなく、まだrootNodeにGeometryを登録してないためである。

pivotノードに2つのBoxを登録する。

 
        pivot.attachChild(blue);
        pivot.attachChild(red);
 

この段階でコードを実行すると、2つのキューブが確認できるはず。

pivotを回転させる。

 
        pivot.rotate( 0.4f , 0.4f , 0.0f );
 

このコードを実行すると、2つのボックスが重なった状態で表示される。

Pivotノードで何が起きているか

ジオメトリを、ユーザが定義した中心点を軸に変形(rotateなど)させることができる。ユーザが定義したジオメトリのセンターポイントは、pivotのものとなる。

  • この例では、2つのGeometryグループを、1つのpivotノードに登録している。一つの共通の中心の周りに2つのジオメトリを回転させるハンドルとして、Pivot Nodeを使用しています。Pivot Nodeを回転させると、ワンステップで接続されたすべてのジオメトリを回転させることができる。Pivot Nodeが回転の中心になっている。他のGeometryを取り付ける前に、Pivot Nodeが(0,0,0)であることを確認する。接続されているすべての子Spatialsを変換(transform)するために、親ノードを変換することは一般的なタスクである。Spacialを移動させたいとき、この方法はゲーム作成の上でとてもよくつかわれる。 Examples: 車両とドライバーを一緒に移動させたり、月と惑星などが太陽を周回する時など。
  • pivot nodeを作成しGeometryを変換する必要がない場合は、すべての変換は、ジオメトリの原点(通常は中央)に対して行われる。
  • もし作成したキューブを直接回転させたい場合、 red.rotate(0.1f , 0.2f , 0.3f); や blue.rotate(0.5f , 0.0f , 0.25f); などとすれば、それぞれの中心点を基準に回転する。惑星の自転と同様である。

どのようにシーングラフを読み込むか?

Task…? Solution!
Spacialの作成

メッシュシェイプを作り、Geometryでラップ後、Materialを追加する。

 
Box mesh = new Box(Vector3f.ZERO, 1, 1, 1); // a cuboid default mesh
Geometry thing = new Geometry("thing", mesh);
Material mat = new Material(assetManager,
   "Common/MatDefs/Misc/ShowNormals.j3md");
thing.setMaterial(mat);
 
オブジェクトがシーンに表示されるようにする

Spatial を rootNode 、もしくは rootNode に接続されている任意のノードに追加する。

rootNode.attachChild(thing);
シーンからオブジェクトを消す

rootNodeから、もしくはrootNodeに接続されている任意のノードからSpacialを切り離す。

rootNode.detachChild(thing);
rootNode.detachAllChildren();
シーン内のSpatialをIDや名前、親子関係で探す
Spatial thing = rootNode.getChild("thing");
Spatial twentyThird = rootNode.getChild(22);
Spatial parent = myNode.getParent();

どのようにSpatialを変換するか

変換、スケーリング、および回転:3Dの変換には3つのタイプがある。

Spatialの移動

原点から右、上、前にどれくらいの距離かを、3次元で指定する。

thing.setLocalTranslation( new Vector3f( 0.0f, 40.2f, -2.0f ) );
X-axis +right -left
Y-axis +up -down
Z-axis +forward -backward
Spatialのリサイズ

長さ、高さ、幅:各次元のスケーリング係数を指定する。
0.0f から1.0fの間でSpacialを縮小する。それ以上だと伸び、1.0fなら変化なしとなる。
各次元が同じ値を用いた場合はそれに比例して大きくなり、違う値の場合は伸張する。
長さを10倍、高さを1/10、幅をそのままにした例

thing.scale( 10.0f, 0.1f, 1.0f );
X-axis 奥行き
Y-axis 高さ
Z-axis
Spatialの回転

3Dの回転は、少々トリッキーである(lean more FastMath.DEG_TO_RAD と度値を乗算し、度単位の角度を指定することができる。
z-axisを軸にオブジェクトを回転させる。

thing.rotate( 0f , 0f , 180*FastMath.DEG_TO_RAD );

If your game idea calls for a serious amount of rotations, it is worth looking into quaternions , a data structure that can combine and store rotations efficiently.(回転と4元数についてなんか言ってるけど、よくわからん)

 
thing.setLocalRotation(
  new Quaternion().fromAngleAxis(180*FastMath.DEG_TO_RAD, new Vector3f(1,0,0)));
 
 
X-axis pitch = nodding your head
Y-axis yaw = shaking your head
Z-axis roll = cocking your head

回転については適当な訳が自分にはできないので、実際にx,y,zそれぞれrotateさせて試してください

Spatialの修復方法

もし予期しない結果を得た場合は、次の一般的な間違いをしたかどうかを確認する。

問題 解決策
作成したGeometryが表示されない rootNode (に接続されているノード)に接続されているか?
Materialがあるか?
変形させたかどうか(位置の変更など)?
オブジェクトがカメラの後ろに配置されていないか?
他のGeometryに隠れていないか?
Spatialが予期しない回転をする 度ではなく、ラジアン値を使用したか?(度を使用した場合、FastMath.DEG_TO_RADを乗算してラジアン値に変換する)
移動する前に、原点(Vector.ZERO)でSpactialを作成したか?
意図されたpivot nodeの周りや他の何かの周りに回転したか?
右の軸の周りに回転させたか?
Geometryが予期しない色や素材を持っている 別のGeometryからMaterialを再利用し、不注意にそのプロパティを変更していないか?(その場合は、クローンする mat2 = mat.clone(); )

Spacialにカスタムデータを追加するには

多くのSpatialsが、プレーヤーと交流することができるゲームキャラクターなどを表わしている。
例えば、共通の中心(ピボット)のまわりの2つの箱を回転させる上記のコードは、軌道に乗る宇宙ステーションへドックに入れられた宇宙船に使用することができるかもしれない。

あなたのゲームによって、ゲーム自体はそれらの位置、回転あるいは大きさ(さきほど知った変形)を変更しません。ゲーム自体はまた、体力などのカスタムプロパティ、在庫、キャラ、または宇宙船放置船体強度と燃料などを持っている。Javaでは、例えば、クラス変数としてエンティティデータを表すfloats、Strings、またはArray。

NodeやGeometryにカスタムデータを直接追加することができる。

pivot.setUserData( "pivot id", 42 );

idナンバーを呼び出したい場合は、このようにする

int id = pivot.getUserData( "pivot id" );