■光源 (javax.media.j3d.Light)

今までは頂点に色を適用して描画してきました。Java 3Dには javax.media.j3d.Light クラスがあり、光源を定義することができます。

クラス継承

java.lang.Object
  |
  +--javax.media.j3d.SceneGraphObject
        |
        +--javax.media.j3d.Node
              |
              +--javax.media.j3d.Leaf
                    |
                    +--javax.media.j3d.Light
                          |
                          |--javax.media.j3d.AmbientLight     // 環境光
                          |
                          |--javax.media.j3d.DirectionalLight // 平行光源
                          |
                          +--javax.media.j3d.PointLight       // 点光源
                                |
                                +--javax.media.j3d.SpotLight  // スポットライト

クラス宣言

public abstract class Light
extends Leaf

javax.media.j3d.Light クラスは抽象クラス (abstcact クラス) であり、実際にはそのサブクラスの AmbientLight, DicectionalLight, PointLight, SpotLight クラスが使用されます。

Light は次の手順で使用します。

  1. Light オブジェクトを生成する
  2. Light オブジェクトの setInfluencingBounds() メソッドで、光源が作用する範囲を設定する
  3. Group ノードに addChild() する

実は上記以外に、照明される物体に「法線ベクトル」というものが設定されていないと、照明は有効になりません

ここでは、照明による効果を確認するため、あらかじめ法線ベクトルが設定済みの com.sun.j3d.utils.geometry.Sphere を使います。

自分で物体の形状を定義してある場合、法線ベクトルも自分で設定しなければなりません。法線ベクトルの計算方法、設定方法に付いては後で詳しく説明します。

■■平行光源(javax.media.j3d.DirectionlLight)

平行光源は、太陽光のように(ほぼ)無限遠に光源があるもの、と考えることができます。

javax.media.j3d.DirectionalLight は平行光源をシミュレートします。


クラス宣言

public class DirectionalLight
extends Light

コンストラクター

public DirectionalLight()

public DirectionalLight(Color3f color,      // 光源の色
                        Vector3f direction) // 光の方向

public DirectionalLight(boolean lightOn,    // 点灯しているかどうか
                        Color3f color,      // 光源の色
                        Vector3f direction) // 光の方向

引数なしのコンストラクターでは、lightOn == true (点灯している状態)、光源の色は白 (1.0f, 1.0f, 1.0f)、光の方向は Z軸のマイナス方向 (0.0f, 0.0f, -1.0f)、つまり視点から画面奥に向かう方向になります。(自動車などの「ヘッドライト」を想像してください)

光源に色を付けると、照明された物体はその色で照らされます。

光の方向は、javax.vecmath.Vector3f で設定します。Transform3Dset() メソッドで Vector3d を使ったときは、移動先の座標を定義するために使用しましたが、Light で使用する Vector3f は方向ベクトルとして使用します。


クラス宣言

public class Vector3f
extends Tuple3f
implements java.io.Serializable

コンストラクター (一部)

public Vector3f(float x, // ベクトルの x 成分
                float y, // ベクトルの y 成分
                float z) // ベクトルの z 成分

正規化のためのメソッド (一部)

public final void normalize()

光の方向に限らず、方向ベクトルは正規化 (ベクトルの長さを 1.0 にする) すると効率良く処理できます。Vector3f は、正規化のためのメソッド normalize() を持っています。

DicectionalLight での光の方向は、TransformGroup の影響を受けることに注意してください。TransformGroupaddChild() された DirectionalLight は、その TransformGroup に回転が適用されれば一緒に回転します。

LightingApplet.java
 1 // Java 3D Test Applet
 2 // LightingApplet.java
 3 //   Copyright (c) 1999 ENDO Yasuyuki
 4 //                      mailto:yasuyuki@javaopen.org
 5 //                      http://www.javaopen.org/j3dbook/index.html
 6 
 7 import java.applet.*;
 8 import java.awt.*;
 9 import javax.media.j3d.*;
10 import javax.vecmath.*;
11 import com.sun.j3d.utils.applet.MainFrame;
12 import com.sun.j3d.utils.universe.SimpleUniverse;
13 import com.sun.j3d.utils.geometry.Sphere;
14 
15 public class LightingApplet extends Applet {
16   public LightingApplet() {
17     GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
18     Canvas3D canvas = new Canvas3D(config);
19     this.setLayout(new BorderLayout());
20     this.add(canvas, BorderLayout.CENTER);
21     
22     SimpleUniverse universe = new SimpleUniverse(canvas);
23     universe.getViewingPlatform().setNominalViewingTransform();
24     
25     universe.addBranchGraph(createSceneGraph());
26   }
27   
28   private BranchGroup createSceneGraph() {
29     BranchGroup root = new BranchGroup();
30     
31     DirectionalLight light = new DirectionalLight();....................(1)
32     BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);...(2)
33     light.setInfluencingBounds(bounds);.................................(3)
34     root.addChild(light);...............................................(4)
35 
36     Sphere sphere = new Sphere(0.6f);...................................(5)
37     root.addChild(sphere);
38     
39     return root;
40   }

(1)で DicectionalLight を生成しています。引数無しのデフォルトのコンストラクターを使ったので、光源の色は白、方向はマイナス Z方向です。

(2)で BoundingSphere を生成し、(3)で setInfluencingBouns() メソッドを使って光源が作用する範囲として設定しています。

(4)で BranchGroupaddChild() しています。

(5)でcom.sun.j3d.utils.geometry.Sphere オブジェクトを半径 0.6 で生成しています。
SphereColorCube などと同様、あらかじめ形状が定義されたテスト用のオブジェクトです。
物体が照明を反映して見えるためには、法線ベクトルというもの(後述)を設定する必要がありますが、Sphere にはデフォルトの状態で法線ベクトルが設定されています。このため、addChild() するだけで表示されます。

このプログラムの実行結果は次のようになります。

LightingApplet1.gif

■■光源に色を設定する

光源に色を設定してみます。

LightingApplet.java (一部)
28   private BranchGroup createSceneGraph() {
29     BranchGroup root = new BranchGroup();
30     
31     DirectionalLight light =
32       new DirectionalLight( new Color3f(1.0f, 0.0f, 0.0f),     // 赤 
33                             new Vector3f(0.0f, 0.0f, -1.0f) ); // マイナスZ方向...(1)
34     BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
35     light.setInfluencingBounds(bounds);
36     root.addChild(light);
37 
38     Sphere sphere = new Sphere(0.6f);
39     root.addChild(sphere);
40     
41     return root;
42   }

(1)で DirectionalLight を生成しています。引数の Color3f に赤色を指定しています。

これ以外の部分は前のサンプルと変わりません。

このプログラムの実行結果は次のようになります。

LightingApplet2.gif

このサンプルでは球体しかありませんが、ほかの物体があっても赤く照明されます。

色と方向が異なる、2つの光源を addChild() するとどうなるでしょうか。

28   private BranchGroup createSceneGraph() {
29     BranchGroup root = new BranchGroup();
30     
31     BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
32 
33     DirectionalLight redLight =
34       new DirectionalLight( new Color3f(1.0f, 0.0f, 0.0f),      // 赤
35                             new Vector3f(0.87f, 0.0f, -0.5f) ); // 斜め左方向から照明 ...(1)
36     redLight.setInfluencingBounds(bounds);
37     root.addChild(redLight);
38 
39     DirectionalLight greenLight =
40       new DirectionalLight( new Color3f(0.0f, 1.0f, 0.0f),       // 緑
41                             new Vector3f(-0.87f, 0.0f, -0.5f) ); // 斜め右方向から照明 ...(2)
42     greenLight.setInfluencingBounds(bounds);
43     root.addChild(greenLight);
44 
45     Sphere sphere = new Sphere(0.6f);
46     root.addChild(sphere);
47     
48     return root;
49   }

(1)で赤い光源、(2)で緑色の光源を生成し、光源が作用する範囲を設定し、addChild() しています。

照明の方向はそれぞれ斜め左方向からと、斜め右方向から照明されるように Vector3f を設定しています。

このプログラムの実行結果は次のようになります。

LightingApplet3.gif

■いろいろな光源

平行光源以外の光源には、点光源、環境光、スポットライトがあります。

■■点光源 (javax.media.j3d.PointLight)

javax.media.j3d.PointLight は(電球のような)点光源をシミュレートします。


クラス宣言

public class PointLight
extends Light

コンストラクター

public PointLight()

public PointLight(Color3f color,       // 光源の色
                  Point3f position,    // 光源の位置座標
                  Point3f attenuation) // 光の減衰係数  

public PointLight(boolean lightOn,      // 点灯しているかどうか
                  Color3f color,       // 光源の色      
                  Point3f position,    // 光源の位置座標
                  Point3f attenuation) // 光の減衰係数  

光源の色に関しては DirectionalLight と同じです。

光源の位置については、Point3f で指定した位置から照明されます。ここで面白いのは、光源から照明された光(による照明効果)は描画されますが、光源そのものは描画されない、ということです。光源がどこにあるかを見せるには、光源位置に何か物体を描画するしかありません。

光の減衰係数は、ちょっと複雑です。

Point3f を引数にとりますが、これは座標として使用されるものではありません。

PointLight は減衰係数を設定する setAttenuation() メソッドも持っていますので、このメソッドで説明します。

public final void setAttenuation(float constant,  // 減衰定数
                                 float linear,    // 減衰一次係数
                                 float quadratic) // 減衰二次係数

public final void setAttenuation(Point3f attenuation)

減衰係数は、距離に応じた光の減衰を計算するために使用されます。この減衰式は、Java 3D API Specificatoin: Appendix E.Equations E.2 Light Equeations に、式 (E.7) として掲載されています。

atteni = 1/(Kci + Kli・di + Kqi・di2)

di距離
Kci減衰定数
Kli減衰一次係数
Kqi減衰二次係数

PointLight で使用する Point3f attenuationx, y, z は、それぞれ次のような意味を持っています。

xKci減衰定数
yKli減衰一次係数
zKqi減衰二次係数

減衰係数についてまとめると次のようになります。

PointLight の引数無しのコンストラクターでは、lightOn == true (点灯している状態)、光源の色は白 (1.0f, 1.0f, 1.0f)、光源の位置は原点 (0.0f, 0.0f, 0.0f)、減衰係数は (1.0f, 0.0f, 0.0f) になります。

LightingApplet.java (一部)
28   private BranchGroup createSceneGraph() {
29     BranchGroup root = new BranchGroup();
30     
31     PointLight light =
32       new PointLight( new Color3f(1.0f, 1.0f, 1.0f),   // 光源の色
33                       new Point3f(0.5f, 0.5f, 0.8f),   // 光源の位置
34                       new Point3f(0.7f, 0.0f, 0.0f) ); // 光の減衰定数、係数 ...(1)
35     BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
36     light.setInfluencingBounds(bounds);
37     root.addChild(light);
38 
39     Sphere sphere = new Sphere(0.6f);
40     root.addChild(sphere);
41     
42     return root;
43   }

(1)で PointLight を生成しています。光源の色は白、光源の位置は、後で追加する球体の右上あたりの位置、減衰係数は定数値だけ設定しています。

DirectionalLight のときと同様に、setInfluencingBounds() メソッドで作用する範囲を設定し、BranchGroupaddChild() しています。

このプログラムの実行結果は次のようになります。

LightingApplet4.gif

■■環境光 (javax.media.j3d.AmbientLight)

javax.media.j3d.AmbientLight は、DirectionalLight, PointLight のような直接光ではなく、周囲に散乱した間接光 (環境光) をシミュレートします。


クラス宣言

public class AmbientLight
extends Light

コンストラクター

public AmbientLight()

public AmbientLight(Color3f color)   // 光源の色

public AmbientLight(boolean lightOn, // 点灯しているかどうか
                    Color3f color)   // 光源の色

引数無しのコンストラクターでは、lightOn == true (点灯している状態)、デフォルトの色は白です。

AmbientLight には方向や位置はありません。

LightingApplet.java (一部)
28   private BranchGroup createSceneGraph() {
29     BranchGroup root = new BranchGroup();
30     
31     AmbientLight light = new AmbientLight();............................(1)
32     BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);...(2)
33     light.setInfluencingBounds(bounds);.................................(3)
34     root.addChild(light);...............................................(4)
35 
36     Sphere sphere = new Sphere(0.6f);
37     root.addChild(sphere);
38     
39     return root;
40   }

(1)で AmbientLight を生成しています。色は白です。

(2)で生成した BoundingSphere を(3)でsetInfluencingBounds()し、(4)で BranchGroupaddChild() しています。

このプログラムの実行結果は次のようになります。

LighitngApplet5.gif

わかりにくいかもしれませんが、全体がうっすらと濃いグレーに照明されています。

AmbientLight は単独で使ってもあまり意味がありません。直接光と一緒に使うべきものです。

次の例では DicertionalLight と一緒に使っています。

LightingApplet.java (一部)
28   private BranchGroup createSceneGraph() {
29     BranchGroup root = new BranchGroup();
30     
31     BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
32     
33     DirectionalLight dlight =                                   ┐
34       new DirectionalLight( new Color3f(1.0f, 0.0f, 0.0f),      │
35                             new Vector3f(0.87f, 0.0f, -0.5f) ); ├(1)
36     dlight.setInfluencingBounds(bounds);                        │
37     root.addChild(dlight);                                      ┘
38     
39     AmbientLight alight = new AmbientLight(); ┐
40     alight.setInfluencingBounds(bounds);      ├(2)
41     root.addChild(alight);                    ┘
42 
43     Sphere sphere = new Sphere(0.6f);
44     root.addChild(sphere);
45     
46     return root;
47   }

(1)で DirectionalLight の生成、作用範囲の設定、BranchGroup への addChild() を行っています。

(2)で AmbientLight の生成、作用範囲設定、addChild() を行っています。

このプログラムの実行結果は次のようになります。

LightingApplet6.gif

直接光があたっていない部分に環境光が適用されているのがわかると思います。

■■スポットライト (javax.media.j3d.SpotLight)

javax.media.j3d.SpotLight (スポットライト)は PointLight サブクラスです。

SpotLight は、光源の向きと照射角度、そして収束度を持った点光源です。


クラス宣言

public class SpotLight
extends PointLight

コンストラクター

public SpotLight()

public SpotLight(Color3f color,       // 光源の色
                 Point3f position,    // 光源の位置
                 Point3f attenuation, // 光の減衰係数
                 Vector3f direction,  // 光の方向ベクトル
                 float spreadAngle,   // スポットライトの角度
                 float concentration) // スポットライトの収束度

public SpotLight(boolean lightOn,     // 点灯しているかどうか
                 Color3f color,       // 光源の色              
                 Point3f position,    // 光源の位置            
                 Point3f attenuation, // 光の減衰係数          
                 Vector3f direction,  // 光の方向ベクトル      
                 float spreadAngle,   // スポットライトの角度  
                 float concentration) // スポットライトの収束度

光源の色、位置、減衰係数は PointLight と同じです。

光の方向ベクトルは、スポットライトの向きを定義します。

スポットライトの角度は、スポットライトが照らす範囲を制限します。スポットライトは、照射範囲を制限された点光源です。

収束度は、スポットライトの光がどれだけ収束するかを定義します。収束度は 0.0f〜128.0f の範囲で指定します。128.0f のとき最も収束度が大きくなります。

収束度のデフォルト値は 0.0f です。0.0f のときは、スポットライトの円錐内の輝度は一定です。

LightingApplet.java (一部)
28   private BranchGroup createSceneGraph() {
29     BranchGroup root = new BranchGroup();
30     
31     BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
32     SpotLight light1 =
33       new SpotLight( new Color3f(1.0f, 1.0f, 1.0f),     // 光源の色              
34                      new Point3f(0.5f, 0.5f, 0.8f),     // 光源の位置            
35                      new Point3f(1.0f, 0.0f, 0.0f),     // 光の減衰係数          
36                      new Vector3f(-0.5f, -0.5f, -0.7f), // 光の方向ベクトル      
37                      (float)(Math.PI / 7.0),            // スポットライトの角度  
38                      0.0f );                            // スポットライトの収束度 ...(1)
39     light1.setInfluencingBounds(bounds);
40     root.addChild(light1);
41 
42     SpotLight light2 =
43       new SpotLight( new Color3f(1.0f, 1.0f, 1.0f),     // 光源の色              
44                      new Point3f(-0.5f, -0.5f, 0.8f),   // 光源の位置            
45                      new Point3f(1.0f, 0.0f, 0.0f),	   // 光の減衰係数          
46                      new Vector3f(0.5f, 0.5f, -0.7f),   // 光の方向ベクトル      
47                      (float)(Math.PI / 7.0),		   // スポットライトの角度  
48                      20.0f );			   // スポットライトの収束度 ...(2)
49     light2.setInfluencingBounds(bounds);
50     root.addChild(light2);
51 
52     Sphere sphere = new Sphere(0.6f, Sphere.GENERATE_NORMALS, 100);...(3)
53     root.addChild(sphere);
54     
55     return root;
56   }

(1)で球体の右上にあって球体の中心方向を照らすスポットライトを生成しています。角度は約 20度です。収束の度合はデフォルト値の 0.0f です。

(2)で球体の左下にあって球体の中心方向を照らすもう一つのスポットライトを生成しています。収束の度合は 20.0f にしてみました。

(3)で Sphere を生成しています。照明の効果をスムースに見せたいので、球体の分割数を 100 に増やしています。

このプログラムの実行結果は次のようになります。

LighitngApplet7.gif

収束度を無し (0.0f) にした左上のライトでは、ポリゴンのふちが目立っているようです。右下のライトは光が収束している効果が確認できます。

■物体の色や輝度を設定する (javax.media.j3d.Material クラス)

今までは光源に色を設定してみましたが、光源に色を設定するのではなく、物体の表面に光の反射の度合や色などの属性を設定することができます。

このために使用するのが javax.media.j3d.Material クラスです。

クラス継承

java.lang.Object
  |
  +--javax.media.j3d.SceneGraphObject
        |
        +--javax.media.j3d.NodeComponent
              |
              +--javax.media.j3d.Material

クラス宣言

public class Material
extends NodeComponent

コンストラクター

public Material()

public Material(Color3f ambientColor,  // 環境反射による色
                Color3f emissiveColor, // 発光による色
                Color3f diffuseColor,  // 拡散反射による色
                Color3f specularColor, // 鏡面反射による色
                float shininess)       // 輝度

ambientColor は、環境光による色を設定します。ambientColorjavax.media.j3d.AmbientLight が無いと有効になりません。

emissiveColor は、それ自体発光している物体の発光色を設定します。emissiveColor は光源が全く無くても有効になります。

diffuseColor は、直接光(白色光)が物体の表面に当たったときに物体から反射される色を設定します。通常、物体の色というときには diffuseColor のことを言っています。

specularColor は、最も輝度が高い部分(ハイライト部分)の色です。

shininess は、specularColor の輝度を設定します。1.0f〜128.0f までの値をとります。shininess128.0f のとき輝度が最大になります。輝度が最大になると、反射光による光沢は最も鋭い状態(絞られた状態)になります。

shininess1.0fのとき輝度は最小になります。輝度が最小になると反射光による光沢はもっとも広がった状態になります。

Material のデフォルト値はそれぞれ次の通りです。

lighting enable : true            // 照明が有効 
ambient color :   (0.2, 0.2, 0.2) // 環境光による色は   濃いグレー
emmisive color :  (0.0, 0.0, 0.0) // 発光による色は     黒 (発光しない)
diffuse color :   (1.0, 1.0, 1.0) // 拡散反射による色は 白
specular color :  (1.0, 1.0, 1.0) // 鏡面反射による色は 白
shininess :       64              // 輝度は             64 (中間値)

コンストラクターでは、lighitngEnable (照明を有効にするかどうか) は、常に「有効」です。

Material には次のようなメソッドがあり、色、輝度、照明の有効/無効がそれぞれ設定できます。設定以外に取得のメソッド (get メソッド) もあります。

メソッド (一部)

public final void setAmbientColor(Color3f color)

public final void setAmbientColor(float r, // red   (赤)
                                  float g, // green (緑)
                                  float b) // blue  (青)

public final void setEmissiveColor(Color3f color)

public final void setEmissiveColor(float r, // red   (赤)
                                   float g, // green (緑)
                                   float b) // blue  (青)

public final void setDiffuseColor(Color3f color)

public final void setDiffuseColor(float r, // red   (赤)
                                  float g, // green (緑)
                                  float b) // blue  (青)

public final void setSpecularColor(Color3f color)

public final void setSpecularColor(float r, // red   (赤)
                                   float g, // green (緑)
                                   float b) // blue  (青)

public final void setShininess(float shininess)

public final void setLightingEnable(boolean state)

setLightingEnable() メソッドで、Material に対する照明の有効/ 無効を設定できます。

■■javax.media.j3d.Appearance

Material は、そのまま Shape3D に設定することはできません。Shape3D に設定するために必要なのが javax.media.j3d.Appearance です。

クラス継承

java.lang.Object
  |
  +--javax.media.j3d.SceneGraphObject
        |
        +--javax.media.j3d.NodeComponent
              |
              +--javax.media.j3d.Appearance

クラス宣言

public class Appearance
extends NodeComponent

コンストラクター

public Appearance()

Shape3DMaterialを設定するには次のような手順が必要です。

  1. Material を生成する
  2. Material に必要な値を設定する
  3. Appearance を生成する。
  4. MaterialsetMaterial() メソッドで Appearance に設定する
  5. AppearancesetAppearance() メソッドで Shape3D に設定する

AppearanceGeometry と共に、Shape3Dに設定される重要なクラスです。Appearance は物体の外見を決定するための重要な機能を数多く持っています。Appearance については改めて詳しく取り上げます。

■■拡散反射による色 (diffuseColor)

MaterialsetDiffuseColor() メソッドは、Shape3D の物体の拡散反射による色を設定します。通常我々は、拡散反射による色を物体の色として認識しています。

MaterialTest.java (一部)

28   private BranchGroup createSceneGraph() {
29     BranchGroup root = new BranchGroup();
30     
31     DirectionalLight light =
32       new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f),
33                             new Vector3f(-0.5f, -0.5f, -0.7f) );
34     BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
35     light.setInfluencingBounds(bounds);
36     root.addChild(light);
37 
38     Material mat = new Material();..........................(1)
39     mat.setDiffuseColor( new Color3f(0.0f, 0.0f, 0.8f) );...(2)
40     Appearance ap = new Appearance();.......................(3)
41     ap.setMaterial(mat);....................................(4)
42 
43     Sphere sphere = new Sphere(0.6f, ap);...................(5)
44     root.addChild(sphere);
45     
46     return root;
47   }

(1)で Material を生成しています。

(2)で setDiffuseColor() メソッドを使って、拡散反射による色を青に設定しています。

(3)で Appearance を生成し、(4)で setMaterial() メソッドを使って Material を設定しています。

(5)で Sphere を生成しています。Sphere のコンストラクターには Appearance を設定できるものがあります。ここではそのコンストラクターを使って、SphereAppearance を設定しています。

このプログラムの実行結果は次のようになります。

MaterialTest1.gif

斜めから白色光で照明された球体が、青色になっているのがわかると思います。

球体の最も明るい部分 (ハイライト) は白く光っています。この色は setSpecularColor() で設定することができます。

物体の暗い部分は黒になっています。暗い部分の色は setAmbientColor() メソッドで設定することができます。。

■■環境光による色 (ambientColor)

環境光による色は、周囲に反射した間接光で照明された物体の色です。

Java 3D では、環境光による色はjavax.media.j3d.AmbientLight が無いと描画されません。

MaterialTest.java (一部)
28   private BranchGroup createSceneGraph() {
29     BranchGroup root = new BranchGroup();
30     
31     BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
32 
33     DirectionalLight dlight =
34       new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f),
35                             new Vector3f(-0.5f, -0.5f, -0.7f) );
36     dlight.setInfluencingBounds(bounds);
37     root.addChild(dlight);
38 
39     AmbientLight alight = new AmbientLight(); ┐
40     alight.setInfluencingBounds(bounds);      ├(1)
41     root.addChild(alight);                    ┘
42 
43     Material mat = new Material();..........................(2)
44     mat.setDiffuseColor( new Color3f(0.0f, 0.0f, 0.8f) );
45     mat.setAmbientColor( new Color3f(0.2f, 0.0f, 0.0f) );...(3)
46     Appearance ap = new Appearance();.......................(4)
47     ap.setMaterial(mat);....................................(5)
48 
49     Sphere sphere = new Sphere(0.6f, ap);...................(6)
50     root.addChild(sphere);
51     
52     return root;
53   }

(1)で AmbientLight を生成して BranchGroupaddChild() しています。

(2)で Material を生成し、(3)で setAmbientColor() メソッドを使って暗い赤色を設定しています。

(4)で Appearance を生成し、(5)で setMaterial() メソッドで Material を設定しています。

(6)でコンストラクターの引数に Appearance を指定して Sphere を生成しています。

このプログラムの実行結果は次のようになります。

MaterialTest2.gif

直接光で照明されている部分は青色になっています。直接光で照明されていない部分は暗い赤色になっているのがわかると思います。

■■輝度 (shininess)

setShininess() メソッドは、鏡面反射による物体のハイライト部分の強度を設定します。

輝度を変化してテストできるように、アプレットに java.awt.TextFieldjava.awt.Scrollbar を追加してみました。はじめにプログラムの実行結果を見てください。

MaterialTest3.gif

TextFieldshininess を数値入力して [ENTER] キーを押すか、Scrollbar をスライ ドさせて shininess を変化させることができます。

輝度は1.0〜128.0の範囲で変化します。
128.0のとき輝度が最大です。 このとき光が最も収束した状態になり、光束が細くなります。
1.0のとき輝度が最小です。 このとき光が最も拡散した状態になり、光束が太くなります。

このプログラムのソースは次の通りです。

MaterialTest.java
 1  // Java 3D Test Applet
 2  // MaterialTest.java
 3  //   Copyright (c) 1999 ENDO Yasuyuki
 4  //                      mailto:yasuyuki@javaopen.org
 5  //                      http://www.javaopen.org/j3dbook/index.html
 6  
 7  import java.applet.*;
 8  import java.awt.*;
 9  import java.awt.event.*;
10  import javax.media.j3d.*;
11  import javax.vecmath.*;
12  import com.sun.j3d.utils.applet.MainFrame;
13  import com.sun.j3d.utils.universe.SimpleUniverse;
14  import com.sun.j3d.utils.geometry.Sphere;
15  
16  public class MaterialTest extends Applet {
17    private Material material = new Material();.......................................(1)
18  
19    private Scrollbar scrollbar = new Scrollbar(Scrollbar.HORIZONTAL, 1, 1, 1, 129); ┬(2)
20    private TextField textField = new TextField("1.0");                              ┘
21  
22  
23    public MaterialTest() {
24      this.setLayout(new BorderLayout());
25  
26      Panel panel = new Panel();
27      panel.setLayout(new BorderLayout());
28      this.add(panel, BorderLayout.SOUTH);
29      
30      scrollbar.addAdjustmentListener( new AdjustmentListener() {  ┐
31        public void adjustmentValueChanged(AdjustmentEvent e) {    │
32          float value = (float)e.getValue();...................(4) │
33          material.setShininess(value);                            ├(3)
34          textField.setText( Float.toString(value) );              │
35        }                                                          │  
36      });                                                          │    
37      panel.add(scrollbar, BorderLayout.CENTER);                   ┘
38  
39      textField.addActionListener(new ActionListener() {         ┐
40        public void actionPerformed(ActionEvent e) {             │
41          float value = 1.0f;                                    │   
42          try {                                                  │
43            value = Float.parseFloat(e.getActionCommand()); ┐    │
44            if (value < 1.0f) value = 1.0f;                 ├(6) │
45            if (value > 128.0f) value = 128.0f;             ┘    ├(5)
46            material.setShininess(value);.....................(7) │
47            scrollbar.setValue( (int)value );                    │
48            System.out.println("value=" + value);                │
49          } catch (NumberFormatException ex) {}                  │
50        }                                                        │
51      });                                                        ┘
52      panel.add(textField, BorderLayout.WEST);
53      
54      GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
55      Canvas3D canvas = new Canvas3D(config);
56      add(canvas, BorderLayout.CENTER);
57      
58      SimpleUniverse universe = new SimpleUniverse(canvas);
59      universe.getViewingPlatform().setNominalViewingTransform();
60      
61      BranchGroup scene = createSceneGraph();
62      universe.addBranchGraph(scene);
63    }
64    
65    private BranchGroup createSceneGraph() {
66      BranchGroup root = new BranchGroup();
67      
68      DirectionalLight light =
69        new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f),
70                              new Vector3f(-0.5f, -0.5f, -0.7f) );
71      BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
72      light.setInfluencingBounds(bounds);
73      root.addChild(light);
74  
75      material.setCapability(Material.ALLOW_COMPONENT_WRITE);......(8)
76      material.setDiffuseColor( new Color3f(0.0f, 0.0f, 0.8f) );...(9)
77      material.setShininess(1.0f);
78      Appearance ap = new Appearance();
79      ap.setMaterial(material);
80  
81      Sphere sphere = new Sphere(0.6f, ap);
82      root.addChild(sphere);
83      
84      return root;
85    }

(1)でMaterial 型の private 変数 material を確保し、Material オブジェクトを生成してこの変数に代入しています。materialTextFiled, Scrollbar で輝度を変化させるときに使用します。

(2)で ScrollBar, TextFiled を生成しています。

(3)は Scrollbar の値が変化したときに使用するイベント処理を記述しています。

イベント処理の記述についての詳しい説明は省きますが、(4)でMaterialsetShininess() メソッドを使用しています。ここでは Scrollbar の変化量を輝度として設定しています。

(5)では TextField への入力で [ENTER] キーが押されたときのイベント処理を記述しています。

(6)で TextField に入力された文字列を float の値に変換しています。

(7)で setShininess() メソッドを使って、入力値を輝度として設定しています。

(8)で setCapability() メソッドを使って、定数値 Material.ALLOW_COMPONENT_WRITE を設定しています。

実行時に値を変更する場合などには、CapabilityBit というものをあらかじめ設定しておく必要があります。このために使用するのが setCapability() メソッドです。これについては後で詳しく説明します。

(9)で setShininess() メソッドで輝度を 1.0f に設定しています。

このプログラムを実行して、輝度をいろいろ変化させてみてください。shininessのデフォルト値の 64.0 ではどうなるでしょうか?

■■鏡面反射による色 (speculerColor)

setSpecularColor() メソッドは、物体の一番輝度の高い部分 (ハイライト部分) の鏡面反射による色を設定します。

MaterialTest.java (一部)
28	  private BranchGroup createSceneGraph() {
29	    BranchGroup root = new BranchGroup();
30	    
31	    DirectionalLight light =
32	      new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f),
33	                            new Vector3f(-0.5f, -0.5f, -0.7f) );
34	    BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
35	    light.setInfluencingBounds(bounds);
36	    root.addChild(light);
37	
38	    Material mat = new Material();
39	    mat.setDiffuseColor( new Color3f(0.0f, 0.0f, 0.8f) );
40	    mat.setSpecularColor( new Color3f(1.0f, 0.0f, 0.0f) );...(1)
41	    //mat.setShininess(1.0f);
42	    mat.setShininess(12.0f);.................................(2)
43	    //mat.setShininess(128.0f);
44	    Appearance ap = new Appearance();
45	    ap.setMaterial(mat);
46	
47	    Sphere sphere = new Sphere(0.6f, ap);
48	    root.addChild(sphere);
49	    
50	    return root;
51	  }

(1)で setSpecularColor() メソッドを使って、鏡面反射による色を赤に設定しています。

(2)で輝度を 12.0f に設定しています。

このプログラムの実行結果は次のようになります。

MaterialTest4.gif

今回はハイライト部分を強調したかったので赤色を設定しました。

■■発光による色 (emissiveColor)

setEmissiveColor() メソッドは、それ自体発光している物体の発光色を設定します。
MaterialTest.java (一部)
28	  private BranchGroup createSceneGraph() {
29	    BranchGroup root = new BranchGroup();
30	    
31	    //DirectionalLight light =                                          ┐
32	    //  new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f),            │
33	    //                        new Vector3f(-0.5f, -0.5f, -0.7f) );      ├(1)
34	    //BoundingSphere bounds = new BoundingSphere(newPoint3d(), 100.0);  │ 
35	    //light.setInfluencingBounds(bounds);                               │ 
36	    //root.addChild(light);                                             ┘
37	
38	    Material mat = new Material();
39	    mat.setEmissiveColor( new Color3f(0.0f, 0.0f, 0.8f) );...(2)
40	    Appearance ap = new Appearance();
41	    ap.setMaterial(mat);
42	
43	    Sphere sphere = new Sphere(0.6f, ap);
44	    root.addChild(sphere);
45	    
46	    return root;
47	  }

(1)は DirectionalLight の生成から addChild() までを行っていますが、すべてコメントにしてあります。つまり、このプログラムでは光源を使用していません。

(2)でsetEmissiveColor() メソッドで発光色を青に設定しています。

このプログラムの実行結果は次のようになります。

全体が青色になって、それ自体発光しているような感じになっています。球体の細部はよくわかりません。

■■Material のまとめ

Material で設定できる色、輝度をすべて使ったサンプルを紹介します。

MaterialTest6.gif

左上の TextField, ScrollbardiffuseColor を変更できます。上から r (赤), g (緑), b (青) です。

右上の TextField, ScrollbaremissiveColor を変更できます。上から r (赤), g (緑), b (青) です。

左下の TextField, ScrollbarspecularColor, shininess を変更できます。上から r (赤), g (緑), b (青), shininess (輝度) です。

右下の TextField, Scrollbar, CheckboxambientColor を変更できます。上から r (赤), g (緑), b (青) です。一番下の Checkbox を使って AmbientLight を ON/OFF できます。

このプログラムのソースは次の通りです。GUIの構築が大半なので、詳しい解説は省略します。

ソースコード (MaterialTest.java )

■プリミティブ以外の物体での法線の指定(GeometryArray#setNormls())

さてこれまで、法線ベクトルについて触れずに説明して来ました。

法線ベクトルとは何でしょうか?

光源がポリゴンを照明するとき、ポリゴンの表面の「明るさ」を計算するためには、ポリゴンの「向き」についての情報が必要になります。

ポリゴンの「向き」を設定するために、法線ベクトルを使用します。

Java 3D では、頂点ごとに法線ベクトルを設定します。法線ベクトルを設定するために、javax.media.GeometryArray には setNormal(), setNormals() があります。

法線ベクトルを設定するメソッド (javax.media.j3d.GeometryArray)

public final void setNormal(int index,      // 法線を設定する頂点 index
                            float[] normal) // 法線ベクトル { x, y, z }

public final void setNormal(int index,       // 法線を設定する頂点 index
                            Vector3f normal) // 法線ベクトル

public final void setNormals(int index,       // 法線の設定を開始する頂点 index
                             float[] normals) // 法線ベクトル配列 { x0, y0, z0, x1, y1, z1, ... }

public final void setNormals(int index,          // 法線の設定を開始する頂点 index
                             Vector3f[] normals) // 法線ベクトル配列 { n0, n1, ... }

public final void setNormals(int index,       // 法線の設定を開始する頂点 index
                             float[] normals, // 法線ベクトル配列 { x0, y0, z0, x1, y1, z1, ... }
                             int start,       // 設定を開始する法線の index 
                             int length)      // 設定を終了する法線の index                      

public final void setNormals(int index,          // 法線の設定を開始する頂点 index                  
                             Vector3f[] normals, // 法線ベクトル配列 { n0, n1, ... }
                             int start,          // 設定を開始する法線の index                      
                             int length)         // 設定を終了する法線の index                      

法線ベクトルを設定する場合、GeometryArray は次のような手順で使用します。

  1. 頂点配列を用意する
  2. 法線ベクトルの配列を用意する
  3. GeometryArray.NORMALS を指定して GeometryArray を生成する
  4. setVertices() メソッドで頂点配列を設定する
  5. setNormals() メソッドで法線ベクトルを設定する
  6. GeometryArray を指定して Shape3D を生成する
  7. Shape3DaddChild() する

今までと違う点は、GeometryArray のコンストラクターで GeometryArray.NORMALS を指定する点と、setNormals() メソッドで法線ベクトルを指定する点の 2点です。

■■ポリゴンの頂点にそれぞれ別の法線ベクトルを設定する

法線ベクトルは照明効果にどのような影響を与えるでしょうか。

三角形ポリゴンの頂点に、それぞれ異なった法線ベクトルを設定して確かめてみましょう。

以上のような前提でプログラミングしてみます。

NormalTest.java (一部)

 54   private BranchGroup createSceneGraph() {
 55     BranchGroup root = new BranchGroup();
 56     root.setCapability(BranchGroup.ALLOW_DETACH);
 57 
 58     root.addChild( createLight() );
 59 
 60     Point3d[] vertices = new Point3d[3];
 61     
 62     vertices[0] = new Point3d(-0.4, -0.8, 0.0); // 左下の頂点
 63     vertices[1] = new Point3d(0.5, -0.4, 0.0);  // 右下の頂点
 64     vertices[2] = new Point3d(-0.6, 0.7, -0.4); // 左上の頂点
 65     
 66     Vector3f[] normals = new Vector3f[3];                          ┐
 67                                                                    │
 68     normals[0] = new Vector3f(0.0f, 0.0f, 1.0f);    // 視点に垂直    ├(1)
 69     normals[1] = new Vector3f(0.1f, 0.1f, 0.89f);   // ほぼ視点に垂直 │
 70     normals[2] = new Vector3f(-0.63f, 0.63f, 0.1f); // かなり斜め    ┘
 71     
 72     TriangleArray geometry =                          
 73       new TriangleArray( vertices.length,             
 74                          GeometryArray.COORDINATES |  
 75                          GeometryArray.NORMALS);...........(2)
 76     geometry.setCoordinates(0, vertices);..................(3)
 77     geometry.setNormals(0, normals);.......................(4)
 78     
 79     Shape3D shape = new Shape3D(geometry);
 80     shape.setAppearance( createAppearance() );
 81     
 82     root.addChild(shape);
 83 
 84     NormalRender nrender = new NormalRender(geometry, 0.5f); // 法線を視覚化 ...(5)
 85     nshape = new Shape3D( nrender.getLineArray() );..........................(6)
 86     root.addChild(nshape);...................................................(7)
 87 
 88     return root;
 89   }
 90 
 91   private Light createLight() {
 92     DirectionalLight light =
 93       new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f),
 94                             new Vector3f(0.0f, 0.0f, -1.0f) ); // ヘッドライト
 95     BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
 96     light.setInfluencingBounds(bounds);
 97     
 98     return light;
 99   }
100 
101   private Appearance createAppearance() {
102     Material mat = new Material();
103     mat.setDiffuseColor( new Color3f(0.0f, 0.0f, 1.0f) );
104     mat.setShininess(128.0f);
105     
106     Appearance ap = new Appearance();
107     ap.setMaterial(mat);
108     
109     return ap;
110   }

(1)で法線ベクトルを生成し、各要素を初期化しています。左下の頂点には視点に垂直な法線、右下の頂点には視点にほぼ垂直な法線、左上の頂点にはかなり斜めな法線を設定しました。

(2)で TraingleArray を生成しています。2番目の引数には、GeometryArray.COORDINATES と OR して GeometryArray.NORMALS を指定しています。

(3)で setNormals() メソッドを使って法線ベクトルを設定しています。

(4)で法線ベクトルを視覚化するために、NormalRender を生成して使用しています。この NormalRender クラスは法線を視覚化する目的で書いたものです。各頂点から法線ベクトル方向に引かれた線分を生成します。

(5)で NormalRender を生成しています。最初の引数は、法線の視覚化の対象となる GeometryArray です。2番目の引数は法線ベクトルを視覚化したときのスケールです。法線ベクトルの長さが 1.0 では視野に収まらないので 0.5 倍しています。

(6)でNormalRendergetLineArray() メソッドを使って、法線ベクトルから生成された LineArray を取得しています。取得した LineArray をコンストラクター引数にして Shape3D を生成しています。

生成した Shape3D を(7)でaddChild() しています。

このプログラムの実行結果は次のようになります。

NormalTest1.gif

チェックボックスを押すと、法線ベクトルを視覚化した LineArray を削除したり追加したりすることができます。

チェックボックスが押されたときに、法線ベクトルを視覚化した LineArray を動的に追加/削除するには次のようにしました。

追加は次のようにします。

  1. Group#numChildren() で"子"ノードの数を取得してforループを開始
  2. Group#getChild(int)で"子"ノードを取得し、追加したいノードと比較する
  3. ノードがすでに追加されていたらループを終了し、何も処理せずに終了する
  4. ノードが追加されていなければGroup#addChild(Node)で追加する

ここでは次のようなメソッドとして独立させました。

112    private void addChild(Group group, Node node) {
113      for (int i=0; i<group.numChildren(); i++) if (node == group.getChild(i)) return;
114      group.addChild(node);
115    }

削除は次のようにします。

  1. Group#numChildren() で"子"ノードの数を取得してforループを開始
  2. Group#getChild(int)で"子"ノードを取得し、追加したいノードと比較する
  3. ノードがすでに追加されていたらGroup#removeChild(int)で削除する

ここでは次のようなメソッドとして独立させました。

117    private void removeChild(Group group, Node node) {
118      for (int i=0; i<group.numChildren(); i++) {
119        if (node == group.getChild(i)) group.removeChild(i);
120      }
121    }

ツリーが"live"状態のときにはノードを動的に追加/削除できないので、次のような方法でツリーを一時的に切り離します。

  1. Locale#removeBranchGraph(BranchGroup)でツリーを切り離す
  2. 切り離したツリーにノードを追加/削除する
  3. Locale#addBranchGraph(BranchGroup)でツリーを再接続する

ここでは次のように処理しました。

 26     Checkbox check = new Checkbox("Normals", true);
 27     check.addItemListener( new ItemListener() {
 28       public void itemStateChanged(ItemEvent e) {
 29         int state = e.getStateChange();
 30         universe.getLocale().removeBranchGraph(scene);
 31         if (state == ItemEvent.SELECTED) {
 32           addChild(scene, nshape);
 33         } else if (state == ItemEvent.DESELECTED) {
 34           removeChild(scene, nshape);
 35         }
 36         universe.addBranchGraph(scene);
 37       }
 38     });
 39     panel.add(check);

SimpleUniverse#getLocale()Localeを取得し、removeBranchGraph()を実行しています。

チェックボックスの状態に応じて追加/削除を行い、SimpleUniverse#addBranchGraph()でツリーを再接続しています。

NormalTest1_off.gif

左下の頂点の法線ベクトルは視点 (と光源) に垂直なので、白く反射しているのがわかると思います。

右下の頂点の法線ベクトルは視点 (と光源) にぼほ垂直なので、明るい青色になっています。

左上の頂点の法線ベクトルはかなり斜めなので、暗い青色になっています。

頂点間では明るさがスムースに補間されています。

NormalRender のソースは次の通りです。

NormalRender.java
 1 // Java 3Dテスト用プログラム
 2 // NormalRedner.java
 3 //   Copyright (c) 1999 ENDO Yasuyuki
 4 //                      mailto:yasuyuki@javaopen.org
 5 //                      http://www.javaopen.org/j3dbook/index.html
 6 
 7 import javax.media.j3d.*;
 8 import javax.vecmath.*;
 9 
10 public class NormalRender {
11   private LineArray nline = null;
12   public NormalRender(GeometryArray geom) { this(geom, 1.0f); }
13   public NormalRender(GeometryArray geom, float scale) {
14     Point3f[] vertices = new Point3f[geom.getVertexCount()];
15     Vector3f[] normals = new Vector3f[geom.getVertexCount()];
16     for (int i=0; i<geom.getVertexCount(); i++) {
17       vertices[i] = new Point3f();
18       normals[i] = new Vector3f();
19     }
20     geom.getCoordinates(0, vertices);
21     geom.getNormals(0, normals);
22     Point3f[] nvertices = new Point3f[vertices.length * 2];
23     int n = 0;
24     for (int i=0; i<vertices.length; i++ ){
25       nvertices[n++] = new Point3f( vertices[i] );
26       nvertices[n++] = new Point3f( vertices[i].x + scale * normals[i].x,
27                                     vertices[i].y + scale * normals[i].y,
28                                     vertices[i].z + scale * normals[i].z );
29     }
30     nline = new LineArray(nvertices.length, GeometryArray.COORDINATES);
31     nline.setCoordinates(0, nvertices);
32   }
33 
34   public LineArray getLineArray() { return nline; }
35 }

詳しい解説は省略しますが、コンストラクターの引数として受け取った GeometryArray から頂点配列と法線ベクトルを取り出し、それを元にして LineArray を生成しています。

メソッドは getLineArray() だけです。法線ベクトルから生成した LineArray を返します。

■■ポリゴンの頂点すべてに同じ法線ベクトルを設定する

前の例では、任意の法線ベクトルを設定してみました。法線ベクトルを計算する方法にはどんなものがあるでしょうか。

平面の法線ベクトルを計算するには、平面上の 3点の座標からベクトルの外積を求める方法があります。

平面上の 3点の座標をそれぞれ (x0, y0, z0), (x1, y1, z1), (x2, y2, z2) とすると、

ax =  x2 - x1 
ay =  y2 - y1 
az =  z2 - z1 

bx =  x0 - x1 
by =  y0 - y1 
bz =  z0 - z1 

外積は次のように求められます。

nx = ay・bz - az・by
ny = az・bx - ax・bz
nz = ax・by - ay・bx

求めた外積 (法線ベクトル) を正規化します。

length = √nx2 + ny2 + nz2

Nx = nx / length
Ny = ny / length
Nz = nz / length

javax.vecmath.Vector3f には normalize() というメソッドがあり、ベクトルを正規化することができます。

NomralTest.java (一部)
 54   private BranchGroup createSceneGraph() {
 55     BranchGroup root = new BranchGroup();
 56     root.setCapability(BranchGroup.ALLOW_DETACH);
 57 
 58     root.addChild( createLight() );
 59 
 60     Point3d[] vertices = new Point3d[3];
 61     
 62     vertices[0] = new Point3d(-0.4, -0.8, 0.0);
 63     vertices[1] = new Point3d(0.5, -0.4, 0.0);
 64     vertices[2] = new Point3d(-0.6, 0.7, -0.4);
 65     
 66     Vector3f faceNormal = calcFaceNormal(vertices); // 面法線の計算 ...(1)
 67 
 68     Vector3f[] normals = new Vector3f[3];...........................(2)
 69     
 70     for (int i=0; i<normals.length; i++) normals[i] = faceNormal;...(3)
 71     
 72     TriangleArray geometry =
 73       new TriangleArray( vertices.length,
 74                          GeometryArray.COORDINATES |
 75                          GeometryArray.NORMALS);
 76     geometry.setCoordinates(0, vertices);
 77     geometry.setNormals(0, normals);
 78     
 79     Shape3D shape = new Shape3D(geometry);
 80     shape.setAppearance( createAppearance() );
 81     
 82     root.addChild(shape);
 83 
 84     NormalRender nrender = new NormalRender(geometry, 0.5f);
 85     nshape = new Shape3D(nrender.getLineArray());
 86     root.addChild(nshape);
 87 
 88     return root;
 89   }
          :
          :
101   private Vector3f calcFaceNormal(Point3d[] vertices) { ...(4)
102     double ax = vertices[2].x - vertices[1].x;
103     double ay = vertices[2].y - vertices[1].y;
104     double az = vertices[2].z - vertices[1].z;
105     double bx = vertices[0].x - vertices[1].x;
106     double by = vertices[0].y - vertices[1].y;
107     double bz = vertices[0].z - vertices[1].z;
108     
109     double nx = ay * bz - az * by;
110     double ny = az * bx - ax * bz;
111     double nz = ax * by - ay * bx;
112 
113     Vector3f normal = new Vector3f( (float)nx,
114                                     (float)ny,
115                                     (float)nz );
116     normal.normalize();
117 
118     //DEBUG
119     System.out.println("nx=" + normal.x + ", ny=" + normal.y + ", nz=" + normal.z);
120 
121     return  normal;
122   }

(1)で calcFaceNormal() メソッドを使って法線ベクトルを計算しています。

(2)で法線ベクトルの配列を確保し、(3)ですべての頂点に同じ法線ベクトルを設定しています。

(4)は外積を使って面法線を計算する calcFaceNormal() メソッドです。平面上の 3点の頂点配列を引数にとり、法線ベクトルを返します。

このプログラムの実行結果は次のようになります。

NormalTest2.gif

平面が同じ青色に描画されているのがわかると思います。

ポリゴンがひとつだと立体的な描画効果がよくわからないので、複数のポリゴンの面法線を計算して描画してみましょう。

NormalTest3_off.gif

この例では、ポリゴン数 = 32 の複数ポリゴンについて面法線を計算し、描画しています。

法線ベクトルを視覚化すると次のようになります。

NormalTest3.gif

このプログラムのソースは次の通りです。

NormalTest.java (一部)
 55   private BranchGroup createSceneGraph() {
 56     BranchGroup root = new BranchGroup();
 57     root.setCapability(BranchGroup.ALLOW_DETACH);
 58 
 59     root.addChild( createLight() );
 60 
 61     Point3d[] vertices = new Point3d[96];         ┐
 62                                                   │
 63     vertices[0]  = new Point3d(-0.4,  0.4, -0.7); │
 64     vertices[1]  = new Point3d(-0.4,  0.2, -0.4); │
 65     vertices[2]  = new Point3d(-0.2,  0.4, -0.4); ├(1)
          :                                           │
          :                                           │
205     vertices[93] = new Point3d( 0.4, -0.2, -0.4); │
206     vertices[94] = new Point3d( 0.2, -0.4, -0.4); │
207     vertices[95] = new Point3d( 0.4, -0.4, -0.7); ┘
208 
209     Vector3f[] normals = calcFaceNormals(vertices);...(2)
210     
211     TriangleArray geometry =
212       new TriangleArray( vertices.length,
213                          GeometryArray.COORDINATES |
214                          GeometryArray.NORMALS);
215     geometry.setCoordinates(0, vertices);
216     geometry.setNormals(0, normals);
217     
218     Shape3D shape = new Shape3D(geometry);
219     shape.setAppearance( createAppearance() );
220     
221     root.addChild(shape);
222 
223     NormalRender nrender = new NormalRender(geometry, 0.2f);
224     nshape = new Shape3D(nrender.getLineArray());
225     root.addChild(nshape);
226 
227     return root;
228   }
          :
          :
240   private Vector3f[] calcFaceNormals(Point3d[] vertices) { ...(3)
241     Vector3f[] normals = new Vector3f[vertices.length];
242     
243     for (int i=0; i<vertices.length; i+=3) {
244       Vector3f fnormal = calcFaceNormal(i, vertices);
245       normals[i] = fnormal;
246       normals[i + 1] = fnormal;
247       normals[i + 2] = fnormal;
248     }
249     
250     return normals;
251   }
252   
253   private Vector3f calcFaceNormal(int index, Point3d[] vertices) { ...(4)
254     double ax = vertices[index + 2].x - vertices[index + 1].x;
255     double ay = vertices[index + 2].y - vertices[index + 1].y;
256     double az = vertices[index + 2].z - vertices[index + 1].z;
257     double bx = vertices[index + 0].x - vertices[index + 1].x;
258     double by = vertices[index + 0].y - vertices[index + 1].y;
259     double bz = vertices[index + 0].z - vertices[index + 1].z;
260     
261     double nx = ay * bz - az * by;
262     double ny = az * bx - ax * bz;
263     double nz = ax * by - ay * bx;
264 
265     Vector3f normal = new Vector3f( (float)nx,
266                                     (float)ny,
267                                     (float)nz );
268     normal.normalize();
269 
270     //DEBUG
271     System.out.println("nx=" + normal.x + ", ny=" + normal.y + ", nz=" + normal.z);
272 
273     return  normal;
274   }

(1)で頂点配列を確保し、各頂点の座標を初期化しています。

(2)で calcFaceNormals() メソッドを使って各ポリゴンの法線ベクトルを計算しています。

(3)は複数ポリゴンの法線ベクトルを計算する calcFaceNormals() メソッドです。

(4)はひとつのポリゴンの法線ベクトルを計算する calcFaceNormal() メソッドです。

■■複数ポリゴンで形成された物体をスムースに描画するには?

実行結果でわかると思いますが、複数ポリゴンで形成された物体をスムースに描画するには、面法線を計算しただけでは不十分のようです。

法線を平均してスムースに描画するには、次のような方法があります。

  1. まず面法線を計算し、各頂点の法線ベクトルを求めます。
  2. 次に同じ頂点の法線ベクトルを平均し、平均した法線ベクトルを各頂点に設定します。

■■法線を計算するには?(GeometryInfoNormaiGenerator)

Java 3D には法線ベクトルを計算してくれる便利なクラスが用意されています。com.sun.j3d.unils.geometry.GeometryInfocom.sun.j3d.utils.geometry.NormalGenerator です。


クラス宣言

public class GeometryInfo
extends java.lang.Object

コンストラクター

public GeometryInfo(int primitive)

フィールド

public static final int TRIANGLE_ARRAY       // TriangleArray
public static final int QUAD_ARRAY           // QuadArray
public static final int TRIANGLE_FAN_ARRAY   // TriangleFanArray
public static final int TRIANGLE_STRIP_ARRAY // TriangleStripArray
public static final int POLYGON_ARRAY        // 複数等高線で形成された面

GeometryInfo のコンストラクターでは、GeometryInfo のフィールドで定義された定数値を指定します。

定数値はそれぞれ TriangleArray, QuarArray, TriangleFanArray, TriangleStripArray などに対応しています。のうち、どの GeometryArray を使うのかを指定します。

POLYGON_ARRAY を指定したとき、複数の等高線から形成された面を定義することができます。

メソッド (一部)

public void setCoordinates(Point3f[] coordinates)

public void setCoordinates(Point3d[] coordinates)

GeometryInfo を生成し、setCoordinates() メソッドで頂点配列を設定します。コンストラクターの引数で指定した GeometryArray が生成されます。

メソッド (一部)
public GeometryArray getGeometryArray()

public IndexedGeometryArray getIndexedGeometryArray(boolean compact)

生成された GeometryArray は、getGeometryArray(), getIndexedGeomeryArray() メソッドを使って取得することができます。

ここでは詳しく説明しませんが、GeometryInfo はこの他にも便利な機能を数多く持っています。

GeometryInfo を元に、頂点法線を計算するクラスが com.sun.j3d.utils.geometry.NormalGenerator です。


クラス宣言

public class NormalGenerator
extends java.lang.Object

コンストラクター

public NormalGenerator()

メソッド (一部)

public void generateNormals(GeometryInfo geom)

generateNormals() メソッドは、引数で指定された GeometryInfo の頂点法線を計算し、GeometryInfo に計算した法線をセットします。

GeometryInfoNormalGenerator の使い方をまとめると次のようになります。

  1. 頂点配列を用意する
  2. GeometryInfo を生成する
  3. GeometryInfosetCoordinates() メソッドで頂点配列を設定する
  4. NormalGenerator を生成する
  5. NormalGereratorgenerateNormals() メソッドで法線ベクトルを生成する。生成された法線ベクトルは NormalGenerator にセットされる
  6. GeometryInfo getGeometry() メソッドで Geometry を取得し、取得した Geometry を元に Shape3D を生成する
  7. 生成した Shape3D addChild() する

では、前の例と全く同じ複数ポリゴンの頂点法線を計算して描画してみましょう。

NormalTest.java (一部)
 57   private BranchGroup createSceneGraph() {
 58     BranchGroup root = new BranchGroup();
 59     root.setCapability(BranchGroup.ALLOW_DETACH);
 60 
 61     root.addChild( createLight() );
 62 
 63     Point3d[] vertices = new Point3d[96];
 64     
 65     vertices[0]  = new Point3d(-0.4,  0.4, -0.7);
 66     vertices[1]  = new Point3d(-0.4,  0.2, -0.4);
 67     vertices[2]  = new Point3d(-0.2,  0.4, -0.4);
          :
          :
207     vertices[93] = new Point3d( 0.4, -0.2, -0.4);
208     vertices[94] = new Point3d( 0.2, -0.4, -0.4);
209     vertices[95] = new Point3d( 0.4, -0.4, -0.7);
210 
211     GeometryInfo ginfo = new GeometryInfo(GeometryInfo.TRIANGLE_ARRAY);...(1)
212     ginfo.setCoordinates(vertices);.......................................(2)
213     
214     NormalGenerator gen = new NormalGenerator();..........................(3)
215     gen.generateNormals(ginfo);...........................................(4)
216 
217     Shape3D shape = new Shape3D(ginfo.getGeometryArray());
218     shape.setAppearance( createAppearance() );
219     
220     root.addChild(shape);
221 
222     NormalRender nrender = new NormalRender(ginfo.getGeometryArray(), 0.2f);
223     nshape = new Shape3D(nrender.getLineArray());
224     root.addChild(nshape);
225 
226     return root;
227   }

(1)で GeometryInfo.TRAINGLE_ARRAY を指定して GeometryInfo を生成しています。

(2)で setCoordinates() メソッドを使って頂点配列を設定しています。

(3)で NormalGererator を生成し、(4)で generateNormals() メソッドを使って頂点法線を計算して GeometryInfo にセットしています。

このプログラムの実行結果は次のようになります。

NormalTest4_off.gif

実際には曲面ではないのですが、頂点法線がうまく平均化されているためになめらかな曲面のように見えます。

NormalGenerator で生成された頂点法線はスムースに平均化されています。

法線ベクトルを視覚化すると次のようになります。

NormalTest4.gif

各頂点の法線ベクトルが平均化されているのが分かると思います。

■霧 (javax.media.j3d.Fog)

javax.media.j3d.Fogを使うと、霧のように遠くがかすんで行く効果が得られます。

クラス継承

java.lang.Object
  |
  +--javax.media.j3d.SceneGraphObject
        |
        +--javax.media.j3d.Node
              |
              +--javax.media.j3d.Leaf
                    |
                    +--javax.media.j3d.Fog
                          |
                          +--javax.media.j3d.ExponentialFog
                          |
                          +--javax.media.j3d.LinearFog

クラス宣言

public abstract class Fog
extends Leaf

コンストラクター

public Fog()

public Fog(Color3f color) // 霧の色

public Fog(float r, // 霧の赤色値
           float g, // 霧の緑色値
           float b) // 霧の青色値

メソッド (一部)

public final void setInfluencingBounds(Bounds region)             // 霧が有効になる領域

public final void setInfluencingBoundingLeaf(BoundingLeaf region) // 霧が有効になる領域リーフ

Fog は抽象クラスで、実際に使われるのはサブクラスの ExponentiolFog, LinearFog です。

Fog のコンストラクターでは霧の色が設定できます。デフォルト値は (0.0, 0.0, 0.0) (黒) です。

Fog は、それが有効になる領域を設定しないと適用されません。これを設定するためのメソッドが setInfluencingBounds(), setInfluencingBoundingLeaf() です。setInfluencingBounds() では javax.media.j3d.Bounds のサブクラス、setInfluencingBoundingLeaf() では javax.media.j3d.BoundingLeaf を指定します。

Fog の使い方は次のようになります。

  1. Fog を生成する
  2. Fog が有効になる領域を設定する
  3. Fog をシーングラフに addChild() する

■■javax.media.j3d.BoundingLeaf

BoundingLeaf は他の Bounds を参照する Leafノードです。

BoundingLeaf のクラス継承

java.lang.Object
  |
  +--javax.media.j3d.SceneGraphObject
        |
        +--javax.media.j3d.Node
              |
              +--javax.media.j3d.Leaf
                    |
                    +--javax.media.j3d.BoundingLeaf

クラス宣言

public class BoundingLeaf
extends Leaf

コンストラクター

public BoundingLeaf()

public BoundingLeaf(Bounds region) // 参照先の Bounds オブジェクト

BoundingLeaf のコンストラクターでは参照先の Bounds オブジェクトを指定します。

BoundingLeafjavax.media.j3d.Leaf のサブクラスなので、addChild() メソッドを使ってシーングラフに追加します。

■■javax.media.j3d.ExponentialFog

javax.media.j3d.ExponentialFog は、霧の強度を設定し、距離に応じた指数関数で霧の効果を計算して霧を描画します。


クラス宣言

public class ExponentialFog
extends Fog

コンストラクター (一部)

public ExponentialFog(Color3f color, // 霧の色
                      float density) // 霧の強度

public ExponentialFog(float r,       // 霧の赤色値
                      float g,       // 霧の緑色値
                      float b,       // 霧の青色値
                      float density) // 霧の強度

ExponentialFog のコンストラクターでは、霧の色と霧の強度を指定できます。

ExponentialFog の霧の効果は次の式で計算されます。

f = e-d・z

f : 霧の係数
d : 霧の強度
z : 視点からピクセルまでの距離

C' = C・f + Cf・(1-f)

C : 霧の適用対象となるピクセルの色
Cf : 霧の色

■■javax.media.j3d.LinearFog

javax.media.j3d.LinearFog は、霧の適用が開始される前方距離と、霧で完全に隠される後方距離を指定して霧を描画します。


クラス宣言

public class LinearFog
extends Fog

コンストラクター (一部)

public LinearFog(Color3f color,        // 霧の色
                 double frontDistance, // 霧の効果が開始される前方距離
                 double backDistance)  // 霧の効果が終了する後方距離  

public LinearFog(float r,              // 霧の赤色値
                 float g,              // 霧の緑色値
                 float b,              // 霧の青色値
                 double frontDistance, // 霧の効果が開始される前方距離
                 double backDistance)  // 霧の効果が終了する後方距離  

LinearFog のコンストラクターでは、霧の色と、霧の適用が開始される前方距離と、霧で完全に覆い隠される後方距離を指定できます。

LinearFog による霧の効果は次の式で計算されます。

     B - z
f = -------
     B - F

B : 霧が適用される前方距離
F : 霧で完全に覆い隠される後方距離
z : 視点からピクセルまでの距離

C' = C・f + Cf・(1-f)

C : 霧の適用対象となるピクセルの色
Cf : 霧の色

ExponentialFog, LinearFog を使ったサンプルの実行結果を見てください。

FogTest1.gif

物体の diffuseColor、ファークリップ面までの距離が変更できます。

CheckboxExponentialFog, LinearFog, または霧が無い状態を切替えられます。
ExponentialFog のときは霧の強度を、LinearFog のときは前方距離、後方距離を入力できます。

霧の色はデフォルト値の黒になっています。霧の色も TextField に RGB を入力して変更出来ます。

サンプルのソースは次の通りです。

FogTest.java (一部)
287    private BranchGroup createSceneGraph() {
288      BranchGroup root = new BranchGroup();
289      root.setCapability(BranchGroup.ALLOW_DETACH);
290  
291      root.addChild(createLight(0.3f, -0.3f, -0.3f));
292      
293      BoundingSphere bounds = new BoundingSphere(new Point3d(), 10000.0);
294      
295      app = createAppearance();
296      
297      double x = -2.0;                                     ┐
298      double y = -2.0;                                     │
299      double z = 2.0;                                      │
300      for (int i=0; i<20; i++) {                           │
301        root.addChild( createSphere(0.5f, x, y, z, app) ); ├(1)
302        x += 0.3 * (double)i;                              │
303        y += 0.3 * (double)i;                              │
304        z -= (double)i;                                    │
305      }                                                    ┘
306  
307      lfog = new LinearFog();..................................(2)
308      lfog.setCapability(Fog.ALLOW_COLOR_READ);
309      lfog.setCapability(Fog.ALLOW_COLOR_WRITE);
310      lfog.setCapability(LinearFog.ALLOW_DISTANCE_WRITE);
311      lfog.setInfluencingBounds(bounds);.......................(3)
312  
313      efog = new ExponentialFog();.............................(4)
314      efog.setCapability(Fog.ALLOW_COLOR_READ);
315      efog.setCapability(Fog.ALLOW_COLOR_WRITE);
316      efog.setCapability(ExponentialFog.ALLOW_DENSITY_WRITE);
317      efog.setInfluencingBounds(bounds);.......................(5)
318  
319      fog = efog;..............................................(6)
320      root.addChild(fog);
321      
322      return root;

サンプルでは 20個の球体を生成してシーンに addChild() しています(1)。

(2)で LinearFog を生成し、(3)で霧が適用される領域を設定しています。

(3)で ExponentialFog を生成し、(4)で霧が適用される領域を設定しています。

(5)で Fog 型の変数 fogBranchGraphaddChild() していますが、(6)で fogExponentialFog を代入しているので、初期状態では ExponentialFog が使われます。
実行中に CheckboxLinearFog を選ぶと LinearFog が使用されるようにしています。

霧の色を変えてみるとわかるのですが、霧の色と背景の色が同じでないと、霧による描画の効果がはっきりわかりません。

背景の色を変えるにはどうしたら良いでしょうか?

Java 3D には背景の色を変えたり、背景に画像を適用したり、背景に別のシーングラフを描画したりするための javax.media.j3d.Background クラスが用意されています。

■背景 (javax.media.j3d.Background)

javax.media.j3d.Background は、背景の色、背景画像、背景用のシーングラフを描画します。

クラス継承

java.lang.Object
  |
  +--javax.media.j3d.SceneGraphObject
        |
        +--javax.media.j3d.Node
              |
              +--javax.media.j3d.Leaf
                    |
                    +--javax.media.j3d.Background

クラス宣言

public class Background
extends Leaf

コンストラクター

public Background()

public Background(Color3f color) // 背景の色

public Background(float r,       // 背景の色の赤色値
                  float g,       // 背景の色の緑色値
                  float b)       // 背景の色の青色値

public Background(ImageComponent2D image) // 背景に使用する画像

public Background(BranchGroup branch)     // 背景に使用するシーングラフ

メソッド (一部)

public final void setApplicationBounds(Bounds region) // 背景が有効になる領域

public final void setApplicationBoundingLeaf(BoundingLeaf region) // 背景が有効になる領域リーフ

Background のコンストラクターでは、背景の色、背景に使用する画像、または背景として描画するシーングラフを指定します。

ここで背景とは、無限遠にあるものを言います。実際には背景は常にいちばん後ろに描画されます。

背景に画像を適用する場合、javax.media.j3d.ImageComponent2D オブジェクトを指定します。ImageComponent2D についてはテクスチャーマッピングのところで詳しく説明します。

Background の使用方法は次の通りです。

  1. 必要なら背景画像に使う ImageComponent2D や、背景に使用するシーングラフを構築した BranchGroup を用意する
  2. Background を生成する。必要なら色を指定する
  3. setInfluencingBounds(), setInfluencingBoundingLeaf() メソッドで適用される領域、または領域リーフを設定する。
  4. Background をシーングラフに addChild() する

サンプルの実行結果を見てください。

FogTest2.gif

この実行例では霧の色と背景の色を同じにしてみました。Fog の効果が良く分かると思います。

サンプルのソースは次の通りです。

FogTest.java (一部)
370    private BranchGroup createSceneGraph() {
371      BranchGroup root = new BranchGroup();
372      root.setCapability(BranchGroup.ALLOW_DETACH);
373      
374      BoundingSphere bounds = new BoundingSphere(new Point3d(), 10000.0);
375  
376      background = createBackground(bounds);...(1)
377      root.addChild(background);...............(2)
378  
379      root.addChild(createLight(0.3f, -0.3f, -0.3f));
380      
381      app = createAppearance();
382      
383      double x = -2.0;
384      double y = -2.0;
385      double z = 2.0;
386      for (int i=0; i<20; i++) {
387        root.addChild( createSphere(0.5f, x, y, z, app) );
388        x += 0.3 * (double)i;
389        y += 0.3 * (double)i;
390        z -= (double)i;
391      }
392  
393      lfog = new LinearFog();
394      lfog.setCapability(Fog.ALLOW_COLOR_READ);
395      lfog.setCapability(Fog.ALLOW_COLOR_WRITE);
396      lfog.setCapability(LinearFog.ALLOW_DISTANCE_WRITE);
397      lfog.setInfluencingBounds(bounds);
398  
399      efog = new ExponentialFog();
400      efog.setCapability(Fog.ALLOW_COLOR_READ);
401      efog.setCapability(Fog.ALLOW_COLOR_WRITE);
402      efog.setCapability(ExponentialFog.ALLOW_DENSITY_WRITE);
403      efog.setInfluencingBounds(bounds);
404  
405      fog = efog;
406      root.addChild(fog);
407      
408      return root;
409    }
410    
411    private Background createBackground(Bounds bounds) {
412      // Texture の生成
413      Image image = null;................................(3)
414      if (this.isStandalone) { ..........................(4)
415        // アプリケーションとして実行されている
416        Toolkit toolkit = Toolkit.getDefaultToolkit(); ┬(5)
417        image = toolkit.getImage("bg.jpg");            ┘
418      } else {
419        // アプレットとして実行されている
420        image = getImage(getCodeBase(), "bg.jpg");
421      }
422      MediaTracker mt = new MediaTracker(this);                                        ┐
423      mt.addImage(image, 0);                                                           ├(6)
424      mt.checkAll(true);                                                               │
425      try { mt.waitForID(0); } catch (InterruptedException e) { e.printStackTrace(); } ┘
426      this.loader = new TextureLoader(image, this);.............(7)
427      Background bg = new Background(this.loader.getImage());...(8)
428      bg.setCapability(Background.ALLOW_COLOR_READ);  ┐
429      bg.setCapability(Background.ALLOW_COLOR_WRITE); ├(9)
430      bg.setCapability(Background.ALLOW_IMAGE_WRITE); ┘
431      bg.setApplicationBounds(bounds);..................(10)
432      return bg;
433    }

(1)で private メソッドである createBackground() を実行して Background を生成し、(2)で BranchGroupaddChild() しています。

createBackground() メソッドでは、(3)で画像のための java.awt.Image 型の変数を宣言しています。

(4)の if 文で isStandalone フラグをみてアプリケーションとして実行されているのか、アプレットとして実行されているのかを判定しています。

アプリケーションとして実行されているときは、java.awt.Toolkit クラスの static メソッドである getDefaultToolkit() で現在の Toolkit オブジェクトを取得して、Toolkit#getImage() でファイル名を指定して画像をファイルから読み込んでいます(5)。

アプレットとして実行されているときは Applet#getImage() メソッドを使って、getCodeBase() で得た codeBase の URL にあるファイル名 face.gif から画像を読み込んでいます。

(6)は java.awt.MediaTracker を使った画像読み込みの同期処理です。waitForID() で完全に画像が読み込まれるまで待ち続けています。

(7)で画像を読み込んだ Image、このアプレット自身 this(imageUpdate() の通知先となる)をコンストラクター引数に指定して TxuterLoader を生成しています。TextureLoader は、java.awt.Image, java.net.URL, または画像ファイルのファイル名を指定して TextureImageComponent2D を生成できる便利なクラスです。TextureLoader についてはテクスチャーマッピングのところで詳しく説明します。

(8)でTextureLoader#getImage() メソッドで ImageComponent2D オブジェクトを取得し、取得した ImageComponent2D を引数にして Background を生成しています。

(9)で必要な capability bit を設定し、(10)で setInfluencingBounds() メソッドで背景が適用される対象となる領域を設定しています。

■文字の描画

com.sun.j3d.utils.geometry.Text2D, javax.media.j3d.Text3D を使うと、3次元空間に文字を描画することができます。

Text2DTest.gif

Text2D では文字を画像として描画し、Text3D では文字を3次元モデルとして描画することができます。

Text3DTest1.gif

■■com.sun.j3d.utils.geometry.Text2D

com.sun.j3d.utils.geometry.Text2D は文字を画像として描画します。

Text2D は標準の javax.media.j3d パッケージではなく、Sun のユーティリティーパッケージ com.sun.j3d.utils パッケージのクラスとして提供されています。

クラス継承

java.lang.Object
  |
  +--javax.media.j3d.SceneGraphObject
        |
        +--javax.media.j3d.Node
              |
              +--javax.media.j3d.Leaf
                    |
                    +--javax.media.j3d.Shape3D
                          |
                          +--com.sun.j3d.utils.geometry.Text2D

クラス宣言

public class Text2D
extends Shape3D

コンストラクター

public Text2D(java.lang.String text,     // 描画する文字列
              Color3f color,             // 色
              java.lang.String fontName, // フォント名
              int fontSize,              // フォントサイズ (単位: ポイント)
              int fontStyle)             // フォントスタイル

Text2D のコンストラクターでは、描画する文字列、文字の色、フォント名、フォントサイズ (単位: ポイント)、フォントスタイルを指定できます。
フォントスタイルは java.awt.Font クラスの定数フィールドを使って指定します。

java.awt.Font の定数フィールド

public static final int PLAIN  // 標準体
public static final int BOLD   // 太文字
public static final int ITALIC // 斜体

サンプルの実行結果は次のようになります。

Text2DTest.gif

文字列を入力し、フォントを選び、色、サイズ、座標を入力して [Add] ボタンを押すと Text2D が追加されます。

いくつも追加してみると分かるのですが、先に追加した Text2Dは、後に追加した Text2D よりも手前に表示されます。Text2D の表示される順序は、Z座標ではなくシーングラフに追加された順番に影響されるようです。

サンプルのソースは次の通りです。

Text2DTest.java (一部)
  1  // Java 3D Test Applet
  2  // Text2DTest.java
  3  //   Copyright (c) 1999 ENDO Yasuyuki
  4  //                      mailto:yasuyuki@javaopen.org
  5  //                      http://www.javaopen.org/j3dbook/index.html
  6  
  7  import java.applet.*;
  8  import java.awt.*;
  9  import java.awt.event.*;
 10  import javax.media.j3d.*;
 11  import javax.vecmath.*;
 12  import com.sun.j3d.utils.applet.MainFrame;
 13  import com.sun.j3d.utils.universe.SimpleUniverse;
 14  import com.sun.j3d.utils.geometry.Text2D;
 15  import com.sun.j3d.utils.image.TextureLoader;
 16  
 17  public class Text2DTest extends Applet {
 18    boolean isStandalone = false;
 19    
 20    private Canvas3D canvas = null;
 21    private SimpleUniverse universe = null;
 22    private BranchGroup scene = null;
 23    private Background background = null;
 24    private TextureLoader loader = null;
 25  
 26    private Color3f bgcolor = new Color3f(0.0f, 0.0f, 0.0f);   // 背景色 黒
 27    private Color3f fontcolor = new Color3f(1.0f, 0.0f, 0.0f); // フォント色 赤
 28    private Vector3f fontposition = new Vector3f(0.0f, 0.0f, 0.0f); // フォント位置
 29  
 30    private TuplePanel fcpanel = new TuplePanel(fontcolor);
 31    private TuplePanel fppanel = new TuplePanel(fontposition);
 32  
 33    public Text2DTest() {
 34      this(false);
 35    }
 36    
 37    public Text2DTest(boolean isStandalone) {
 38      this.isStandalone = isStandalone;
 39      this.setLayout(new BorderLayout());
               :
               :
133          Transform3D t3d = new Transform3D();
134          t3d.setTranslation( new Vector3f(fppanel.getTuple3f()) );
135          TransformGroup trans = new TransformGroup(t3d);
136          trans.addChild(
137            new Text2D( txfield.getText(),                 ┐      
138                        new Color3f(fcpanel.getTuple3f()), ├(1)
139                        fchoice.getSelectedItem(),         │       
140                        size, style ) );                   ┘
141          universe.getLocale().removeBranchGraph(scene);.....(2)
142          scene.addChild(trans);.............................(3)
143          universe.addBranchGraph(scene);....................(4)
144        }
145      });
146      dpanels[2].add(addbutton);
147  
148    }
149    
150    public void init() {}
151  
152    public void start() {
153      GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
154      this.canvas = new Canvas3D(config);
155      this.add(this.canvas, BorderLayout.CENTER);
156      
157      universe = new SimpleUniverse(canvas);
158      universe.getViewingPlatform().setNominalViewingTransform();
159      
160      scene = createSceneGraph();
161      universe.addBranchGraph(scene);
162    }
163    
164    private BranchGroup createSceneGraph() {
165      BranchGroup root = new BranchGroup();
166      root.setCapability(BranchGroup.ALLOW_DETACH);
167      
168      BoundingSphere bounds = new BoundingSphere(new Point3d(), 10000.0);
169  
170      background = createBackground(bounds);
171      root.addChild(background);
172  
173      return root;
174    }

[Add] ボタンが押されるたびに、Text2DBranchGroupaddChild() するようにしました。

このサンプルでは次のような方法で使用可能なフォントの一覧を取得しています。

 83      Font[] fonts
 84        = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();

(1)で入力値を元に Text2D を生成しています。

実行時に Text2D を追加するために、(2)でLocale からBranchGroupremoveBranchGraph()しています。

(3)でText2Dが追加されている TransformGroupBranchGroupaddChild() しています。

Text2Dを追加したら、(4)でふたたびSimpleUniverseBranchGroupaddBranchGraph() しています。

■■javax.media.j3d.Text3D

javax.media.j3d.Text3D は文字フォントから座標データを取得して、厚みのある3次元モデルとして抽出します。

クラス継承

java.lang.Object
  |
  +--javax.media.j3d.SceneGraphObject
        |
        +--javax.media.j3d.NodeComponent
              |
              +--javax.media.j3d.Geometry
                    |
                    +--javax.media.j3d.Text3D

クラス宣言

public class Text3D
extends Geometry

コンストラクター

public Text3D()

public Text3D(Font3D font3D)           // 元になる Font3D オブジェクト

public Text3D(Font3D font3D,           // 元になる Font3D オブジェクト
              java.lang.String string) // 描画する文字列              

public Text3D(Font3D font3D,           // 元になる Font3D オブジェクト
              java.lang.String string, // 描画する文字列              
              Point3f position)        // 文字列の描画位置            

public Text3D(Font3D font3D,           // 元になる Font3D オブジェクト
              java.lang.String string, // 描画する文字列              
              Point3f position,        // 文字列の描画位置            
              int alignment,           // 文字列の整列方法
              int path)                // 文字列の描画方向

定数フィールド (一部)

public static final int ALIGN_CENTER // 中央揃え
public static final int ALIGN_FIRST  // 先頭揃え
public static final int ALIGN_LAST   // 末尾揃え

public static final int PATH_LEFT    // 左向きに描画
public static final int PATH_RIGHT   // 右向きに描画
public static final int PATH_UP      // 上向きに描画
public static final int PATH_DOWN    // 下向きに描画

Text3D のコンストラクターでは javax.media.j3d.Font3D を使って文字フォントを設定します。また、描画する文字列、文字の位置座標、整列方法、描画方向を指定することもできます。

Text2DShape3D のサブクラスだったので直接 BranchGroupaddChild() しましたが、Text3Djavax.media.j3d.Geometry のサブクラスなので、Shape3D などに設定して使用します。

■■javax.media.j3d.Font3D

javax.media.j3d.Font3Djava.awt.Font 元に3次元フォントデータを構築します。

クラス継承

java.lang.Object
  |
  +--javax.media.j3d.Font3D

クラス宣言

public class Font3D
extends java.lang.Object

コンストラクター

public Font3D(java.awt.Font font,           // 元になる java.awt.Font オブジェクト
              FontExtrusion extrudePath)    // 側面の押し出し

public Font3D(java.awt.Font font,           // 元になる java.awt.Font オブジェクト
              double tessellationTolerance, // テセレーションの精度
              FontExtrusion extrudePath)    // 側面の押し出し

Font3D のコンストラクターでは java.awt.Fontjavax.media.j3d.FontExtrusion を指定します。
FontExtrusion はフォントの側面の形状を決定するためのクラスで、引数無しのコンストラクターで FontExtrusion を生成すると 0.2 の直線が使われます。

サンプルの実行結果は次のようになります。

Text3DTest1.gif

Text3D で描画する文字や、フォント、色、位置、整列方法、描画方向を変えることができます。

サンプルのソースは次の通りです。

Text3DTest.java (一部)
  1  // Java 3D Test Applet
  2  // Text3DTest.java
  3  //   Copyright (c) 1999 ENDO Yasuyuki
  4  //                      mailto:yasuyuki@javaopen.org
  5  //                      http://www.javaopen.org/j3dbook/index.html
  6  
  7  import java.applet.*;
  8  import java.awt.*;
  9  import java.awt.event.*;
 10  import javax.media.j3d.*;
 11  import javax.vecmath.*;
 12  import com.sun.j3d.utils.applet.MainFrame;
 13  import com.sun.j3d.utils.universe.SimpleUniverse;
 14  
 15  public class Text3DTest extends Applet {
 16    private Canvas3D canvas = null;
 17    private SimpleUniverse universe = null;
 18    private BranchGroup scene = null;
 19    private TransformGroup trans = null;
 20    private TransformGroup strans = null;
 21    private float scale = 0.2f;
 22    private Material mat = null;
 23    private Font[] fonts = null;
 24    private int nfont = 0;
 25    private Font font = null;
 26    private int style = java.awt.Font.PLAIN;
 27    private String text = "Java 3D";
 28    private Text3D text3d = null;
 29  
 30    private Color3f fontcolor = new Color3f(1.0f, 0.0f, 0.0f); // フォント色 赤
 31    private TuplePanel fcpanel = new TuplePanel(fontcolor);
 32  
 33    private Point3f fontposition = new Point3f(0.0f, 0.0f, 0.0f); // フォント位置 原点
 34    private TuplePanel fppanel = new TuplePanel(fontposition);
 35  
        :
        :
199    private BranchGroup createSceneGraph() {
200      BranchGroup root = new BranchGroup();
201      root.setCapability(BranchGroup.ALLOW_DETACH);
202      
203      BoundingSphere bounds = new BoundingSphere(new Point3d(), 10000.0);
204  
205      root.addChild( createLight(0.3f, -0.3f, -0.3f, bounds) );
206      
207      AmbientLight alight = new AmbientLight();
208      alight.setInfluencingBounds(bounds);
209      root.addChild(alight);
210      
211      trans = new TransformGroup();
212      trans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
213      trans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
214      root.addChild(trans);
215      
216      Transform3D t3d = new Transform3D();
217      t3d.setScale(scale);
218      
219      strans = new TransformGroup(t3d);
220      strans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
221      strans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
222      trans.addChild(strans);
223  
224      text3d =                                                     ┐
225        new Text3D( new Font3D( font, new FontExtrusion() ),...(2) │
226                    text,                                          ├(1)
227                    fontposition_,                                 │
228                    Text3D.ALIGN_FIRST,                            │
229                    Text3D.PATH_RIGHT );                           ┘
230      text3d.setCapability(Text3D.ALLOW_FONT3D_WRITE);    ┐
231      text3d.setCapability(Text3D.ALLOW_STRING_WRITE);    │
232      text3d.setCapability(Text3D.ALLOW_ALIGNMENT_READ);  ├(3)
233      text3d.setCapability(Text3D.ALLOW_ALIGNMENT_WRITE); │
234      text3d.setCapability(Text3D.ALLOW_PATH_READ);       │
235      text3d.setCapability(Text3D.ALLOW_PATH_WRITE);      ┘
236      
237      Shape3D shape = new Shape3D(text3d, createAppearance());...(4)
238     
239      strans.addChild(shape);....................................(5)
240  
241      return root;
242    }

(1)で Text3D を構築しています。
(2)で Font3D を生成しています。フォントは Choice で選ばれたフォントが使用され、FontExtrusion は引数無しのコンストラクターで生成しているので側面には長さ 0.2 の直線が使用されます。
文字位置は原点 (0.0, 0.0, 0.0)、整列方法は先頭揃え、描画方向は右方向です。

(3)で capability bit をセットし、(4)で Text3D をコンストラクター引数にして Shape3D を構築し、(5)で TransformGorupaddChild() しています。

これを書いている時点では Java 3D バージョン 1.1.x, 1.2 Alpha1, 1.2 Beta1 の Text3D にバグがあり、漢字かな混じりの日本語文字列では正常に3次元モデルが構築できないようです。

Text3DBug.gif

■■javax.media.j3d.FontExtrusion

javax.media.j3d.FontExtrusionFont3D で構築する3次元フォントの側面の形状を定義するクラスです。

クラス継承

java.lang.Object
  |
  +--javax.media.j3d.FontExtrusion

クラス宣言

public class FontExtrusion
extends java.lang.Object

コンストラクター

public FontExtrusion()

public FontExtrusion(java.awt.Shape extrusionShape) // 側面に適用する java.awt.Shape

public FontExtrusion(java.awt.Shape extrusionShape, // 側面に適用する java.awt.Shape
                     double tessellationTolerance)  // テセレーションの精度

java.awt.Shape で側面の形状を設定することができます。デフォルトでは長さ 0.2 の直線です。

サンプルの実行結果を見てください。

Text3DTest2.gif

Checkboxjava.awt.Line2D, java.awt.CubicCurbe2D, java.awt.QuadCurve2D を選び、線の形状を変更できます。

サンプルのソースは次の通りです。

Text3DTest.java (一部)
  1  // Java 3D Test Applet
  2  // Text3DTest.java
  3  //   Copyright (c) 1999 ENDO Yasuyuki
  4  //                      mailto:yasuyuki@javaopen.org
  5  //                      http://www.javaopen.org/j3dbook/index.html
  6  
  7  import java.applet.*;
  8  import java.awt.*;
  9  import java.awt.geom.*;
 10  import java.awt.event.*;
 11  import javax.media.j3d.*;
 12  import javax.vecmath.*;
 13  import com.sun.j3d.utils.applet.MainFrame;
 14  import com.sun.j3d.utils.universe.SimpleUniverse;
 15  
 16  public class Text3DTest extends Applet {
 17    private Canvas3D canvas = null;
 18    private SimpleUniverse universe = null;
 19    private BranchGroup scene = null;
 20    private TransformGroup trans = null;
 21    private TransformGroup strans = null;
 22    private float scale = 0.2f;
 23    private Material mat = null;
 24    private Font[] fonts = null;
 25    private int nfont = 0;
 26    private Font font = null;
 27    private int style = java.awt.Font.PLAIN;
 28    private String text = "Java 3D";
 29    private Text3D text3d = null;
 30    private Shape shape = null;
 31  
 32    private Line2D.Double line2d = new Line2D.Double(0.0, 0.0, 1.0, 0.0);
 33  
 34    private CubicCurve2D.Double c2d =
 35      new CubicCurve2D.Double(0.0, 0.0, 0.2, 0.7, 0.7, 0.8, 1.0, 1.0);
 36  
 37    private QuadCurve2D.Double q2d = 
 38      new QuadCurve2D.Double(0.0, 0.0, 0.0, 1.0, 1.0, 1.0);
 39  
 40    private Color3f fontcolor = new Color3f(1.0f, 0.0f, 0.0f); // フォント色 赤
 41    private TuplePanel fcpanel = new TuplePanel(fontcolor_);
 42  
 43    private Point3f fontposition = new Point3f(0.0f, 0.0f, 0.0f); // フォント位置 原点
 44    private TuplePanel fppanel = new TuplePanel(fontposition_);
 45  
 46    public Text3DTest() {
 47      this.setLayout(new BorderLayout());
 48  
 49      shape = line2d;
          :
	  :
757    private BranchGroup createSceneGraph() {
758      BranchGroup root = new BranchGroup();
759      root.setCapability(BranchGroup.ALLOW_DETACH);
760      
761      BoundingSphere bounds = new BoundingSphere(new Point3d(), 10000.0);
762  
763      root.addChild( createLight(0.3f, -0.3f, -0.3f, bounds) );
764      
765      AmbientLight alight = new AmbientLight();
766      alight.setInfluencingBounds(bounds);
767      root.addChild(alight);
768      
769      trans = new TransformGroup();
770      trans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
771      trans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
772      root.addChild(trans);
773      
774      Transform3D t3d = new Transform3D();
775      t3d.setScale(scale);
776      
777      strans = new TransformGroup(t3d);
778      strans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
779      strans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
780      trans.addChild(strans);
781     
782      text3d =
783        new Text3D( new Font3D( font, new FontExtrusion(shape) ),
784                    text,
785                    fontposition_,
786                    Text3D.ALIGN_FIRST,
787                    Text3D.PATH_RIGHT );
788      text3d.setCapability(Text3D.ALLOW_FONT3D_WRITE);
789      text3d.setCapability(Text3D.ALLOW_STRING_WRITE);
790      text3d.setCapability(Text3D.ALLOW_ALIGNMENT_READ);
791      text3d.setCapability(Text3D.ALLOW_ALIGNMENT_WRITE);
792      text3d.setCapability(Text3D.ALLOW_PATH_READ);
793      text3d.setCapability(Text3D.ALLOW_PATH_WRITE);
794      
795      Shape3D shape3d = new Shape3D(text3d, createAppearance());
796     
797      strans.addChild(shape3d);
798  
799      return root;
800    }

クラスの最初で java.awt.Line2D.Double, java.awt.CubicCurbe2D.Double, java.awt.QuadCurve2D.Double を宣言し、コンストラクターで初期値として Line2D.Double を使用するようにしています。

Font3D の生成時にコンストラクターで代入した java.awt.Line2D.Double が使用されます。実行時に Checkbox で他の Shape に切替えられます。

■モーフィング jamax.media.j3d.Morph

javax.media.j3d.Morph クラスは、物体の形状をなめらかに変形させるためのクラスです。

クラス継承

java.lang.Object
  |
  +--javax.media.j3d.SceneGraphObject
        |
        +--javax.media.j3d.Node
              |
              +--javax.media.j3d.Leaf
                    |
                    +--javax.media.j3d.Morph

クラス宣言

public class Morph
extends Leaf

コンストラクター

public Morph(GeometryArray[] geometryArrays) // 変化ごとの GeometryArray の配列

public Morph(GeometryArray[] geometryArrays, // 変化ごとの GeometryArray の配列
             Appearance appearance)          // 概観上の属性

メソッド (一部)

public final void setWeights(double[] weights) // 変化ごとの混合比

Morph のコンストラクターは Shape3D に良く似ています。だたし Morph には GeometryArray の配列を引数にとるコンストラクターしかありません。

Morph には次のような制約があります。

  • GeometryArray 配列の各要素は、すべて同じ頂点数であること
  • GeometryArray 配列の各要素は、すべて同じクラスであること
  • setWeights() メソッドで指定する float 配列の要素数は、GeometryArray 配列の要素数と同じであること
  • setWeights() メソッドで指定する float 配列のすべての要素を合計した値は、必ず 1.0 であること

この制約からわかるように、Morph は各頂点を一次補間して変形を実現しているだけです。float 配列 weights で指定された混合比に従って頂点座標が補間計算されます。

例えば GeometryArray 配列 geometryArrays の要素数が 2 だったとします。

weights 配列の各要素が [ 1.0f, 0.0f ] なら、計算結果の頂点座標は geometryArrays[0] の頂点座標と同じになります。weights[ 0.0f, 1.0f ] なら、計算結果の頂点座標は geometryArrays[1] の頂点座標と同じになります。

weights[ 0.5f, 0.5f ] なら、geometryArrays[0] の頂点座標と geometryArrays[1] の頂点座標を1:1の比率で補間した頂点座標が計算されます。

サンプルの実行結果を見てください。

MorphTest.gif

このサンプルでは外部ファイルから "J", "3", "D" の形の物体を読み込み、スクロールバーの位置に応じて物体を変形させています。スクロールバーがいちばん左にあるとき "J"、中央が "3"、いちばん右で "D" の形に変形させています。

ここではスクロールバーで weights 配列の各要素の値を変化させていますが、後に解説する javax.media.j3d.Interpolator を使って変形をアニメーションをアニメーションすることも可能です。その場合は Interpolator を継承したクラスを自分で書くことになります。Java 3D に付属のデモサンプル MorphingMorphingBehavior.java というソースがあるので参考にしてください。

サンプルのソースは次の通りです。

MorphTest.java (一部)
 76    private BranchGroup createSceneGraph() {
 77      BranchGroup root = new BranchGroup();
 78  
 79      Bounds bounds = new BoundingSphere(new Point3d(), 100.0);
 80  
 81      Light alight = new AmbientLight();
 82      alight.setInfluencingBounds(bounds);
 83      root.addChild(alight);
 84      
 85      Light light = new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f),
 86                                          new Vector3f(-0.57f, -0.57f, -0.57f) );
 87      light.setInfluencingBounds(bounds);
 88      root.addChild(light);
 89  
 90      Transform3D t3d = new Transform3D();
 91      t3d.setScale(0.4);
 92      TransformGroup trans = new TransformGroup(t3d);
 93      trans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
 94      trans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
 95      trans.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
 96      root.addChild(trans);
 97  
 98      MouseRotate rotate = new MouseRotate(trans);
 99      rotate.setSchedulingBounds(bounds);
100      root.addChild(rotate);
101  
102      MouseTranslate translate = new MouseTranslate(trans);
103      translate.setSchedulingBounds(bounds);
104      root.addChild(translate);
105  
106      MouseZoom zoom = new MouseZoom(trans);
107      zoom.setSchedulingBounds(bounds);
108      root.addChild(zoom);
109      
110      GeometryArray[] garray = new GeometryArray[3];...(1)
111      
112      try {
113        garray[0] = loadObjFile("j.obj"); ┐
114        garray[1] = loadObjFile("3.obj"); ├(2)
115        garray[2] = loadObjFile("d.obj"); ┘
116      } catch (Exception e) {
117        e.printStackTrace();//DEBUG
118      }
119  
120      Appearance app = new Appearance();
121      Material mat = new Material();
122      mat.setDiffuseColor(new Color3f(0.0f, 0.0f, 1.0f));
123      app.setMaterial(mat);
124  
125      morph = new Morph(garray, app);                 ┐
126      morph.setCapability(Morph.ALLOW_WEIGHTS_WRITE); ├(3)
127      morph.setWeights(weights);	                 │
128      trans.addChild(morph);                          ┘
129  
130      return root;
131    }

(1)で要素数 3の GeometryArray 配列を宣言しています。

(2)で外部ファイルから物体を読み込んでいます。

(3)でMorph 生成、weights 配列設定、TransformGroup への addChild() を行っています。

外部ファイルには Wavefront .obj 形式のものを3つ用意しました。

■外部ファイルの読み込み -com.sun.j3d.loaders.Loader インターフェース-

Morph のサンプルでは、同じ頂点配列を持つ GeometryArray を外部ファイルから読み込んでいます。

まずあらかじめモデリングした物体を変形させ、同じ頂点座標を持つファイルとして別名保存しました。

つぎに com.sun.j3d.loaders.Loader インターフェースの実装クラスを使って外部ファイルから物体を読み込みます。

interface 宣言

public interface Loader

メソッド (一部)

public Scene load(java.lang.String fileName) // 読み込み元ファイル名

public Scene load(java.net.URL url)          // 読み込み元 URL

public Scene load(java.io.Reader reader)     // 読み込み元 Reader

定数フィールド

public static final int LOAD_LIGHT_NODES      // Light ノードを読み込む
public static final int LOAD_FOG_NODES        // Fog ノードを読み込む
public static final int LOAD_BACKGROUND_NODES // Background ノードを読み込む
public static final int LOAD_BEHAVIOR_NODES   // Behavior ノードを読み込む
public static final int LOAD_VIEW_GROUPS      // Group ノードを読み込む
public static final int LOAD_SOUND_NODES      // Sound ノードを読み込む
public static final int LOAD_ALL              // すべてを読み込む

読み込まれた結果は com.sun.j3d.leaders.Scene インターフェースとして取得できます。

Scene には様々な種類のオブジェクトを取得するメソッドがあります。

interface 宣言

public interface Scene

メソッド (一部)

public BranchGroup getSceneGroup() // 読み込んだすべての root の BranchGroup を取得

Java 3Dには、Loader interface を実装したクラスとして次のものが付属しています。

com.sun.j3d.loaders.objectfile.ObjectFile // Wavefront の .obj ファイルを読み込む

com.sun.j3d.loaders.lw3d.Lw3dLoader // LightWave のファイルを読み込む

今回のサンプルでは ObjectFile を使用しました。

MorphTest.java (一部)
 18  import com.sun.j3d.loaders.objectfile.ObjectFile;
        :
 23  public class MorphTest extends Applet {
        :
 27    ObjectFile loader = null;
        :
133    private GeometryArray loadObjFile(String filename)
134         throws FileNotFoundException, MalformedURLException,
135           IncorrectFormatException, ParsingErrorException
136    {
137      Scene scene = null;
138      
139      if (loader == null) loader = new ObjectFile();
140      if (isStandalone) {
141        scene = loader.load(filename);
142      } else {
143        URL url = new URL(getCodeBase(), filename);
144        scene = loader.load(url); 
145      }
146      BranchGroup bg = scene.getSceneGroup();
147      Shape3D shape = (Shape3D)bg.getChild(0);
148      return (GeometryArray)shape.getGeometry();
149    }

アプリケーションとして動作しているときはファイル名指定で、アプレットとして動作しているときは java.net.URL を指定して .obj ファイルを読み込んでいます。

読み込まれた Scene から BranchGroup を取得し、そこからさらに Shape3D を取得し、Shape3D#getGoemetry()Goemetry オブジェクトを取得し、GeometryArray 型にキャストして戻り値として返しています。

ENDO Yasuyuki