Behavior, InterpolatorJava 3D のイベント処理は、今までの Java のイベント処理とはかなり異なっています。
Java 3D のリテインド・モードでは、描画のスケジューリングは処理系に委ねられています。シーングラフがいったん "live" 状態になると、いつ何がどういう順序で描画されるのか、プログラマーが制御する方法はありません。
キーボード、マウスなどのイベント処理も、
この自動スケジューリング機能の制御下に置かれる必要があります。
従来のイベント・リスナーを使ったイベント処理や、
Thread を使ったアニメーションも
出来ないわけではありませんが、
Java 3D の自動スケジューリングと整合しない場合があります。
Java 3D では、イベント処理、アニメーションなどのために、従来とは違う処理モデルを導入しました。このためのクラスが javax.media.j3d.Behavior です。
クラス継承
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.Node
|
+--javax.media.j3d.Leaf
|
+--javax.media.j3d.Behavior
クラス宣言
public abstract class Behavior
extends Leaf
コンストラクター
public Behavior()
Behavior は抽象クラスなので、実際にはそのサブクラスを使用します。キーボード、マウス操作のための Behavior は javax.media.j3d パッケージでは提供されていません。
イベント処理のためには Behavior を継承したクラスを自分で書く必要があります。だたし、キーボード、マウス処理のための単純なクラスが com.sun.j3d.utils.behavior 配下のパッケージで提供されていますので、これを使うと良いでしょう。
Behavior の使用方法は次の通りです。
Behavior の生成setSchedulingBounds(Bounds) で、この Behavior へのスケジューリングを有効にする領域を設定addChild() するキーボード、マウスなどのイベントが発生すると、Behavior オブジェクトの processStimulus() メソッドが呼ばれます。Behavior は processStimulus()メソッドでイベントを処理します。例えばキーボードイベントなら、押されたキーを判定して視点を移動したり、マウスイベントなら、マウスの移動量を取得して物体を回転/移動させたりします。
Behavior はスケジューリングが有効になる領域を持っています。この領域の範囲外では Behavior は動作しません。この領域が設定されていないとき、Behavior は全く動作しません。setSchedulingBounds() は必ず設定してください。
アニメーションには、Behavior のサブクラス javax.media.j3d.Interpolator を使用します。
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.Node
|
+--javax.media.j3d.Leaf
|
+--javax.media.j3d.Behavior
|
+--javax.media.j3d.Interpolator
クラス宣言
public abstract class Interpolator
extends Behavior
コンストラクター
public Interpolator()
public Interpolator(Alpha alpha) // Alpha オブジェクト
Interpolator も抽象クラスなので、実際にはそのサブクラスを使用します。位置、大きさ、回転、色、透明度など様々なアニメーションのためのクラスが定義されています。
Interpolator によるアニメーションでは、時間による状態の変化を、javax.media.j3d.Alpha というクラスを使って定義します。
クラス継承
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.NodeComponent
|
+--javax.media.j3d.Alpha
クラス宣言
public class Alpha
extends NodeComponent
コンストラクター
public Alpha()
public Alpha(int loopCount, // 繰り返しの回数
int mode, // 変化モード(増加、減少、または両方)
long triggerTime, // 最初の起動開始までのミリ秒
long phaseDelayDuration, // 最初の起動開始後に本当に変化を開始するまでのミリ秒
long increasingAlphaDuration, // 増加に要するミリ秒
long increasingAlphaRampDuration, // 増加が加速するミリ秒
long alphaAtOneDuration, // 1.0 を保持するミリ秒
long decreasingAlphaDuration, // 減少に要するミリ秒
long decreasingAlphaRampDuration, // 減少が加速するミリ秒
long alphaAtZeroDuration) // 0.0 を保持するミリ秒
public Alpha(int loopCount, // 繰り返しの回数
long triggerTime, // 最初の起動開始までのミリ秒
long phaseDelayDuration, // 最初の起動開始後に本当に変化を開始するまでのミリ秒
long increasingAlphaDuration, // 増加に要するミリ秒
long increasingAlphaRampDuration, // 増加が加速するミリ秒
long alphaAtOneDuration) // 1.0 を保持するミリ秒
Interpolator, Alpha については後に詳しく説明します。
では実際にキーボード、マウス処理をやってみましょう。
com.sun.j3d.utils.behaviors.keyboard.KeyNavigator)com.sun.j3d.utils.behaviors.keyboard.KeyNavigatorBehavior を使うと、矢印キー(方向キー)による視点や物体の移動が容易に実現できます。
クラス宣言 public class KeyNavigatorBehavior extends Behavior コンストラクター public KeyNavigatorBehavior(TransformGroup targetTG) // 移動対象の TransformGroup
KeyNavigatorBehavior のコンストラクターでは、移動対象の TransformGroup を指定します。物体を移動させる場合は、移動させたい物体を TransformGroup に addChild() して、物体の「親」になった TransformGroup を指定すれば良いですが、視点を移動させたい場合はどうしたら良いでしょうか。
SimpleUniverse を使ってシーン・グラフを構築しているときに、視点側の TransformGroup を取得するには次のようにします。
TransformGroup viewtrans = universe.getViewingPlatform().getViewPlatformTransform();
まず、SimpleUniverse の getViewingPlatform メソッドを使って、com.sun.j3d.utils.universe.ViewingPlatform オブジェクトを取得します。
SimpleUniverse のメソッド (一部) public ViewingPlatform getViewingPlatform() // com.sun.j3d.utils.universe.ViewingPlatform を取得
つぎに、com.sun.j3d.utils.universe.ViewingPlatform の getViewPlatformTransform() メソッドを使って、視点側の TransformGroup を取得します。
com.sun.j3d.utils.universe.ViewingPlatform のメソッド (一部) public TransformGroup getViewPlatformTransform() // 視点側の TransformGroup を取得
KeyNavigatorBehavior の使用方法は次の通りです。
TransformGroup をコンストラクターで指定して KeyNavigatorBehavior を生成するsetSchedulingBounds() メソッドで、スケジューリングが有効となる領域 (javax.media.j3d.Bounds オブジェクト) を設定するaddChild() する実行結果を見てください。

画面下部に、「床」となる QuadArray が置いてあります。
キーボードの矢印キー (↑↓←→) を使って、視点を前後に移動したり、左右に向きを変えたりできます。キー操作は次の通りです。
| キー | 操作 |
|---|---|
| ↑ | 前進 |
| ↓ | 後退 |
| ← | 左に向きを変える |
| → | 右に向きを変える |
| PgUp | 下に向きを変える (ティルト・ダウン) |
| pgDn | 上に向きを変える (ティルト・アップ) |
| Alt + ← | 左に平行移動 |
| Alt + → | 右に平行移動 |
| Alt + PgUp | 上昇 (上方向に平行移動) |
| Alt + pgDn | 下降 (下方向に平行移動) |
| Shift + 他のキー | 移動量を大きくする |
画面内にキー入力のフォーカスが無いと移動できません。移動できないときは画面内をマウスでクリックしてみてください。
テクスチャー・マッピングを使用したので、ウインドウを拡大すると動作が遅くなって行きます。あまり大きくしないでください。
ソースコードは次の通りです。
KeyNaviagtorTest.java
1 // Java 3D Test Applet
2 // KeyNavigatorTest.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.universe.PlatformGeometry;
14 import com.sun.j3d.utils.behaviors.keyboard.*;
15
16 public class KeyNavigatorTest extends Applet {
17 private SimpleUniverse universe = null;
18
19 public KeyNavigatorTest() {
20 GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
21 Canvas3D canvas = new Canvas3D(config);
22 this.setLayout(new BorderLayout());
23 this.add(canvas, BorderLayout.CENTER);
24
25 universe = new SimpleUniverse(canvas);
26 universe.getViewingPlatform().setNominalViewingTransform();
27 universe.getViewer().getView().setBackClipDistance(100.0); ................(1)
28 BranchGroup scene = createSceneGraph();
29
30 universe.addBranchGraph(scene);
31 }
32
33 private BranchGroup createSceneGraph() {
34 BranchGroup root = new BranchGroup();
35
36 BoundingSphere bounds = new BoundingSphere( new Point3d(), 100.0 );
37
38 TransformGroup viewtrans =
39 universe.getViewingPlatform().getViewPlatformTransform(); ...............(2)
40 KeyNavigatorBehavior keybehavior = new KeyNavigatorBehavior(viewtrans); ...(3)
41 keybehavior.setSchedulingBounds(bounds);...................................(4)
42 PlatformGeometry vp = new PlatformGeometry();..............................(5)
43 vp.addChild(keybehavior);..................................................(6)
44 universe.getViewingPlatform().setPlatformGeometry(vp);.....................(7)
45
46 root.addChild(createFloor());
47
48 return root;
49 }
(1)でjavax.media.j3d.View の setBackClipDistance() メソッドを使って、視点から奥のクリップ面 (ファー・クリップ面) までの距離を増大させています。
SimpleUniverse のデフォルトでは、視点からファー・クリップ面までの距離は 10.0 です。KeyNavigatorBehavior は移動できる距離が大きいので、SimpleUniverse のデフォルトの視野空間 (ビューイング・ボリューム) では狭かったためにこのような処理をしました。
(2)で視点側の TransformGroup を取得しています。
(3)で KeyNavigatorBehavior を生成しています。コンストラクターの引数に、視点側の TransformGroup を指定しています。この TransformGroup がキーボードによる移動の対象になります。
(4)でスケジューリングが有効になる領域 (BoundingShpere) を設定しています。
(5)でcom.sun.j3d.utils.universe.PlatformGeometryを生成しています。実は KeyNavigatorBehaviorはシーングラフのどこに追加しても動作します。動作はするのですが、ここではView側のツリーに追加してみます。
PlatformGeometryはBranchGroupのサブクラスで、View側のツリー構築のために使用されます。
クラス継承
java.lang.Object
|
+--javax.media.j3d.SceneGraphObject
|
+--javax.media.j3d.Node
|
+--javax.media.j3d.Group
|
+--javax.media.j3d.BranchGroup
|
+--com.sun.j3d.utils.universe.PlatformGeometry
クラス宣言
public class PlatformGeometry
extends BranchGroup
コンストラクター
public PlatformGeometry()
SimpleUniverseを使って構築した視点側のツリーには、BranchGroupを動的に追加することはできません。BranchGroupではなくPlatformGeometryを生成し、com.sun.j3d.utils.universe.ViewingPlotformの次のメソッドを使って追加する必要があります。
com.sun.j3d.utils.universe.ViewingPlatformのメソッド public void setPlatformGeometry(PlatformGeometry pg)
(6)で PlatformGeometry に KeyNavigatorBehavior を addChild() しています。
(7)でPlatformGeometryをcom.sun.j3d.utils.universe.ViewingPlatformにセットしています。
com.sun.j3d.utils.behaviors.mouse パッケージcom.sun.j3d.utils.behaviors.mouseパッケージのサンプルはすでに簡単に説明しています。ここではマウス操作で変更された TransformGroupの状態を取得する方法について調べてみましょう。
TransformGroup の状態を取得するには? (MouseBehaivorCallback を使ってみる)MouseBehavior によるマウス操作で TransformGroup が変更されますが、この変化を取得する方法は無いでしょうか。
com.sun.j3d.utils.behaviors.mouse パッケージには、このための interface として MouseBehaviorCallback が用意されています。
public interface MouseBehaviorCallback
public static final int ROTATE // MouseRotate による操作
public static final int TRANSLATE // MouseTraslate による操作
public static final int ZOOM // MouseZoom による操作
public void transformChanged(int type, // 回転/移動/ズームのどの操作か
Transform3D transform) // 変更された Transfom3D
MouseBehaivorCallback の使用方法は次のようになります。
MouseBehaviorCallback を implements したクラスを書く。transformChanged() メソッドで変更された Transform3D が取得できる。setupCallback() メソッドで MouseBehavior に設定するMouseBehavior による操作が行われたとき transformChanged() メソッドが呼ばれる。サンプルの実行結果を見てください。

マウスの左ボタンで ColorCube を回転させると、ウインドウ下部の q.x, q.y, q.z, q.w の値が変化します。この値は、変化した Transform3D の回転要素を四元数 (Quotanion) として取得したものです。
四元数とは何でしょうか? 四元数を使うと回転をエレガントに表現でき、 計算が簡単になります。
四元数については巻末の「javax.vecmathパッケージ詳説」をお読みください。
QuatTest.java (一部)
50 private BranchGroup createSceneGraph() {
51 BranchGroup root = new BranchGroup();
52
53 trans = new TransformGroup();
54 trans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
55 trans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
56 trans.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
57
58 BoundingSphere bounds = new BoundingSphere(new Point3d(), 100.0);
59
60 MouseRotate rotator = new MouseRotate(trans);
61 rotator.setSchedulingBounds(bounds);
62 rotator.setupCallback( new MouseBehaviorCallback() { ┐
63 public void transformChanged(int flag, Transform3D trans) { ┐ ├(1)
64 trans.get(quat4f); ...................................(3) ├(2) │
65 tuplePanel.set(quat4f); ..............................(4) │ │
66 } ┘ │
67 }); ┘
68 root.addChild(rotator);
69
70 trans.addChild( new ColorCube(0.4) );
71
72 root.addChild(trans);
73
74 return root;
75 }
(1)でMouseBehaviorCallback を implements した内部クラスを定義し、setupCallback() メソッドで MouseRotate に設定しています。
MouseRotate を使って ColorCube を回転させたときにtransformChanged() メソッドが実行されます。
(2)が transformCharged() メソッドの定義です。(3)の get() で Transform3D での回転をQuat4fに取得しています。取得した Quat4f を(4)で TuplePanel に set() しています。
このサンプルでは回転を取得して表示しているだけですが、
取得したTransform3D を別のオブジェクトに適用することもできます。
com.sun.j3d.utlis.behaviors.pickingcom.sun.j3d.utils.behaivors.mouse.MouseBehavior のサブクラス MouseRotate, MouseTranslate, MouseZoom は、適用対象の TransformGroup をあらかじめ設定しておきました。複数の物体を別個に回転/移動/ズームするにはどうしたら良いでしょうか。
このためには、マウスクリックなどで物体を選択し、 選択した物体に対して回転/移動/ズームなどの操作を適用する必要があります。
この、マウスのクリックなどで物体を選択することを「ピッキング」といいます。
Java 3D でピッキングを行うための概略は次の通りです。
BranchGroup のピッキングのためのメソッド pickAll(), pickAllSorted(), pickClosest(), pickAny() で調べる。戻り値として、交差した物体が格納された javax.media.j3d.SceneGraphPath オブジェクトが返される。com.sun.j3d.utils.behaviors.picking パッケージでは、Ray と交差したもっとも手前の物体を取得し、その「親」の TransformGroup に対して回転/移動/ズームを適用します。複数の物体をそれぞれ別の TransformGroup に addChild() しておけば、ピックした物体をそれぞれ別個に回転/移動/ズームさせることができます。
クラス継承 java.lang.Object | +--javax.media.j3d.SceneGraphObject | +--javax.media.j3d.Node | +--javax.media.j3d.Leaf | +--javax.media.j3d.Behavior | +--com.sun.j3d.utils.behaviors.picking.PickMouseBehavior | |--com.sun.j3d.utils.behaviors.picking.PickRotateBehavior // 回転 | |--com.sun.j3d.utils.behaviors.picking.PickTranslateBehavior // 移動 | +--com.sun.j3d.utils.behaviors.picking.PickZoomBehavior // ズーム クラス宣言 public abstract class PickMouseBehavior extends Behavior public class PickRotateBehavior extends PickMouseBehavior implements MouseBehaviorCallback public class PickTranslateBehavior extends PickMouseBehavior implements MouseBehaviorCallback public class PickZoomBehavior extends PickMouseBehavior implements MouseBehaviorCallback コンストラクター (一部) public PickMouseBehavior(Canvas3D canvas, // 描画対象の Canvas3D BranchGroup root, // ピック対象の BranchGroup Bounds bounds) // スケジューリングが有効になる領域 public PickRotateBehavior(BranchGroup root, // ピック対象の BranchGroup Canvas3D canvas, // 描画対象の Canvas3D Bounds bounds, // スケジューリングが有効になる領域 int pickMode) // ピッキングのモード public PickTranslateBehavior(BranchGroup root, // ピック対象の BranchGroup Canvas3D canvas, // 描画対象の Canvas3D Bounds bounds, // スケジューリングが有効になる領域 int pickMode) // ピッキングのモード public PickZoomBehavior(BranchGroup root, // ピック対象の BranchGroup Canvas3D canvas, // 描画対象の Canvas3D Bounds bounds, // スケジューリングが有効になる領域 int pickMode) // ピッキングのモード com.sun.j3d.utils.behaviors.picking.PickObject の定数フィールド (一部) public static final int USE_BOUNDS // 境界(Bounds)でピックする public static final int USE_GEOMETRY // 幾何学的形状を使ってピックする
PicRotateBehavior, PickTranslateBehavior, PickZoomBehaviorはPickMouseBehaviorのサブクラスです。
PickMouseBehaviorを継承したクラスを書けば、回転/移動/ズーム以外の操作を行うことも可能です。(後の節ではPickMouseBehaviorを継承した簡単なサンプルを書いています)ピッキングには 2種類のモードがあります。
物体のまわりの領域 (境界) を使ってピックするモード (
PickObject.USE_BOUNDSを指定する) と、物体の幾何学的な形状 (ジオメトリ) を使ってピックするモード (PickObject.USE_GEOMETRYを指定する) です。デフォルトではUSE_BOUNDSです。物体のまわりの境界 (Bounds) を使うモードでは、物体の周囲の矩形領域 (javax.media.j3d.BoundingBox) や球状の領域 (javax.media.j3d.BoundingSphere)、または 閉じたポリゴンの境界領域 (javax.media.j3d.BoundingPolytope) を使ってピッキングを行います。処理は
USE_GEOMETRYモードでのピッキングよりも軽くなります。ジオメトリを使うモードでは
javax.media.j3d.Geometryオブジェクトで定義される物体の幾何学的な形状を使ってピッキングを行います。USE_BOUNDSモードよりも正確なピッキングが可能です。ただし、ピックする可能性のあるすべてのGeometryのALLOW_INTERSECTビットを (setCapability()で) 設定する必要があります。サンプルの実行結果を見てください。

複数の物体それぞれを回転/移動/ズームすることができます。マウスボタンは com.sun.j3d.utils.behaviors.mouse パッケージのときと同じです。
| マウスボタン | 操作 |
|---|---|
| 左ドラッグ | 回転 |
| 中央ドラッグ | ズーム (Z軸方向への移動) |
| 右ドラッグ | 移動 (X軸、Y軸方向) |
このサンプルのソースは次の通りです。
PickMouseTest.java (一部)
48 private BranchGroup createSceneGraph() {
49 BranchGroup root = new BranchGroup();
50
51 BoundingSphere bounds = new BoundingSphere( new Point3d(), 100.0 );
52
53 PickRotateBehavior rotator =
54 new PickRotateBehavior(root, canvas, bounds, PickObject.USE_GEOMETRY); ...(1)
55 root.addChild(rotator);.....................................................(2)
56
57 PickTranslateBehavior translator = ┐
58 new PickTranslateBehavior(root, canvas, bounds, PickObject.USE_GEOMETRY); │
59 root.addChild(translator); ├(3)
60 │
61 PickZoomBehavior zoomer = │
62 new PickZoomBehavior(root, canvas, bounds, PickObject.USE_GEOMETRY); │
63 root.addChild(zoomer); ┘
:
:
163 }
(1)で PickRotateBehavior を生成しています。コンストラクターの引数はピック対象の BranchGroup、描画に使用する Canvas3D、スケジューリングが有効になる領域 (Bounds)、ピッキングのモードです。ここではピッキングのモードに USE_GEOMETRY を指定しています。
(2)で BranchGroup に PickRotateBehavior を addChild() しています。(3)ではこれと同様に PickTranslateBehavior, PickZoomBehavior の生成、addChild() を行っています。
ピッキングのためのBehaviorを生成し、
シーングラフに追加した後は、LineArray, Box, Cylinder, Cone, Sphere を生成し、capability bit の設定を行っています。このサンプルでは USE_GEOMETRY モードでピッキングを行っているので、つぎの設定が必要です。
| 対象 | capability bit | 意味 |
|---|---|---|
| TransformGroup | ALLOW_TRANSFORM_READ | ピック時に Transform3D を読み取る |
| ALLOW_TRANSFORM_WRITE | 回転/移動/ズームを適用する | |
| ENABLE_PICK_REPORTING | ピックされたことを"報告"する | |
| Geometry | ALLOW_INTERSECT | 交差を許可する |
| Shape3D | ALLOW_GEOMETRY_READ | ピック時に Geometry を読み取る |
| Primitive | ENABLE_GEOMETRY_PICKING | ジオメトリでのピッキングを可能にする |
ピックされたときに、回転/移動/ズーム以外の操作を行うにはどうしたら良いでしょうか。
前の節で書いた通り、com.sun.j3d.utils.behaviors.picking.PickMouseBehavior を継承したクラスを書けば独自のピッキング処理を行うことができます。
ここでは、ピックされた物体を特定する処理を書いてみましょう。
まず、ピッキングの際に実行されるメソッドを定義した interface を書きました。
SimplePickingCallback.java
1 // Java 3Dテスト用プログラム
2 // SimplePickingCallback.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.Node;
8
9 public interface SimplePickingCallback {
10 void picked(int nodeType, Node node);
11 }
ピッキングの際に picked() メソッドを実行することにします。引数として渡すのは、ピックされた javax.media.j3d.Node の種類と、ピックされたNode オブジェクトです。
ピックされた Node の種類の指定には、PickObject の定数フィールドを使用します。
com.sun.j3d.utils.behaviors.picking.PickObject の定数フィールド (一部) public static final int SHAPE3D // javax.media.j3d.Shape3D オブジェクト public static final int MORPH // javax.media.j3d.Morph オブジェクト public static final int PRIMITIVE // com.sun.j3d.utils.geometry.Primitive オブジェクト public static final int LINK // javax.media.j3d.Link オブジェクト public static final int GROUP // javax.media.j3d.Group オブジェクト public static final int TRANSFORM_GROUP // javax.media.j3d.TransformGroup オブジェクト public static final int BRANCH_GROUP // javax.media.j3d.BranchGroup オブジェクト public static final int SWITCH // javax.media.j3d.Switch オブジェクト
つぎに PickMouseBehavior を継承したクラスを書きます。
SimplePicking.java
1 // Java 3Dテスト用プログラム
2 // SimplePicking.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 com.sun.j3d.utils.behaviors.picking.*;
9
10 public class SimplePicking extends PickMouseBehavior {
11 protected SimplePickingCallback callback = null;
12 protected int pickMode = PickObject.USE_BOUNDS;
13 protected int nodeType = PickObject.SHAPE3D;
14
15 public SimplePicking(BranchGroup root, Canvas3D canvas, Bounds bounds) {
16 super(canvas, root, bounds);
17
18 this.setSchedulingBounds(bounds);
19 }
20
21 public SimplePicking( BranchGroup root, Canvas3D canvas, Bounds bounds,
22 int mode )
23 {
24 super(canvas, root, bounds);
25 pickMode = mode;
26
27 this.setSchedulingBounds(bounds);
28 }
29
30 public SimplePicking( BranchGroup root, Canvas3D canvas, Bounds bounds,
31 int mode, int type)
32 {
33 super(canvas, root, bounds);
34 pickMode = mode;
35 nodeType = type;
36
37 this.setSchedulingBounds(bounds);
38 }
39
40 public void setPickMode(int mode) { pickMode = mode; }
41 public int getPickMode() { return pickMode; }
42
43 public void setNodeType(int type) { nodeType = type; }
44 public int getNodeType() { return nodeType; }
45
46 public void setupCallback(SimplePickingCallback callback) { this.callback = callback; }
47
48 public void updateScene(int x, int y) {
49 Node node = pickScene.pickNode(pickScene.pickClosest(x, y), nodeType);
50 callback.picked(nodeType, node);
51 }
52 }
PickMouseBehavior は、ピッキングの際に updateScene() メソッドを実行します。PickMouseBehavior のサブクラスでは、updateScene() メソッドを実装してピッキングの処理を書きます。
ここでは、マウスクリックした位置に最も近い Node を取得しています。このとき、Node の種類を指定していますが、この種類は SimplePicking のコンストラクターで与えたものです。デフォルトでは PickObject.SHAPE3D です。
クリックした位置に最も近い Node を取得したら、SimplePickingCallback を implements したクラスの picked() メソッドを実行します。
以前のサンプルに SimplePicking を追加してみましょう。
PickMouseTest.java (一部)
48 private BranchGroup createSceneGraph() {
49 BranchGroup root = new BranchGroup();
50
51 BoundingSphere bounds = new BoundingSphere( new Point3d(), 100.0 );
52
53 PickRotateBehavior rotator =
54 new PickRotateBehavior(root, canvas_, bounds, PickObject.USE_GEOMETRY);
55 root.addChild(rotator);
56
57 PickTranslateBehavior translator =
58 new PickTranslateBehavior(root, canvas_, bounds, PickObject.USE_GEOMETRY);
59 root.addChild(translator);
60
61 PickZoomBehavior zoomer =
62 new PickZoomBehavior(root, canvas_, bounds, PickObject.USE_GEOMETRY);
63 root.addChild(zoomer);
64
65 SimplePicking picker =
66 new SimplePicking( root, canvas_, bounds,
67 PickObject.USE_GEOMETRY, PickObject.PRIMITIVE ); ...(1)
68 picker.setupCallback( new SimplePickingCallback() {
69 public void picked(int type, Node node) { ┐
70 if (node != null) { ┐ │
71 String data = (String)node.getUserData(); ...(4) │ │
72 System.out.println(data ); ├(3) ├(2)
73 } else { │ │
74 System.out.println("Error: node is null."); │ │
75 } ┘ │
76 } ┘
77 });
78 root.addChild(picker);........................................(5)
:
:
119 Transform3D bt3d = new Transform3D();
120 bt3d.set(new Vector3d(-0.4, 0.4, 0.0));
121 TransformGroup btrans = new TransformGroup(bt3d);
122 btrans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
123 btrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
124 btrans.setCapability(TransformGroup.ENABLE_PICK_REPORTING);
125 trans.addChild(btrans);
126
127 Box box =
128 new Box( 0.25f, 0.25f, 0.25f,
129 Primitive.GENERATE_TEXTURE_COORDS |
130 Primitive.ENABLE_GEOMETRY_PICKING,
131 ap );
132 box.setUserData("this is box.");...............................(6)
133 btrans.addChild(box);
:
:
182 }
(1)で SimplePicking を生成しています。ピッキングは USE_GEOMETRY モード、ピックする物体の種類は PickObject.PRIMITIVE です。
(2)でSimplePickingCallback を implements し
た内部クラスを定義し、setupCallback() メソッドで
SimplePicking に設定しています。
(3)が picked() メソッドの定義です。
(4)でgetUserData() メソッドを使って Node に追加されたユーザー・データを取得しています。このサンプルではユーザー・データは String です。
setUserData(), getUserData() は、
javax.media.j3d.Node のスーパー・クラスである
javax.media.j3d.SceneGraphObject のメソッドです。
javax.media.j3d.SceneGraphObject のメソッド (一部) public void setUserData(java.lang.Object userData) // ユーザー固有のデータ public java.lang.Object getUserData() // ユーザー固有データを取得
SceneGraphObject のサブクラスでは、setUserData(), getUserData() を使ってユーザー固有のデータを設定/取得することができます。
(5)で SimplePicking を BranchGroup に addChild() しています。
(6)でsetUserData() を使って
String ("this is box.") を設定しています。
以下同様に、Cone, Cylinder, Sphereにも
setUserData()でStringを設定しました。
実行結果は次の通りです。クリックした物体をコンソール表示します。

$ java PickMouseTest this is box. this is cylinder. this is cone. this is sphere. this is box. this is cylinder. this is cone. this is sphere.
従来の Java のイベントモデル、Delegation Listener モデルを使ったイベント処理も可能です。この場合、Canvas3D に対して add*Listener() してください。Container に add*Listener() してもイベントは通知されません。
サンプルソースは次の通りです。
PickSelectionFeedback.java
1 // Java 3Dテスト用アプレット
2 // PickSelectionFeedback.java
3 // Copyright (c) 1999 ENDO Yasuyuki
4 // mailto:yasuyuki@javaopen.org
5 // http://www.javaopen.org/j3dbook/index.html
6
7 import java.awt.*;
8 import java.awt.event.*;
9 import javax.media.j3d.*;
10 import javax.vecmath.*;
11 import com.sun.j3d.utils.geometry.Primitive;
12 import com.sun.j3d.utils.geometry.Cone;
13 import com.sun.j3d.utils.geometry.Cylinder;
14 import com.sun.j3d.utils.geometry.Box;
15 import com.sun.j3d.utils.behaviors.picking.PickObject;
16 import com.sun.j3d.utils.behaviors.picking.PickRotateBehavior;
17 import com.sun.j3d.utils.behaviors.picking.PickTranslateBehavior;
18 import com.sun.j3d.utils.behaviors.picking.PickZoomBehavior;
19 import com.sun.j3d.utils.applet.MainFrame;
20 import com.sun.j3d.utils.universe.*;
21
22 public class PickSelectionFeedback extends java.applet.Applet {
23 private Canvas3D canvas = null;
24 private BranchGroup scene = null;
25
26 public PickSelectionFeedback() {
27 this.setLayout(new BorderLayout());
28
29 GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
30
31 canvas = new Canvas3D(config);
32 this.add(canvas, BorderLayout.CENTER);
33
34 //これでマウスイベントが通知される
35 canvas.addMouseListener(new MouseAdapter() { ┐
36 public void mouseClicked( MouseEvent e ){ ┐ │
37 Node node = pickNode(e.getX(), e.getY());.....(3) │ │
38 if (node != null) { ├(2) │
39 String data = (String)node.getUserData();...(4) │ ├(1)
40 System.out.println("UserData=" + data);.....(5) │ │
41 } │ │
42 } ┘ │
43 }); ┘
44
45 SimpleUniverse universe = new SimpleUniverse(canvas);
46 universe.getViewingPlatform().setNominalViewingTransform();
47
48 scene = createSceneGraph();
49
50 universe.addBranchGraph(scene);
51 }
52
53 private Node pickNode(int mx, int my) { ┐
54 PickObject pickObject = new PickObject(canvas, scene);.............(7) │
55 SceneGraphPath sgPath = │
pickObject.pickClosest(mx, my, PickObject.USE_GEOMETRY);..........(8) ├(6)
56 if (sgPath == null) return null; │
57 Node node = pickObject.pickNode(sgPath, PickObject.PRIMITIVE, 1);)...(9)│
58 return node;........................................................(10)│
59 } ┘
(1)で java.awt.event.MouseAdapter を継承した内部クラスを定義し、addMouseListener() メソッドで Canvas3D に設定しています。(2)が mouseClicked() メソッドの定義です。(3)では、クリックされた位置のマウス座標を渡して、private メソッドである pickNode() を実行しています。戻り値はピックされた Node です。(4)で getUserData() を使って取得したユーザー定義データを、(5)でコンソール表示しています。
(6)が pickNode() メソッドの定義です。(7)で PickObject を生成しています。(8)でpickClosest() メソッドで、クリック位置に最も近い Node が格納された SceneGraphPath を取得しています。
(9)で SceneGraphPath に格納されている最初の Primitive を取得し、(10)で戻り値として返しています。
このサンプルの実行結果は次のようになります。

イベントリスナーを使ったイベント処理も可能ですが、
なるべくBehavior を使うことをおすすめします。
Java 3D の描画スケジューリングの管理外に、
別のイベント処理が存在するのは良い設計ではありません。
今までは、すでに提供されている Behaviar を使って来ました。自分で Behaviar を継承したクラスを書くにはどうしたら良いでしょうか?
Behavior を継承したクラスでは、次のことを行う必要があります。
initialize() メソッドの最後で、起動条件 (javax.media.j3d.WakeupCondition) を指定して wakeupOn() メソッドを実行する。processStimulus() メソッドが実行されるようになる。processStimulus() メソッドの最後で、次回の起動条件 (WakeupCondition) を指定して wakeupOn() メソッドを実行する。initialize()のときとは違う起動条件 (WakeupCondition) を指定することもできる。wakeupOn() を実行せずに、イベントを 1回で終らせることも出来る。では、最小限の Behavior を書いてみましょう。指定のミリ秒が経過するたびにコンソールにメッセージを表示するテスト用のサンプルです。
ここでは、起動条件として、javax.media.j3d.WakeupOnElapsedTime を使用しました。
クラス継承
java.lang.Object
|
+--javax.media.j3d.WakeupCondition
|
+--javax.media.j3d.WakeupCriterion
|
+--javax.media.j3d.WakeupOnElapsedTime
クラス宣言
public final class WakeupOnElapsedTime
extends WakeupCriterion
コンストラクター
public WakeupOnElapsedTime(long milliseconds) // 起動までのミリ秒
ソースコードは次の通りです。
TimerBehavior.java
1 // Java 3Dテスト用プログラム
2 // TimerBehavior.java
3 // Copyright (c) 1999 ENDO Yasuyuki
4 // mailto:yasuyuki@javaopen.org
5 // http://www.javaopen.org/j3dbook/index.html
6
7 import java.util.Enumeration;
8 import javax.media.j3d.Behavior;
9 import javax.media.j3d.WakeupOnElapsedTime;
10
11 public class TimerBehavior extends Behavior {
12 protected WakeupOnElapsedTime wup = null; // 起動条件になる WakeupCriterion
13
14 public TimerBehavior(long sleep) {
15 super();
16 wup = new WakeupOnElapsedTime(sleep); // ミリ秒を指定して起動条件を生成 ...(1)
17 }
18
19 public void initialize() { ┐
20 System.out.println("initialize()."); //DEBUG ├(2)
21 wakeupOn(wup); // 起動条件の設定 ...(3) │
22 } ┘
23
24 public void processStimulus(Enumeration criteria) { ┐
25 System.out.println("processStimulus()."); //DEBUG ├(4)
26 wakeupOn(wup); // 次回の起動条件 ...(5) │
27 } ┘
28 }
コンストラクター(1)では、引数で取得したミリ秒を使って WakeupOnElapsedTime を生成しています。
(2)が initialize() メソッドの定義です。(3)で wakeupOn() メソッドを実行しています。引数は WakeupOnElapsedTime です。これで、指定のミリ秒が経過した後に processStimulus() メソッドが実行されるようになります。
(4)が processStimulus() メソッドの定義です。(5)で wakeupOn() メソッドを実行しています。引数は WakeupOnElapsedTime です。これで、指定のミリ秒が経過した後に、ふたたび processStimulus() メソッドが実行されます。
TimerBehavior の使用方法は次の通りです。
TimerBehavior を生成するsetSchedulingBounds() メソッドでスケジューリングが有効になる領域 (Bounds) を設定するBranchGroup に addChild() する実際のコード例を次に示します。
TimerBehavior timer = new TimerBehavior(1000); // 1秒後に起動
timer.setSchedulingBounds(new BoundingSphere(new Point3d(), 100.0));
root.addChild(timer);
今までのどのサンプルでもかまいません。
TimerBehavior を追加してみてください。実行結果は次のようになります。
$ java TimerBehaviorTest processStimulus(). processStimulus(). processStimulus(). processStimulus(). processStimulus(). processStimulus(). processStimulus().
initialisze() での初期の起動条件の指定や、processStimulus() での次回以降の起動条件の指定はわかりましたが、このままでは何の役にも立たないので、processStimulus() の中で起動するメソッドを定義した interface を書いてみます。
TimerBehaviorCallback.java
1 // Java 3Dテスト用プログラム
2 // TimerBehaviorCallback.java
3 // Copyright (c) 1999 ENDO Yasuyuki
4 // mailto:yasuyuki@javaopen.org
5 // http://www.javaopen.org/j3dbook/index.html
6
7 public interface TimerBehaviorCallback {
8 void wakeup();
9 }
TimerBehavior のコンストラクターでこの interface を impluments したクラスを指定し、processStimulus() メソッドが実行されたときに TimerBehaviorCallback#wakeup() を実行してやることにします。
TimerBehavior.java
1 // Java 3Dテスト用プログラム
2 // TimerBehavior.java
3 // Copyright (c) 1999 ENDO Yasuyuki
4 // mailto:yasuyuki@javaopen.org
5 // http://www.javaopen.org/j3dbook/index.html
6
7 import java.util.Enumeration;
8 import javax.media.j3d.Behavior;
9 import javax.media.j3d.WakeupOnElapsedTime;
10
11 public class TimerBehavior extends Behavior {
12 protected WakeupOnElapsedTime wup = null; // 起動条件になる WakeupCriterion
13 protected TimerBehaviorCallback callback = null; // 起動時に呼ぶオブジェクト
14
15 public TimerBehavior(long sleep, TimerBehaviorCallback newCallback) {
16 super();
17 wup = new WakeupOnElapsedTime(sleep); // ミリ秒を指定して起動条件を生成
18 callback = newCallback; // 起動時に呼ぶオブジェクトの登録
19 }
20
21 public void initialize() {
22 System.out.println("initialize()."); //DEBUG
23 wakeupOn(wup); // 起動条件の設定
24 }
25
26 public void processStimulus(Enumeration criteria) {
27 //System.out.println("processStimulus()."); //DEBUG
28
29 if (callback != null) callback.wakeup(); // 外部オブジェクトを呼ぶ
30
31 wakeupOn(wup); // 次回の起動条件
32 }
33 }
TimerBehaviorCallback を
implements したクラスを書いて、
wakeup() メソッドで起動時の処理を書けば、
一定の間隔で動作する処理を実現できます。
TimerBehavior timer =
new TimerBehavior( 1000, new TimerBehaviorCallback() {
public void wakeup() {
// 起動されたときにやりたい処理
}
});
timer.setSchedulingBounds(new BoundingSphere(new Point3d(), 100.0));
root.addChild(timer);
WakeupCondition のいろいろこれまで WakeupOnElapsedTime を使って簡単な Behavior を書いてみました。
ここでは Behavior の起動条件を宣言するためのクラスである WakeupCondition について少し詳しく見てみたいと思います。
クラス継承
java.lang.Object
|
+--javax.media.j3d.WakeupCondition // 起動条件基底クラス
|
+--javax.media.j3d.WakeupAnd // 起動条件配列のAND
|
+--javax.media.j3d.WakeupAndOfOrs // (起動条件配列のOR)配列のAND
|
+--javax.media.j3d.WakeupOr // 起動条件配列のOR
|
+--javax.media.j3d.WakeupOrOfAnds // (起動条件配列のAND)配列のOR
|
+--javax.media.j3d.WakeupCriterion
|
+--javax.media.j3d.WakeupOnActivation // Behavior がアクティブ化された
|
+--javax.media.j3d.WakeupOnAWTEvent // AWT イベントが発生した
|
+--javax.media.j3d.WakeupOnBehaviorPost // 他のBehaviorから起動された
|
+--javax.media.j3d.WakeupOnCollisionEntry // 物体が他の物体に衝突した
|
+--javax.media.j3d.WakeupOnCollisionExit // 物体が他の物体と衝突しなくなった
|
+--javax.media.j3d.WakeupOnCollisionMovement // 物体が他の物体と衝突したまま移動した
|
+--javax.media.j3d.WakeupOnDeactivation // Behaviorが非アクティブ化された
|
+--javax.media.j3d.WakeupOnElapsedFrame // 指定のFrameが経過した
|
+--javax.media.j3d.WakeupOnElapsedTime // 指定のミリ秒が経過した
|
+--javax.media.j3d.WakeupOnSensorEntry // Sensorの中心が指定領域に入った
|
+--javax.media.j3d.WakeupOnSensorExit // Sensorの中心が指定領域から外れた
|
+--javax.media.j3d.WakeupOnTransformChange // 指定の TransformGroup が変更された
|
+--javax.media.j3d.WakeupOnViewPlatformEntry // ViewPlatformの中心が指定領域に入った
|
+--javax.media.j3d.WakeupOnViewPlatformExit // ViewPlatformの中心が指定領域から外れた
クラス宣言
public abstract class WakeupCriterion
extends WakeupCondition
実際の起動条件となるのは WakeupCriterion のサブクラスです。はじめのサンプルで使った WakeupOnElapsedTime や、マウス、キーボードなどのイベント処理を行う WakeupOnAWTEvent、衝突判定を行う WakeupOnCollisionEntry, WakeupOnCollisionExit, WakeupOnCollisionMovement など様々なクラスがあります。
そして、これらの起動条件の配列 (WakeupCriterion[]) の AND, OR、またはそれらの混合を得るための WakeupAnd, WakeupAndOfOrs, WakeupOr, WakeupOrOfAnds など WakeupCondition の直接のサブクラスもあります。
これらの起動条件を組み合わせていろいろな Behavior を書くことができます。
つぎの項目では WakeupOnAWTEvent を使ってキーボードをイベント処理する簡単なサンプルを書いてみます。
WakeupOnAWTEvent を使ったキーボード処理を書いてみます。
クラス継承
java.lang.Object
|
+--javax.media.j3d.WakeupCondition
|
+--javax.media.j3d.WakeupCriterion
|
+--javax.media.j3d.WakeupOnAWTEvent
クラス宣言
public final class WakeupOnAWTEvent
extends WakeupCriterion
コンストラクター
public WakeupOnAWTEvent(int AWTId) // java.awt.Event クラスのイベント定数
public WakeupOnAWTEvent(long eventMask) // java.awt.AWTEvent クラスのイベントマスク定数
WakeupOnAWTEvent のコンストラクターでは起動条件となる java.awt.Event クラスのイベント定数、または java.awt.AWTEvent クラスのイベントマスク定数を指定します。
27 kpress = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED);
ここでは java.awt.KeyEvent.KEY_PRESSED イベント定数を指定しています。もし複数のAWTイベントを処理する必要がある場合は次のようにします。
WakuepOnAWTEvent の配列を用意するWakeupOnAWTEvent オブジェクトを生成するWakeupOnAWTEvent で WakeupOnAWTEvent 配列の各要素を初期化するWakuepOnAWTEvent 配列をコンストラクター引数に指定して WakeupAnd や WakeupOr を生成するWakeupAnd/WakeupOr を引数にして、initialize() メソッドの最後で wakeupOn() を実行するBehavior の initialize() では起動条件となる WakeupOnAWTEvent を指定して wakuepOn() を実行します。
28 wakeupOn(kpress);
指定されたイベントが発生すると、Java 3D は Behavior オブジェクトの processStimulus() を実行します。
processStimulus() では、引数に渡って来た java.util.Enumeration から WakeupCondition を取り出します。
31 public void processStimulus(Enumeration criteria) {
32 WakeupOnAWTEvent wevent = null;
33 AWTEvent[] events = null;
34 while (criteria.hasMoreElements()) {
35 wevent = (WakeupOnAWTEvent)criteria.nextElement();
:
:
78 }
ここでは nextElement()取り出した WakeupCondition を initialize() で指定した WakeupOnAWTEvent にキャストして変数 wevent に代入しています。
実際の java.awt.AWTEvent は WakeupOnAWTEvent#getAWTEvent() メソッドで配列として取り出します。
36 events = wevent.getAWTEvent();
取り出した AWTEvent を一つづつ処理します。
37 for (int i=0; i<events.length; i++) {
38 if (events[i] instanceof KeyEvent) {
:
:
75 }
76 }
ここではキーイベントを仮想キーコードで処理しています。
39 int code = ((KeyEvent)events[i]).getKeyCode();
40 switch (code) {
41 case KeyEvent.VK_UP:
:
47 break;
48 case KeyEvent.VK_DOWN:
:
54 break;
55 case KeyEvent.VK_RIGHT:
:
64 break;
65 case KeyEvent.VK_LEFT:
:
74 break;
75 }
ここでは KEY_PRESS イベントしか処理していません。Sun のユーティリティー・パッケージ com.sun.j3d.utils.behaviors.keyboard.KeyNavigatorBehavior では KEY_PRESSED, KEY_RELEASED を処理しているので、複数のキーイベントを処理する参考になると思います。
キーイベント処理サンプルのソースは次の通りです。
SimpleKeyBehavior.java
1 // Java 3Dテストプログラム
2 // SimpleKeyBehavior.java
3 // Copyright (c) 1999 ENDO Yasuyuki
4 // mailto:yasuyuki@javaopen.org
5 // http://www.javaopen.org/j3dbook/index.html
6
7 import java.awt.event.*;
8 import java.awt.AWTEvent;
9 import java.util.*;
10 import javax.media.j3d.*;
11 import javax.vecmath.*;
12
13 public class SimpleKeyBehavior extends Behavior {
14 private static final double ANGLE = Math.PI / 180.0;
15 private static final double STEP = 0.1;
16 private Transform3D t3d = new Transform3D();
17 private Transform3D ct3d = new Transform3D();
18 private Matrix4d matrix = new Matrix4d();
19 private TransformGroup trans = null;
20 private WakeupOnAWTEvent kpress = null;
21
22 public SimpleKeyBehavior(TransformGroup aTrans) {
23 trans = aTrans;
24 }
25
26 public void initialize() {
27 kpress = new WakeupOnAWTEvent(KeyEvent.KEY_PRESSED);
28 wakeupOn(kpress);
29 }
30
31 public void processStimulus(Enumeration criteria) {
32 WakeupOnAWTEvent wevent = null;
33 AWTEvent[] events = null;
34 while (criteria.hasMoreElements()) {
35 wevent = (WakeupOnAWTEvent)criteria.nextElement();
36 events = wevent.getAWTEvent();
37 for (int i=0; i<events.length; i++) {
38 if (events[i] instanceof KeyEvent) {
39 int code = ((KeyEvent)events[i]).getKeyCode();
40 switch (code) {
41 case KeyEvent.VK_UP:
42 //System.out.println("VK_UP");//DEBUG
43 t3d.set(new Vector3d(0.0, 0.0, -STEP));
44 trans.getTransform(ct3d);
45 ct3d.mul(t3d);
46 trans.setTransform(ct3d);
47 break;
48 case KeyEvent.VK_DOWN:
49 //System.out.println("VK_DOWN");//DEBUG
50 t3d.set(new Vector3d(0.0, 0.0, STEP));
51 trans.getTransform(ct3d);
52 ct3d.mul(t3d);
53 trans.setTransform(ct3d);
54 break;
55 case KeyEvent.VK_RIGHT:
56 //System.out.println("VK_RIGHT");//DEBUG
57 t3d.rotY(-ANGLE);
58 trans.getTransform(ct3d);
59 ct3d.get(matrix);
60 ct3d.setTranslation(new Vector3d(0.0, 0.0, 0.0));
61 ct3d.mul(t3d);
62 ct3d.setTranslation(new Vector3d(matrix.m03, matrix.m13, matrix.m23));
63 trans.setTransform(ct3d);
64 break;
65 case KeyEvent.VK_LEFT:
66 //System.out.println("VK_LEFT");//DEBUG
67 t3d.rotY(ANGLE);
68 trans.getTransform(ct3d);
69 ct3d.get(matrix);
70 ct3d.setTranslation(new Vector3d(0.0, 0.0, 0.0));
71 ct3d.mul(t3d);
72 ct3d.setTranslation(new Vector3d(matrix.m03, matrix.m13, matrix.m23));
73 trans.setTransform(ct3d);
74 break;
75 }
76 }
77 }
78 }
79 wakeupOn(kpress);
80 }
81
82 public Node cloneNodeComponent(boolean forceDuplication) {
83 SimpleKeyBehavior simpleKeyBehavior = new SimpleKeyBehavior(trans);
84 simpleKeyBehavior.duplicateNode(this, forceDuplication);
85 return simpleKeyBehavior;
86 }
87
88 public void duplicateNode(Node node, boolean forceDuplication) {
89 super.duplicateNode(node, forceDuplication);
90 }
91
92 public void updateNodeReference(NodeReferenceTable table) {
93 super.updateNodeReferences(table);
94 TransformGroup newTransformGroup =
95 (TransformGroup)table.getNewObjectReference(trans);
96 trans = newTransformGroup;
97 }
98 }
99
cloneNodeComponent(), duplicateNode(), updateNodeReference()の3つのメソッドは、cloneTree()を正しく処理するために必要です。
キーボードの矢印キー [↑][↓]で前進/後退、[←][→]で左方向、右方向への回転を行っています。
衝突判定には次の WakuepCriterion (のサブクラス) を使用します。
javax.media.j3d.WakeupOnCollisionEntry // 物体が他の物体に衝突した javax.media.j3d.WakeupOnCollisionExit // 物体が他の物体と衝突しなくなった javax.media.j3d.WakeupOnCollisionMovement // 物体が他の物体と衝突したまま移動した
注意点としては、
WakeupOnCollisionEntry が発生しても、
必ず WakeupOnCollisionExit が発生するとは限らない、
ということがあります。
物体が高速で移動しているために
WakeupOnCollisionExit が発生せずに通過してしまう場合があります。
ここでは WakeupOnCollisionMovement を使って、壁にぶつかった物体を跳ね返す動作をさせる Behavior を書いてみます。
クラス継承
java.lang.Object
|
+--javax.media.j3d.WakeupCondition
|
+--javax.media.j3d.WakeupCriterion
|
+--javax.media.j3d.WakeupOnCollisionMovement
クラス宣言
public final class WakeupOnCollisionMovement
extends WakeupCriterion
コンストラクター
public WakeupOnCollisionMovement(SceneGraphPath armingPath) // 衝突を検知したい SceneGraphPah
public WakeupOnCollisionMovement(SceneGraphPath armingPath, // 衝突を検知したい SceneGraphPath
int speedHint) // 幾何学形状で検知するか領域で検知するか
public WakeupOnCollisionMovement(Node armingNode) // 衝突を検知したい Node
public WakeupOnCollisionMovement(Node armingNode, // 衝突を検知したい Node
int speedHint) // 幾何学形状で検知するか領域で検知するか
public WakeupOnCollisionMovement(Bounds armingBounds) // 衝突を検知したい領域
定数フィールド
public static final int USE_GEOMETRY // 幾何学形状で衝突を検知する
public static final int USE_BOUNDS // 領域で衝突を検知する
コンストラクター引数に指定された SceneGraphPath, Node, Bounds に、別の物体が衝突(交差)しながら移動したときに Behavior が起動されるようになります。
speedHint には 定数 USE_BOUNDS または USE_GEOMETRY を指定します。デフォルトは USE_BOUNDS です。
USE_BOUNDS では領域 (javax.media.j3d.Bounds) を使って衝突判定するため比較的高速な判定が可能ですが、正確な衝突判定は難しくなります。
USE_GEOMETRY を指定すると正確な判定が可能ですが、衝突判定処理のために実行速度がかなり遅くなります。また、物体が速く動く場合には衝突判定が難しくなります。
WakeupOnCollisionMovement には、衝突の検知対象や、衝突相手を得るためのメソッドが用意されています。
メソッド public SceneGraphPath getArmingPath() // 衝突検知対象の SceneGraphPath public Bounds getArmingBounds() // 衝突検知対象の領域 public SceneGraphPath getTriggeringPath() // 衝突相手の SceneGraphPath public Bounds getTriggeringBounds() // 衝突相手の領域
javax.media.j3d.SceneGraphPath には、格納されている様々なオブジェクトを取得するメソッドが用意されています。
SceneGraphPath のメソッド (一部) public final Node getObject() // 終端の Node を取得する
サンプルでは衝突相手の物体を取得するために SceneGraphPath#getObject() を使っています。
サンプルの実行結果を見てください。

前のサンプルで作成した SimpleKeyBehavior で視点側の TransformGroup を移動させています。
視点の前と後ろに ColorCube を追加し、それぞれ前方と後方の衝突を検知させています。ColorCube が "壁" である Text3D に衝突すると、進行方向の反対方向に跳ね返るようにしています。
衝突検知と跳ね返りによる移動は次のようにしました。
Text3D に、その"親"である TransformGroup を setUserData() でセットしておくTransformGroup、視点の前と後ろのColorCube、進行方向ベクトルを指定し、衝突検知用の Behavoir を生成するSceneGraphPath を取得し、getObject() メソッドで"壁"の Node を取得するgetUserData() で"壁"の TransformGroup を取得する衝突判定の Behavior のソースは次の通りです。
WallCollisionBehavior.java
1 // Java 3Dテストプログラム
2 // WallCollisionBehavior.java
3 // Copyright (c) 1999 ENDO Yasuyuki
4 // mailto:yasuyuki@javaopen.org
5 // http://www.javaopen.org/j3dbook/index.html
6
7 import java.util.*;
8 import javax.media.j3d.*;
9 import javax.vecmath.*;
10
11 public class WallCollisionBehavior extends Behavior {
12 public static final int USE_BOUNDS = WakeupOnCollisionMovement.USE_BOUNDS;
13 public static final int USE_GEOMETRY = WakeupOnCollisionMovement.USE_GEOMETRY;
14
15 private static final Vector3d ORIGIN = new Vector3d(0.0, 0.0, 0.0); // 原点
16 private static final double VSCALE = 0.1; // 衝突時の移動量
17
18 private Transform3D ot3d = new Transform3D(); // 衝突を検知したい物体のT3D
19 private TransformGroup otrans = null; // 衝突を検知したい物体のTG
20 private Node onode = null; // 衝突を検知したい物体
21 private Vector3d ovector = null; // 衝突を検知したい物体の進行方向
22
23 private Transform3D nt3d = new Transform3D(); // 壁のT3D
24 private Transform3D rt3d = new Transform3D(); // 衝突による移動
25
26 private int hint = USE_BOUNDS; // 領域検査か幾何学的検査か
27 private WakeupOnCollisionMovement move = null; // Behavior起動条件
28
29 public WallCollisionBehavior(TransformGroup trans, Node node, Vector3d vector) {
30 this(trans, node, vector, USE_BOUNDS);
31 }
32
33 public WallCollisionBehavior(TransformGroup trans, Node node, Vector3d vector, int hint) {
34 onode = node;
35 otrans = trans;
36 ovector = vector;
37 hint = hint;
38 }
39
40 public void initialize() {
41 move = new WakeupOnCollisionMovement(onode, hint);
42 wakeupOn(move);
43 }
44
45 public void processStimulus(Enumeration criteria) {
46 while (criteria.hasMoreElements()) {
47 WakeupOnCollisionMovement wakeup =
48 (WakeupOnCollisionMovement)criteria.nextElement();
49 SceneGraphPath npath = wakeup.getTriggeringPath(); // 衝突した壁を含むPath
50 Node node = npath.getObject();
51 System.out.println("\nnode.getBounds()=" + node.getBounds());//DEBUG
52 System.out.println("onode.getBounds()=" + onode.getBounds());//DEBUG
53 TransformGroup ntrans = (TransformGroup)node.getUserData();//壁にセットしてあったTG
54 if (ntrans != null) {
55 ntrans.getTransform(nt3d); // 壁のT3D
56 System.out.println("nt3d=\n" + nt3d);//DEBUG
57 nt3d.setTranslation(ORIGIN); // 原点に戻しておく
58 Vector3d nvec = new Vector3d(0.0, 0.0, 1.0); // 壁の法線ベクトル初期値
59 nt3d.transform(nvec); // 壁の法線ベクトルを計算
60
61 otrans.getTransform(ot3d); // 視点のT3D
62 System.out.println("o.x=" + ovector.x + ", o.y=" + ovector.y + ", o.z=" + ovector.z);//DEBUG
63 System.out.println("ot3d=\n" + ot3d);//DEBUG
64 ot3d.setTranslation(ORIGIN); // 原点に戻しておく
65 ot3d.transpose(); // 視点マトリックスの転置
66 ot3d.transform(nvec); // 壁の法線を視点座標に変換
67 System.out.println("n.x=" + nvec.x + ", n.y=" + nvec.y + ", n.z=" + nvec.z);//DEBUG
68
69 Vector3d onegate = new Vector3d(); // 物体の逆ベクトル
70 onegate.negate(ovector); // 逆ベクトルを計算
71 double odotn = onegate.dot(nvec); // 内積 O・N
72 System.out.println("odotn=" + odotn);//DEBUG
73 nvec.scale(2.0 * odotn); // 2(O・N)N
74 System.out.println("n.x=" + nvec.x + ", n.y=" + nvec.y + ", n.z=" + nvec.z);//DEBUG
75 Vector3d rvec = new Vector3d(nvec); // 反射ベクトル
76 rvec.sub(nvec, onegate); // 2(O・N)N - O // Phong の式
77 rvec.normalize(); // 正規化
78 rvec.scale(VSCALE); // 移動量でスカラー倍
79 System.out.println("r.x=" + rvec.x + ", r.y=" + rvec.y + ", r.z=" + rvec.z);//DEBUG
80 rt3d.set(rvec); // 移動ベクトルをセットする
81
82 otrans.getTransform(ot3d); // もう一度現在のTransform取得
83 ot3d.mul(rt3d); // 移動を乗算
84 otrans.setTransform(ot3d); // 移動させる
85 }
86 }
87
88 wakeupOn(move);
89 }
90
91 public Node cloneNodeComponent(boolean forceDuplication) {
92 WallCollisionBehavior wallCollisionBehavior =
93 new WallCollisionBehavior(otrans, onode, ovector, hint);
94 wallCollisionBehavior.duplicateNode(this, forceDuplication);
95 return wallCollisionBehavior;
96 }
97
98 public void duplicateNode(Node node, boolean forceDuplication) {
99 super.duplicateNode(node, forceDuplication);
100 }
101
102 public void updateNodeReference(NodeReferenceTable table) {
103 super.updateNodeReferences(table);
104
105 Node newNode =
106 (Node)table.getNewObjectReference(onode);
107 onode = newNode;
108
109 TransformGroup newTransformGroup =
110 (TransformGroup)table.getNewObjectReference(otrans);
111 otrans = newTransformGroup;
112 }
113
114 }
サンプル・アプレットのソースは次の通りです。
CollisionTest.java (一部)
46 private BranchGroup createSceneGraph() {
47 BranchGroup root = new BranchGroup();
48
49 Bounds bounds = new BoundingSphere(new Point3d(), 100.0);
50
51 // 視点側の TG を取得
52 TransformGroup vtrans = universe.getViewingPlatform().getViewPlatformTransform();
53
54 // 視点の前に置く物体の root
55 PlatformGeometry pg = new PlatformGeometry();
56
57 // キーボードによる視点移動 Behavior
58 SimpleKeyBehavior kb = new SimpleKeyBehavior(vtrans);
59 kb.setSchedulingBounds(bounds);
60 pg.addChild(kb);
61
62 // 環境光源
63 AmbientLight alight = new AmbientLight();
64 alight.setInfluencingBounds(bounds);
65 root.addChild(alight); // どこにaddChild()しても良い
66
67 // 視点に固定する平行光源
68 DirectionalLight light =
69 new DirectionalLight( new Color3f(1.0f, 1.0f, 1.0f),
70 new Vector3f(0.57f, -0.57f, -0.57f) );
71 light.setInfluencingBounds(bounds);
72 pg.addChild(light);
73
74 // フロントバンパー
75 Transform3D ft3d = new Transform3D();
76 ft3d.setTranslation(new Vector3d(0.0, -0.13, -0.5));
77 TransformGroup ftrans = new TransformGroup(ft3d);
78 pg.addChild(ftrans);
79
80 ColorCube fcube = new ColorCube(0.04);
81 fcube.setCapability(ColorCube.ALLOW_BOUNDS_READ);//TEST
82 ftrans.addChild(fcube);
83
84 // 最小限の BoundingBox
85 BoundingBox box = new BoundingBox( new Point3d(-0.04, -0.04, -0.04),
86 new Point3d( 0.04, 0.04, 0.04) );
87
88 WallCollisionBehavior fcollision =
89 new WallCollisionBehavior(vtrans, fcube, new Vector3d(0.0, 0.0, -1.0)); // 前向き
90 fcollision.setSchedulingBounds(box);
91 root.addChild(fcollision);
92
93 // リヤバンパー
94 Transform3D rt3d = new Transform3D();
95 rt3d.setTranslation(new Vector3d(0.0, -0.13, 0.5));
96 TransformGroup rtrans = new TransformGroup(rt3d);
97 pg.addChild(rtrans);
98
99 ColorCube rcube = new ColorCube(0.04);
100 rcube.setCapability(ColorCube.ALLOW_BOUNDS_READ);//TEST
101 rtrans.addChild(rcube);
102
103 WallCollisionBehavior rcollision =
104 new WallCollisionBehavior(vtrans, rcube, new Vector3d(0.0, 0.0, 1.0)); // 後ろ向き
105 rcollision.setSchedulingBounds(box);
106 root.addChild(rcollision);
107
108 universe.getViewingPlatform().setPlatformGeometry(pg);
109
110 // 衝突相手となる壁
111 root.addChild(createText3D( "Front", 0.0, -0.5, -3.0, 0.0,
112 0.0f, 1.0f, 0.0f )); // green
113
114 root.addChild(createText3D( "Back", 0.0, -0.5, 3.0, Math.PI,
115 0.0f, 0.0f, 1.0f )); // blue
116
117 root.addChild(createText3D( "Left", -3.0, -0.5, 0.0, (Math.PI / 2.0),
118 1.0f, 1.0f, 0.0f )); // yellow
119
120 root.addChild(createText3D( "Right", 3.0, -0.5, 0.0, -(Math.PI / 2.0),
121 0.0f, 1.0f, 1.0f )); // cyan
122
123 // チェッカー模様の床 (衝突判定の対象外)
124 root.addChild(createFloor());
125
126 return root;
127 }
"壁"となる Text3D の生成はつぎのように行っています。
129 private TransformGroup createText3D( String text,
130 double x, double y, double z, double angle,
131 float r, float g, float b)
132 {
133 Transform3D t3d = new Transform3D();
134 t3d.setTranslation(new Vector3d(x, y, z));
135 Transform3D rt3d = new Transform3D();
136 rt3d.rotY(angle);
137 t3d.mul(rt3d);
138 TransformGroup trans = new TransformGroup(t3d);
139 trans.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
140
141 Text3D text3d =
142 new Text3D( new Font3D(new Font("dialog", Font.PLAIN, 1), new FontExtrusion() ),
143 text, new Point3f(), Text3D.ALIGN_CENTER, Text3D.PATH_RIGHT );
144
145 Material mat = new Material();
146 mat.setDiffuseColor(new Color3f(r, g, b));
147 mat.setShininess(128);
148 Appearance app = new Appearance();
149 app.setMaterial(mat);
150
151 Shape3D shape = new Shape3D(text3d, app);
152 shape.setCapability(Shape3D.ALLOW_BOUNDS_READ);//TEST
153 shape.setUserData(trans); // ユーザーデータに親のTGをセットしておく
154 trans.addChild(shape);
155
156 return trans;
157 }
WallCollisionBehavior で衝突相手の TransformGroup を取得できるように、setUserData() で"親"の TransformGroup をセットしています。
このサンプルでは IndexedQuadArray を使ってチェッカー模様の"床"を生成していますが、距離が近いためそのままでは常に床との衝突が判定されてしまいます。
衝突検知の対象から床を除外するため、Node#setCollidable() で床との衝突を検知させないようにしています。
159 // チェッカー模様の床を作る
160 private BranchGroup createFloor() {
161 BranchGroup bg = new BranchGroup();
162
163 int roop = 40;
164
165 Point3f[] vertices = new Point3f[roop * roop];
166
167 float start = -20.0f;
168
169 float x = start;
170 float z = start;
171
172 float step = 1.0f;
173
174 int[] indices = new int[(roop - 1)*(roop - 1) * 4];
175 int n = 0;
176
177 Color3f white = new Color3f(1.0f, 1.0f, 1.0f);
178 Color3f black = new Color3f(0.0f, 0.0f, 0.0f);
179 Color3f[] colors = { white, black };
180
181 int[] colorindices = new int[indices.length];
182
183 for (int i=0; i<roop; i++) {
184 for (int j=0; j<roop; j++) {
185 vertices[i*roop + j] = new Point3f(x, -1.0f, z);
186 z += step;
187 if (i<(roop - 1) && j<(roop - 1)) {
188 int cindex = (i % 2 + j) % 2;
189 colorindices[n] = cindex; indices[n++] = i * roop + j;
190 colorindices[n] = cindex; indices[n++] = i * roop + (j + 1);
191 colorindices[n] = cindex; indices[n++] = (i + 1) * roop + (j + 1);
192 colorindices[n] = cindex; indices[n++] = (i + 1) * roop + j;
193 }
194 }
195 z = start;
196 x += step;
197 }
198
199 IndexedQuadArray geom = new IndexedQuadArray( vertices.length,
200 GeometryArray.COORDINATES |
201 GeometryArray.COLOR_3,
202 indices.length );
203 geom.setCoordinates(0, vertices);
204 geom.setCoordinateIndices(0, indices);
205 geom.setColors(0, colors);
206 geom.setColorIndices(0, colorindices);
207
208 Shape3D floor = new Shape3D(geom);
209 floor.setCollidable(false); // 衝突を検知させないようにする
210 bg.addChild(floor);
211
212 bg.compile();
213
214 return bg;
215 }
このサンプルでは USE_BOUNDS で衝突検知を行っています。こ
の場合あまり正確な衝突判定はできません。
USE_GEOMETRY を使うと正確な衝突判定ができますが、
速い移動のときに連続して衝突検知することができない場合がありました。
迷路を使ったアクションゲームのような衝突判定は、 このサンプルの方法では実現できません。 迷路を使ったアクションゲームでは、 迷路の壁の平面で空間を細かく分割し、 その各空間ごとに衝突判定を行っています。
ENDO Yasuyuki