Java3D自分FAQ
これは、私がJava3Dを始めるに当たってぶち当たった壁を列挙し、
可能ならばそれを解決した方法をあげたページです。
よってあんまり正しいFAQの形式をとってません。
よってこの解法が正しいかどうか保証はありません。
よって鵜呑みにするとひどいめにあうかもしれません。
Q1. Java3Dを始めるには何が必要でしょうか?
本家JavaSoftページ
Java 3D(TM) API Home Page
を参照しましょう。ここから必要最低限の情報は得られます。
とりあえず、Win環境でJava3Dプログラムを始めるには、以下のものが必要です。
- Java3D 1.1 implementation
Java3DのJavaSoftによるインプリです。
正式公開版が出たので、JDC(Java Developer Connection)に入る必要もなくなりました。
上のページからだれでもただでダウンロードできます。
- OpenGL Implementation
Windows NT4.0/95OSR2.0以降の場合は標準でついてくるので問題ないのですが、
95のOSR2.0より前の場合は、Open GLのインプリを取ってくる必要があります。
www.opengl.orgなどにあります。
- Java2(JDK 1.2)
一部Java2Dのクラスなどを利用する関係もあって、JDK1.2が必要です。
ただ、JDK1.1.5などでも動かす分には動いてしまいました。
が、開発中に、java.awt.GraphicsConfigurationなどを利用する必要が
出てくると、最低限JDK1.2のクラスファイルは必要なので、あったほうがいいでしょう。
JDK1.2は、Java2という名前で、正式リリースされています。
Javasoftのページからリンクをたどればすぐに
見つかるでしょう。
Q2. サンプルHelloUniverseをVisual Cafeでコンパイルしようと思うのですが
注)以下の内容は古いJDKおよびJava3Dをもとにした内容なので、現在のJava3Dとは一致しません。
また、今はVisual Cafeを利用していないため、現在のバージョンでどのような設定が
必要かは調べていません。それほど変化はないとは思います。
プロジェクトの設定で、クラスパスを設定できるのですが、Java3Dの各種jarファイル
を設定するのがうまくいきませんでした。そこで、Visual CafeのBin配下にある、
sc.iniを以下のようにしてしまいました。
PATH=%@P%\..\BIN;%@P%\..\Java\bin;%PATH%;c:\java3d\bin;c:\jdk1.2beta3\bin;
BIN=c:\jdk1.2beta3\bin
INCLUDE=%@P%\..\INCLUDE
LIB=%@P%\..\Lib
HELP=%@P%\..\HELP
JAVAINC=%@P%\..\JAVA\SRC
CLASSPATH=.;%@P%\COMPONENTS\SYMBEANS.JAR;%@P%\..\JAVA\LIB;%@P%\..\JAVA\LIB\SYMCLASS.ZIP;
%@P%\..\JAVA\LIB\CLASSES.ZIP;%@P%\..\JAVA\LIB\DBAW.ZIP;%@P%\COMPONENTS\DBAW_AWT.JAR;
c:\java3d\lib\appext\j3dutils.jar;c:\java3d\lib\sysext\j3dcore.jar;
c:\java3d\lib\sysext\j3daudio.jar;c:\jdk1.2beta3\lib\classes.zip
JAVA_HOME=c:\jdk1.2beta3
要はここのクラスパス設定に、Java3D関係およびJDK1.2のクラスファイルを
設定してしまいました。あと、java3dのbinにもパスを通しておきました。
jdk1.2beta3をBINやJAVA_HOMEに設定することに意味があるかどうかは
分かりません。
Q3. Hello Universeは実行できましたが、たまに黒い画面しか出て
こなかったりするのですが
Java3D 1.1 Alpha 1だとよく発生しましたが、Alpha 2にしてからは
発生しませんので、よしとしましょう。
Q4. HelloUniverseにでてくるTransformGroup,BranchGroupって何? シーングラフって?
HelloUniverseを見ていると、TransformGroupやBranchGroupなどが
立方体の制御をしているように見えます。これらはシーングラフを
構成するノードの1種です。
まず、シーングラフってどんな感じのものってことを、
Java 3D API Specificationの、Scene Graph Basics
を読んで漠然ととらえましょう。
基本的には、Figure 2-1のような木構造でJava3Dのシーンを呼ばれるものが
構成されています。この木構造のグラフをシーングラフと呼び、
このシーングラフによって、Java3Dで描画する仮想世界を表現します。
その木のノード(木の構成要素)として以下の3つをとりあえず使ってみましょう。
- BranchGroup
この下にいろいろなノードをぶらさげます。これ自身には情報はありません。
ノードのつなぎ目です。
- TransformGroup
ある物体の場所を決めるためのノードです。このノードの情報を変えることで、
物体を移動させることができます。
- Shape3D
物体の形です。HelloUniverseではnew ColorCube().getShape()とすることで
色つきキューブの形を得ています。ColorCubeというのはJava3Dの
ユーティリティクラスの一つです。
BranchGroupの下に、TransformGroupとShape3Dをぶら下げた木で、
一つの物体を表現します。
Q5. HelloUniverseにでてくるSimpleUniverseってどう使うの?
HelloUniverseでも使われている、
SimpleUniverse(com.sum.j3d.utils.universe.SimpleUniverse)
を用いることで、シーングラフおよびそれを表示するCanvas3Dを設定できます。
Canvas3Dは、シーングラフで表現された仮想世界を表示する、
仮想世界の覗き穴のようなものです。
設定は、以下のようにします。
BranchGroup scene = new BranchGroup();
Canvas3D canvas3D = new Canvas3D();
SimpleUniverse u = new SimpleUniverse(canvas3D);
u.addBranchGraph(scene);
このようにすることで、Canvas3D上に、BranchGroup sceneを根にした
シーングラフが表示されます。
このシーングラフにQ4で述べた形の「物体」を追加します。
この物体を表すクラスを定義すると便利そうです。
ここではObjTransというクラスを定義しています。雛形は以下の通りです。
import javax.media.j3d.*;
import javax.vecmath.*;
public class ObjTrans {
public TransformGroup objTrans;
private Group root;
private BranchGroup bg;
public ObjTrans(Shape3D shape, Group group) {
root = group;
objTrans = new TransformGroup();
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
bindShape(shape);
}
public void bindShape(Shape3D shape) {
objTrans.addChild(shape);
bg = new BranchGroup();
bg.setCapability(BranchGroup.ALLOW_DETACH);
bg.addChild(objTrans);
setPosition();
root.addChild(bg);
}
}
ObjTransをシーングラフに加えるには以下のようにします。
BranchGroup scene = new BranchGroup();
Canvas3D canvas3D = new Canvas3D();
SimpleUniverse u = new SimpleUniverse(canvas3D);
u.addBranchGraph(scene);
ObjTrans ot = new ObjTrans(new ColorCube().getShape(), scene);
ObjTransコンストラクタの第1引数にShape3Dを、
第2引数にそのObjTransを加えるシーングラフ中のノードを指定します。
この場合は、先ほど用意した、シーングラフの根っこ、sceneを指定しました。
Q6. TransformGroupを使って物体の位置決めをしたいのですが
空間(x,y,z)に角度(d1,d2,d3)で存在する物体をTransformGroupで指定する
には、以下のようにしてみました。ObjTransに以下のメソッドを加えます。
public void setPosition(float x, float y, float z, float d1, float d2, float d3) {
Transform3D trns = new Transform3D();
Transform3D trnsX = new Transform3D();
Transform3D trnsY = new Transform3D();
Transform3D trnsZ = new Transform3D();
trnsY.rotY(d1);
trnsX.rotX(d2);
trnsZ.rotZ(d3);
trns.mul(trnsZ, trns);
trns.mul(trnsX, trns);
trns.mul(trnsY, trns);
trns.setTranslation(new Vector3f(x, y, z));
objTrans.setTransform(trns);
}
注) これで一応動きますが、このメソッドは、かなり高価(時間がかかる)です。
可能な限り、Transform3Dに用意されているメソッドを用いて、最小限の計算ですむように
した方がいいです。また、このようなメソッドを用いる場合でも、使用するTransform3Dは
あらかじめnewで生成しておくべきです。オブジェクトの生成は、かなりのオーバヘッドになります。
回転用の4x4行列を含んだtrnsX,Y,Zを用意し、順にtrnsに乗じて
いきます。最後にVector3fで指定された並行移動を適用し、
できあがったTransform3Dオブジェクトを、TransformGroupにセットします。
とりあえず、これで正しく動作しますが、無駄があるかもしれません。
あと、このTransformGroup(上のObjTrans)に書き込みを許可することを
忘れないようにしましょう。書き込みを許可せずに、すでにレンダリングが
行われている物体のTransformGroupに設定を行おうとするとExceptionが
発生します。書き込みを許可するには、
objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
とします。ObjTransではコンストラクタ中で行っています。
Q7. 複数の物体を扱いたいのですが
Q5の要領で、sceneに次々にObjTransを加えていけば複数の物体を
扱えます。
Q8. 物体を追加しようとするとExceptionが発生するのですが
レンダリングが始まってからのノードの追加は、Q6と同様、
明示的に許可しなければなりません。sceneに対して
ノードの追加を許可するには、
scene.setCapability(BranchGroup.ALLOW_CHILDREN_EXTEND);
とします。
Q9. 物体をシーンから消すには?
ObjTransの根っこのBranchGroupをdetach()します。するとこのObjTransは
シーングラフから外され、シーンから消えます。
public void unbind() {
bg.detach();
}
BranchGroupをdetachするにはdetachが許可する必要があります。
bg.setCapability(BranchGroup.ALLOW_DETACH);
それに加えて、detachするノードの上位ノードで、子ノードのWRITEを許可する
必要もあります。
scene.setCapability(BranchGroup.ALLOW_CHILDREN_WRITE);
Q10. 物体の形をつくるには?
Shape3Dクラスで物体の形を定義します。Shape3Dのコンストラクタは、
new Shape3D(Geometry geometry)
ですから、Geometryクラスで形を定義できそうです。
GeometryクラスのサブクラスGeometryArrayを使って点や線や三角形、四角形から
構成される物体の形を生成できます。
四角形で構成される物体の形を定義するには、以下のようにします。
float[] verts = {
8f,0,8f, 8f,0,-8f, -8f,0,-8f, -8f,0,8f,
8f,-0.8f,8f, -8f,-0.8f,8f, -8f,-0.8f,-8f, 8f,-0.8f,-8f,
};
float[] colors = {
0.2f, 1f, 0.5f, 0.2f, 1f, 0.5f, 0.2f, 1f, 0.5f, 0.2f, 1f, 0.5f,
0.5f, 1f, 0.2f, 0.5f, 1f, 0.2f, 0.5f, 1f, 0.2f, 0.5f, 1f, 0.2f,
};
QuadArray geo = new QuadArray(8,
QuadArray.COORDINATES | QuadArray.COLOR_3);
geo.setCoordinates(0, verts);
geo.setColors(0, colors);
GeometryArrayのサブクラスQuadArrayを使います。メソッドsetCoordinatesおよび
setColorsで各面の頂点の場所と色を指定します。4つの頂点が一つの四角形の面を
表します。同じ面の頂点の色を別々にすると、グラデーションがかった面になります。
四角形、三角形以外の面を定義したり、それらを混ぜて物体を構成する方法は
まだよく分かってません。だれか教えてください。
Q11. 視点を変えるには?
SimpleUniverseから視点の位置に相当するTransformGroupを取り出すことができます。
ViewingPlatform vplt = u.getViewingPlatform();
viewpoint = vplt.getViewPlatformTransform();
あとはこのTransformGroupをQ6のように変化させれば視点が変化します。
Q12. ライトの設定をするには?
シーングラフにLightを加えるだけです。例えば、directional lightを
加えたい場合は、
BranchGroup bgLt = new BranchGroup();
BoundingSphere bounds = new BoundingSphere(new Point3d(0, 0, 0), 100.0);
bgLt.addChild(new BoundingLeaf(bounds));
DirectionalLight drcl =
new DirectionalLight(Boot.gray, new Vector3f(0, -1, 0));
drcl.setInfluencingBounds(bounds);
bgLt.addChild(drcl);
とでもして、クラスDirectionalLightのインスタンスを持つBranchGroupを作り、
scene.addChild(bgLt);
とします。BoundingLeafというのは、その光の有効範囲を決めているのだと
思いますが、詳しいことはわかってません。丸写しです。
あと、PointLightとかにもいえるのですが、光の位置や方向がこちらの意図する
通りに反映されていないような気がします。なにか設定に間違いがあるかも
しれません。
Q13. ライトを設定してもなんにもかわらないのですが
物体にライトを反映するには、物体の形を定義する際に、面の法線を設定する
必要が有ります。Q10のように形を定義する際に、
float[] verts = {
8f,0,8f, 8f,0,-8f, -8f,0,-8f, -8f,0,8f,
8f,-0.8f,8f, -8f,-0.8f,8f, -8f,-0.8f,-8f, 8f,-0.8f,-8f,
};
float[] colors = {
0.2f, 1f, 0.5f, 0.2f, 1f, 0.5f, 0.2f, 1f, 0.5f, 0.2f, 1f, 0.5f,
0.5f, 1f, 0.2f, 0.5f, 1f, 0.2f, 0.5f, 1f, 0.2f, 0.5f, 1f, 0.2f,
};
float[] normals = {
0,1,0, 0,1,0, 0,1,0, 0,1,0,
0,-1,0, 0,-1,0, 0,-1,0, 0,-1,0,
};
QuadArray geo = new QuadArray(8,
QuadArray.COORDINATES | QuadArray.COLOR_3 | QuadArray.NORMALS );
geo.setCoordinates(0, verts);
geo.setColors(0, colors);
geo.setNormals(0, normals);
として、法線をメソッドsetNormalsを使って設定します。これに加えて、
Appearance ap = new Appearance();
Material mt = new Material();
mt.setLightingEnable(true);
ap.setMaterial(mt);
として、物体のAppearanceを設定し、光が反映するように設定します。
そして、
new Shape3D(geo, ap);
とすれば、ライトが反映される物体になります。
Q14. ポリゴンではなくワイヤーフレームで表示するには?
Q13のAppearanceで設定します。
Appearance ap = new Appearance();
PolygonAttributes pa = new PolygonAttributes();
pa.setPolygonMode(PolygonAttributes.POLYGON_LINE);
ap.setPolygonAttributes(pa);
のように、PolygonAttributesを設定し、Appearanceにセットします。
Q15. Canvas3D上に文字を書きたいのですが
Canvas3DのメソッドpostRenderで、ダブルバッファの裏面を取得し、
そのバッファに書き込みを行えばいいのだと思うのですが、
取得する方法が分かりません。だれか教えてください。
一応postSwap中で、getGraphicsでCanvasのGraphicsを取得し、
それに文字を書き込めば画面には反映されますが、当然ちらついてしまいます。
Q16. 文字を空間に表示するには?
com.sun.j3d.utils.geometry.Text2Dを使えば、文字がテクスチャに張られた
板を得ることができます。Text2Dのコンストラクタを使って、
Text2D textObject = new Text2D("Hello!",
new Color3f(1f, 1f, 1f),
"Serif",
24,
Font.BOLD);
などと設定するだけで、textObjectにShape3D(Text2DはShape3Dのサブクラスです)
を得ることができます。ただ、このままだと文字があまりに小さいので、
Transform3DのsetScaleメソッドなどを使って、適当に拡大した方がよいでしょう。
コンストラクタの第4引数のフォントサイズでも大きさを調節できますが、
あまり大きくするとすぐにメモリが足りなくなります。
あと、文字を表示するたびにText2Dを生成すると、結構な時間がかかってしまいます。
そこで、あらかじめText2Dのインスタンスを作っておいて、必要に応じて
cloneNodeメソッドによって複製するとよいでしょう、と、思ったのですが、
複製するにはText2DのAppearanceが読み書きできなければいけないらしく、
それらの属性をいちいち許可しないと複製できませんでした。
Appearance app = textObject.getAppearance();
app.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_READ);
app.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_READ);
app.setCapability(Appearance.ALLOW_MATERIAL_READ);
app.setCapability(Appearance.ALLOW_POINT_ATTRIBUTES_READ);
app.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_READ);
app.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ);
app.setCapability(Appearance.ALLOW_TEXGEN_READ);
app.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_READ);
app.setCapability(Appearance.ALLOW_TEXTURE_READ);
app.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ);
app.setCapability(Appearance.ALLOW_COLORING_ATTRIBUTES_WRITE);
app.setCapability(Appearance.ALLOW_LINE_ATTRIBUTES_WRITE);
app.setCapability(Appearance.ALLOW_MATERIAL_WRITE);
app.setCapability(Appearance.ALLOW_POINT_ATTRIBUTES_WRITE);
app.setCapability(Appearance.ALLOW_POLYGON_ATTRIBUTES_WRITE);
app.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_WRITE);
app.setCapability(Appearance.ALLOW_TEXGEN_WRITE);
app.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_WRITE);
app.setCapability(Appearance.ALLOW_TEXTURE_WRITE);
app.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_WRITE);
これ本当にこう書くしかないんでしょうか。面倒なのですが。
(これは後で、もう少しスマートな方法がありました。Q18を参照してください)
Q17. 3Dの文字は?
当然あります。Font3DクラスとText3Dクラスを使います。
float sl = str.length();
Font3D f3d = new Font3D(new Font("dialog", Font.PLAIN, 2),
new FontExtrusion());
Text3D txt = new Text3D(f3d, str,
new Point3f( -sl/2.0f, -1.f, -1.f));
Shape3D sh = new Shape3D();
sh.setGeometry(txt);
こんな感じです。できあがった文字を表示すると、結構重いです。
Q18. 同じ形の物体を複数表示するときに、良い方法は?
SharedGroupと、Linkを使いましょう。Shape3DをBranchGroupでなくて、
SharedGroupにbindしておけば、あとでLinkを用いて、シーングラフ中の
複数の場所からそのShape3Dを用いることができます。
SharedGroup sg = new SharedGroup();
を参照するには、
new Link(sg);
とします。
(これを使えば、Q16のようにいちいち形状をcloneする必要はありません)
Q19. JBuilder2でJava3Dを扱うには?
JBuilder2はJava3Dを扱う際に便利な機能があります。
まず、ファイル→プロジェクトプロパティのパスタグの中のJavaライブラリに、
Java3Dを加えましょう。
追加を選んで、クラスパス、ソースパスおよび文章パスを加えます。
デフォルトのパスにJava3Dがインストールしてあるならば、
- クラスパス
C:\jdk1.2\jre\lib\ext\vrml97.jar;C:\jdk1.2\jre\lib\ext\j3dutils.jar;C:\jdk1.2\jre\lib\ext\vecmath.jar;
C:\jdk1.2\jre\lib\ext\j3dcore.jar;C:\jdk1.2\jre\lib\ext\j3daudio.jar
- ソースパス
設定なし
- 文章パス
設定なし
(C:\Java3D\html_core;C:\Java3D\html_utils;C:\Java3D\html_vrml97)
という具合でしょう。
また、ターゲットJDKバージョンをJDK1.2にする必要もあります。定義を押して、設定しましょう。
この設定をすれば、Java3DをJBuilder2から利用できます。
また、Java3Dのクラス名などの上でF1キーを
押すことで、対応するJavaDocをHelpとしてみることが、
β版まではできたのですが、リリース版はJavaDocがJDK1.2の形式になってしまったため、
JBuilder2上から参照することができません。β版のJava3DのDOCを持ってくるか、
Java3Dのクラスから、JDK1.1のJavaDocでHTMLファイルを生成すれば参照可能です。
(これ結構致命的なんですけど。Inpriseさん直してくれませんかね)
Q20. 半透明を出すには?
TransparencyAttributesを用います。
Appearance ap = new Appearance();
TransparencyAttributes ta = new TransparencyAttributes();
ta.setTransparency(0.1f);
ap.setTransparencyAttributes(ta);
として物体の透明度を設定した後に、Appearanceに設定します。
Q21. HUDのような、視点前に固定された物体を表示するには?
以下のようにして、PlatformGeometryを取得して、その下に
物体を追加します。
Locale locale = new Locale(this);
ViewingPlatform vplt = new ViewingPlatform(1);
vplt.setNominalViewingTransform();
viewpoint = vplt.getViewPlatformTransform();
PlatformGeometry vPg = new PlatformGeometry();
vplt.setPlatformGeometry(vPg);
vPg.setCapability(Group.ALLOW_CHILDREN_EXTEND);
vPg.setCapability(Group.ALLOW_CHILDREN_WRITE);
SimpleUniverseが提供するgetViewingPlatformで、ViewingPlatformを
取得した場合、Capabilityを変更することができませんので、
SimpleUniverseを使わないようにしましょう。
Q22. イミーディエートモードって? どうやって使うの?
通常のモード(リテインドモード)の場合、Java3Dのレンダリングエンジンは、
自動的にシーングラフを画面上に描画してくれます。描画のタイミングなどを
ユーザが管理する必要はありません。これは、逆にいえば、ユーザが管理することが
できないということでもあります。
リテインドモードでは、Java3Dのレンダリングエンジンが、シーングラフ上のCapabilityなどを
判断条件に、最適化されたレンダリングを行っています。しかし、いくつかのアプリケーションでは、
シーングラフを用いるほどの高度な3D空間を表現する必要がないかもしれません。
たとえば、いくつかのShape3Dを画面上に表示したいが、それらを奥行きでソートする必要がない場合などは、
シーングラフの助けを得るまでもありません。
このような場合、イミーディエートモードを用いることで、より高速化が図れる可能性があります。
ただし、イミーディエートモードでは、画面描画のタイミングや、物体の表示順番などは
すべてユーザが面倒をみなければなりません。
イミーディエートモードを使うには、まずJava3Dのレンダリングエンジンを止め、
物体を直接描画するためのGraphicsContext3Dを取得する必要があります。
canvas3D.stopRenderer();
GraphicsContex3D gc = canvas3D.getGraphicsContext3D();
GraphicsContext3DのsetModelTransform(Transform3D trns)メソッドを用いて、物体の位置を決め、
draw(Shape3D shape)メソッドを用いてShape3Dを描画することができます。
gc.setModelTransform(trns);
gc.draw(shape);
また、drawメソッドでは、通常Canvas3Dの裏画面に描画されます。そこで、すべての物体が書き終わった
時点で、画面をスワップする必要があります。
canvas3D.swap();
もどる