Behavior, Interpolator
Java 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.java1 // 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.picking
com.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.java1 // 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.java1 // 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.java1 // 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.java1 // 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.java1 // 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.java1 // 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.java1 // 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.java1 // 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