japan.internet.comThe Internet & IT Network
RSS
  • ニュース
  • コラム
  • リサーチ
  • ヘッドライン
  • 特集
  • ブログ
  • プレスリリース
  • 専門チャンネル
  • イベント
  • ランキング
  • ニュースメール
2008年10月7日
文字サイズ文字サイズ小文字サイズ中文字サイズ大
デベロッパー コラム2008年4月11日 10:00
developer.com
developer.com japan.internet.com 編集部メールホームrss
米国 Jupitermedia が運営する、ソフトウェア開発の専門サイト

Java 3Dの変換処理を理解する

海外海外internet.com発の記事

はじめに

概要

シリーズの構成

 このレッスンは、Java 3D APIに関するシリーズ記事の一部です。本シリーズではJava 3Dの基本から、非常に複雑なプログラミングまでを取り上げ、以前のレッスン「Understanding Lighting in the Java 3D API」(稿末の「参考資料」を参照)で説明したような複雑なプログラミングについても解説します。

 第1回「Back to Basics in the Java 3D API」、第2回「Understanding Transforms in Java 2D」に続き、今回のレッスンのタイトルは「Java 3Dの変換処理を理解する」です。

 今後のレッスンでは、Java 3Dでのユーザーとオブジェクトのやり取り、高度なアニメーション、およびテクスチャを扱います。

レッスンの目的

 このレッスンでは、Java 3Dの変換処理について学びます。さらに、ここで学んだ知識を利用して、Java 3Dコードを作成します。

Java 3Dプログラムのコンパイルと実行

 Java 3D APIを使用するプログラムをコンパイルして実行するためには、Java 3D APIソフトウェアをダウンロードしてインストールする必要があります。本稿の執筆時点では、バージョン1.5.0をダウンロードできます。

 これに加えて、Microsoft DirectXまたはOpenGLダウンロードしてインストールする必要があります。このチュートリアルシリーズに掲載されているサンプルプログラムはすべて、Microsoft DirectXを使用して開発およびテストされています。OpenGLによるテストは行われていません。

本稿を読む際のヒント

 本稿を読む際は、このページをもう1つ別のブラウザウィンドウで開いておき、以下のリンクを使用して図やプログラムリストを随時参照することをお勧めします。

  • 図1 Java 3Dの可視オブジェクト
  • 図2 スキンで覆われた状態の3Dレンダリングされた球体
  • 図3 スキンがない状態の3Dレンダリングされた球体
  • 図4 Aliceアイススケータの2種類のレンダリング
  • 図5 3つの基本的なJava 2D変換を表す行列演算
  • 図6 5つの3D変換を表す行列演算
  • 図7 Java3D010プログラムのBranchGroup階層
  • 図8 Java3D010の画面出力とユーザー入力GUIの例
  • 図9 平行移動、次に回転
  • 図10 図9のパラメータを使用する変換行列
  • 図11 回転、次に平行移動
  • 図12 平行移動、回転、平行移動、および拡大縮小

プログラムリスト

補足資料

 筆者が作成したさまざまなオンラインJavaチュートリアルの他のレッスンも学習することをお勧めします。全体の索引についてはhttp://www.dickbaldwin.com/toc.htmを参照してください。

全般的な背景情報

このレッスンで扱う変換の種類

 このレッスンでは、次の種類の変換を扱います。

  • 拡大縮小
  • 平行移動
  • 回転

必要な背景知識

 2Dまたは3Dでの変換(特に回転による変換)を理解するには、三角法をある程度理解している必要があります。大変だとは思いますが、仕方ありません。これに加えて、行列と行列代数の知識があると役立つでしょう。

 変換について理解できるようになるべく詳しく説明するつもりですが、必要な背景知識についてまで説明することは、このチュートリアルの範囲を超えています。必要な背景知識については、簡単な説明のみを行います。技術的な背景については、前回のレッスンの「Understanding Transforms in Java 2D」や、「Trigonometry」、「Planar transformations」、「Spatial transformations」、および「A little algebra」という記事を参照することをお勧めします(稿末の「参考資料」を参照)。

Java 3Dの可視オブジェクト

 まず、Java 3Dを使用して作成した球体を図1に示します。図1の黄色と白色の球体のレンダリング品質は意図的に低く抑えられているため、ファセット(小面)が目立ちます。

図1 Java 3Dの可視オブジェクト
図1 Java 3Dの可視オブジェクト

メッシュを用いた作成

 3Dの可視オブジェクトは、通常、ポリゴン(普通は三角形)を相互接続したメッシュを構成し、そのメッシュをスキンで覆うことによって作成されます。図2に、3Dでレンダリングした球体をスキンで完全に覆った例を示します。

図2 スキンで覆われた状態の3Dレンダリングされた球体
図2 スキンで覆われた状態の3Dレンダリングされた球体

メッシュの表示

 図3は3Dでレンダリングした同じ球体ですが、スキンが削除された状態です(図2と図3はAliceプログラム開発環境を使用して作成。稿末の「参考資料」を参照)。

図3 スキンがない状態の3Dレンダリングされた球体
図3 スキンがない状態の3Dレンダリングされた球体

 図3は、前述の、相互に接続された三角形のメッシュを示しています。

ワイヤーフレーム描画

 図3に示すようなメッシュのレンダリングは、「ワイヤーフレーム」描画ともいいます(図3は、Aliceのワイヤーフレームレンダリングです。ほとんどのワイヤーフレーム描画には、図3のように光源を使用して作成される3Dレンダリングはまず含まれません。多くの場合、ワイヤーフレーム描画は、単純に白色の背景に黒色の線で表示されます)。

ビジュアル属性

 オブジェクトのさまざまなビジュアル属性(シェーディング、色など)は、個々の三角形を覆うスキン部分に適用される色、明暗度、およびその他の表示要素によって実現されます。たとえば、図2の場合、右上の三角形を覆うスキンは非常に明るいのに対して、左下の三角形を覆うスキンは非常に暗くなっています。この効果を使用することで、3Dレンダリングと呼ばれる視覚的な錯覚が作り出されます。

品質の制御

 多くの場合、メッシュを構成する三角形の数は制御可能です。オブジェクトの3Dレンダリングの品質は、より多くの三角形から成るメッシュを構成することによって、ある程度まで向上できます。

 たとえば、図1の黄色と白色の球体は、2つの球体の表面のファセットが認識できるように、意図的にそのレンダリング品質が低く抑えられています。この球体のワイヤーフレーム描画を確認すれば、球体を構成する三角形の数が少ないことがわかります。これに対して図2および図3は、多くの三角形を使用して構成されており、その結果、レンダリング品質が向上しています。図2の球体は、図1の黄色と白色の球体よりも、本当の球体のように見えます。

 これらの概念は、球体より複雑なビジュアルオブジェクトにも適用されます。図4は、Aliceのアイススケータオブジェクトの「ソリッド」レンダリングと「ワイヤーフレーム」レンダリングを示しています(3Dプログラミングがどのようなものであるかをとりあえず把握するには、筆者のWebページ「Learn to Program using Alice」(稿末の「参考資料」を参照)にアクセスし、そこに掲載されているチュートリアルレッスンを行ってみることをお勧めします)。

図4 Aliceアイススケータの2種類のレンダリング
図4 Aliceアイススケータの2種類のレンダリング

 ご覧のとおり、図4のオブジェクトは、相互に接続された数百個の三角形で構成されています。

点とは

 点は、3つの座標値(x,y,z)で定義される3D空間内の位置です。xの値は、「原点」を基準としたときのx軸(図2および図3の赤色の線)に沿った点の位置を定義します。yの値は、y軸(図2および図3の緑色の線)に沿った点の位置を定義します。zの値は、z軸(図2および図3の青色の線)に沿った点の位置を定義します。x、y、zの各軸は、3D空間の「原点」で交差します。図2および図3の球体は、その原点を中心にして配置されています。

 図3および図4において、三角形が相互に接続する位置を頂点と呼びます。各頂点の位置は、点とその3つの座標値で指定されます。一般的に言って、3Dオブジェクトに適用される変換は、各頂点を指定する座標値の変更を伴います。

x軸、y軸、z軸

 座標軸は、グラフに表示されることもあれば、表示されないこともあります(図2および図3では、意図的に座標軸を表示しました)。しかし、座標軸が表示されていない場合であっても、座標軸は存在するものとみなされます。

 x軸は、通常、水平方向の軸で、左から右に向かって正の値を取ります。y軸は、通常、垂直方向の軸で、下から上に向かって正の値を取ります(幸いなことに、Java 2Dの場合とは異なり、Java 3Dのy軸は下から上が正の方向です)。z軸は、通常、背面から前面に向かって、つまり、画面を突き出てユーザーに向かうものと見なされます。Java 3Dでは、ユーザーに向かう方向がzの正の方向です。

原点の位置

 Java 3Dでは、原点はユニバース(空間)の中心にあります。ViewingPlatformを操作することによって画面上での原点の表示位置を変更することはできますが、原点は通常、このプログラムでユニバースを表示するために使用されるCanvasオブジェクトの中心にあります。

ベクトルとは

 Wikipedia(稿末の「参考資料」を参照)によると、ベクトルにはさまざまな種類があります。「空間ベクトル」は、大きさと方向で定義されるオブジェクトです。これに対して「スカラー」は、大きさのみを持つオブジェクトです。

 また、Wikipediaによると、ベクトルという用語は、「行ベクトル」または「列ベクトル」という、1次元の方向行列を表すのにも使用されます。このレッスンでは、ベクトルという用語を、両方の意味(「空間」および「行列」)で使用します。用語がどちらの意味で使用されているかは、説明のコンテキストから明らかです。

変換とは

 このレッスンで使用する「変換」という用語は、オブジェクトを定義するすべての点の座標値を、異なる座標値に変換する操作のことを指します。新しい座標値で定義されるオブジェクトは、異なるサイズであったり(拡大縮小)、異なる場所であったり(平行移動)、異なる回転であったり(回転)、いずれかの次元で押しつぶされたようになっていたり、いずれかの軸に対して反転する場合もありますが、多くの場合、元のオブジェクトを表現したものであると認識できます。

アフィン変換とは

 このレッスンで使用する変換は、実際にはアフィン変換です。Java 3Dのドキュメントには、次のように記載されています。

アフィン行列では、平行移動、回転、射影、異方性拡大縮小、およびシアーを行うことができます。直線は直線のままであり、平行線は平行のままですが、直線が交差する角度は変化できます。変換をアフィンとして分類するには、4番目の行が[0, 0, 0, 1]であることが必要です。

平行移動

 オブジェクトの平行移動とは、3D空間内のある位置から3D空間内の別の位置へオブジェクトを移動することです(その方法については、後述します)。オブジェクトの平行移動は、オブジェクトを定義するすべての点を平行移動することによって行われます。点を平行移動するには、点を定義するx、y、zの各座標値にオフセットを追加します。

 たとえば、元の点の座標がx1、y1、z1で、オフセットがtx、ty、tzの場合、点の新しい位置の座標は、次で表されます。

x2 = x1 + tx
y2 = y1 + ty
z2 = z1 + tz

拡大縮小

 オブジェクトの拡大縮小とは、オブジェクトを表すすべての点の位置を、非常に限定的な方法で変更することです。必ずというわけではありませんが、拡大縮小変換は一般にオブジェクトのサイズを変更するものであり、オブジェクトが大きくなったり、小さくなったりします(1または-1という係数による拡大縮小では、サイズが変わらないことに注意してください)。拡大縮小変換を行うには、オブジェクトを定義するすべての点のx、y、zの各座標値に、同じスケール係数または異なるスケール係数を乗算します。

 座標値を乗算するのに使用するスケール係数が同一の場合、オブジェクトは単純に大きくなるか、または小さくなります(スケール係数が負の場合は、軸に対して反転します)。各軸に適用されるスケール係数が異なる場合、オブジェクトはいずれかの次元で押しつぶされたようになったり、場合によっては軸に対して反転する場合もありますが、多くの場合、同じオブジェクトを表現したものであると認識できます。

 拡大縮小変換で使用されるスケール係数は、原点が基準です。たとえば、スケール係数が2.0の場合、新しい点の座標値は、元の点から原点までの距離の2倍になります。

 元の点の座標がx1、y1、z1で、スケール係数がsx、sy、szの場合、点の新しい位置の座標は、次で表されます。

x2 = x1 * sx
y2 = y1 * sy
z2 = z1 * sx

回転

 回転は、平行移動や拡大縮小ほど直感的ではなく、理解するにはある程度の三角法の知識が必要です。しかし、その仕組みを理解していなくても、決められた手順で回転変換を作成し、使用することは可能です。

 次の方程式は、2D空間の原点を中心とする点の回転を表しており、「Planar transformations」(稿末の「参考資料」を参照)に記載されています。vは、回転の角度を表します。

x2 = x1 * cos(v) - y1 * sin(v)
y2 = x1 * sin(v) + y1 * cos(v)

 この方程式は、「Spatial transformations」(稿末の「参考資料」を参照)で、3Dに拡張されます。3Dでは、物事が非常に複雑になります。2Dの場合は、xy平面でのみ回転が可能です。つまり、原点を中心に回転できます。しかし、3Dにおける回転は、xy、yz、zxのいずれかの平面に限定されません。いずれかの軸、または3つのすべての軸を中心に回転させることすら可能です。そのため、3組の回転方程式が存在します。この3組の方程式については、変換の行列方程式と合わせて後で示します。

正の回転角

 この式を導いた執筆者によると、軸を中心に反時計回りを正の回転角としています。これは、軸の正方向の端に位置して原点を見た場合、その軸を中心とする正の回転角が反時計回りであることを意味します。

行列

 以前のレッスン「Understanding Transforms in Java 2D」では(稿末の「参考資料」を参照)、行列を使用してJava 2Dの変換を実行する方法について説明しました。「同次座標」についても説明しました。

 記事「Planar transformations」(稿末の「参考資料」を参照)には、図5に示す行列方程式が記載されています。この行列方程式を解くことで、2Dの平行移動、拡大縮小、回転の各変換を実現できます。

図5 3つの基本的なJava 2D変換を表す行列演算
図5 3つの基本的なJava 2D変換を表す行列演算

Java 3Dの変換方程式

 記事「Spatial transformations」(稿末の「参考資料」を参照)では、明確に説明しているわけではありませんが、同じ執筆者によって、2Dの方程式を3Dの一連の方程式に拡張できるだけの十分な情報が与えられています。3Dバージョンの同次変換行列方程式を、図6に示します。

図6 5つの3D変換を表す行列演算
図6 5つの3D変換を表す行列演算

 図6のz軸を中心とする回転を表す3D行列方程式が、図5の2D回転方程式によく似ていることは、注目に値するでしょう。2Dは、xy平面のみを含むように制約を受けた3Dの1つの形に過ぎない、とも考えられます。そのため、2Dの原点を中心とする回転が、3Dのz軸を中心とする回転に似ているのです。

合成演算

 2Dに関する以前のレッスンで学習したように、同次の行列方程式を使用する利点の1つは、個々の変換を表す個々の行列をあらかじめ乗算して1つの合成行列を作成することにより、一連の変換を1つの行列乗算にまとめられることにあります。オブジェクトを表す各点で、一連の変換を個別に実行する必要はありません。合成変換行列をあらかじめ作成しておくことで、各点を表す列ベクトルと、すべての変換を表す合成行列を乗算して、各点を変換できます。計算に要するリソースの削減効果は計り知れません。

耳寄りな話

 変換行列の作成と使用を要素レベルに至るまで完全に理解していれば、Java 3Dプログラムの作成は可能です。しかし、幸いなことに、ほとんどのJava 3Dプログラムは、行列についてあまり深く考えなくても作成することができます。これは、Java 3D APIが、行列演算の大部分をごく一般的なJavaプログラミングインターフェイスの背後で抽象化している、便利なメソッドを提供しているためです。しかし、便利なメソッドを使用する場合であっても、背後で実際には何が行われているかについて、多少なりとも認識しておくとよいでしょう。

Java 3Dプログラムの全体構造

 Java 3Dシーンのプログラムの全体構造は、階層ツリーです。このツリーのルートはユニバースです。ルートから下に移動すると、次にLocaleクラスのオブジェクトがあります。Localeクラスについて、Sunは次のように述べています。

LocaleオブジェクトはVirtualUniverse内の高解像度の位置を定義するものであり、その位置で、BranchGroupをルートとするサブグラフ(ブランチグラフ)のコレクションのコンテナとしての役割を果たします...。
Localeオブジェクトは、その高解像度の座標を設定および取得するメソッドと、ブランチグラフを追加、削除、および列挙するメソッドを定義します。

 したがって、Localeオブジェクトはツリー内のノードであり、1つ以上のBranchGroupクラスの子を持つことができます。

BranchGroupクラス

 BranchGroupクラスについて、Sunは次のように述べています。

BranchGroupは、シーングラフブランチのルートへのポインタとしての役割を果たします。BranchGroupオブジェクトは、Localeのオブジェクトセットに挿入できる唯一のオブジェクトです。

 したがって、BranchGroupもツリー内のノードであり、1つ以上のNode型の子を持つことができます。Nodeクラスは、Java 3Dバージョン1.5.0の時点では、次の2つのサブクラスを持つ抽象クラスです。

  • Group
  • Leaf

 つまり、BranchGroupオブジェクトは、任意の数のGroup型またはLeaf型の子を持つことができます。

Leafクラス

 Leafクラスについて、Sunは次のように述べています。

Leafノードは、子を持たないすべてのシーングラフノードの抽象クラスです。Leafノードは、ライト、ジオメトリ、およびサウンドを指定します。また、シーングラフを共有するための特別なリンクおよびインスタンス化の機能を指定します。仮想世界におけるビューの配置と方向を決めるビュープラットフォームも提供します。

 このプログラムでは、ColorCubeオブジェクトという、Leafクラスのサブクラスを使用します。上記の説明から、ColorCubeオブジェクトをBranchGroupオブジェクトの子として追加できることがわかりますが、実際にその操作を行います。

Groupクラス

 Groupクラスについて、Sunは次のように述べています。

Groupノードオブジェクトは、汎用的なグループ化ノードです。Groupノードは、1つの親と任意の数の子から成り、不特定の順序で(または並列に)レンダリングされます。子がNULLであってもかまいませんが、NULLの子ノードでは処理は実行されません。Groupノードオブジェクトに対する処理には、Groupノードの子の追加、削除、および列挙が含まれます。Groupノードのサブクラスによって、別のセマンティクスが追加されます。

 Groupクラスのサブクラスは、次のとおりです。

  • BranchGroup
  • OrderedGroup
  • Primitive
  • SharedGroup
  • Switch
  • TransformGroup
  • ViewSpecificGroup

 これらのクラスの多くは、さらにいくつかのサブクラスを持ちます。したがって、非常に多くの種類のオブジェクトを、BranchGroupオブジェクトの子として追加することができます。しかし、このプログラムでは、BranchGroupオブジェクトの子として、LeafオブジェクトとTransformGroupオブジェクトのみを使用します。

TransformGroupクラス

 TransformGroupクラスについて、Sunは次のように述べています。

変換を含むGroupノードです。TransformGroupノードは、すべての子の配置、方向付け、および拡大縮小を行うことができるTransform3Dオブジェクトを通じて、単一の空間変換を指定します。
指定される変換は、アフィン変換です...。
シーングラフ内の変換の効果は累積的です。LocaleからLeafノードへの直接パス内の各TransformGroupを連結したものが、合成モデル変換(CMT)を定義します...。

「変換を含む」とは

 ドキュメントには、TransformGroupオブジェクトは「変換を含む」と記載されていますが、変換はオブジェクトの子ではないことに注意してください。変換は、Transform3D型のオブジェクトへの参照をパラメータとして、オブジェクトでsetTransformメソッドを呼び出すことによって確立されるオブジェクトのプロパティです。したがって、TransformGroupオブジェクトに関連する変換は1つだけですが、オブジェクトはNode型の子をいくつも持つことができます。

「効果は累積的」とは

 効果が累積的であるということは、TransformGroupオブジェクトに含まれる変換の効果が、階層内のTransformGroupオブジェクトの子、孫、ひ孫など、すべてのオブジェクトに適用されることを意味します。

BranchGroup階層

 図7は、このレッスンで紹介するプログラムのBranchGroup階層を示しています。

図7 Java3D010プログラムのBranchGroup階層
図7 Java3D010プログラムのBranchGroup階層

画面出力およびユーザー入力GUIの例

 図8は、このプログラムによって生成されるユーザー入力GUIおよび画面出力の例を示しています。

図8 Java3D010の画面出力とユーザー入力GUIの例
図8 Java3D010の画面出力とユーザー入力GUIの例

ユーザー入力GUI

 図8の右側のユーザー入力GUIでは、プログラムで実行される次の4種類の変換の動作を制御するパラメータを入力できます。

  • 平行移動
  • 回転
  • 平行移動
  • 拡大縮小

Replotボタン

 入力GUIの下部にある[Replot]ボタンをクリックすると、ユーザー入力パラメータと、図7のTransformGroup階層を使用して、図8の左側に示すように、新しい3Dユニバースが作成され、表示されます。

8個のLeafオブジェクト

 階層には8個のLeafオブジェクトがあり、これらはすべてColorCubeクラスのオブジェクトです。この8個のオブジェクトは、図7の階層内で斜体で示されています。これらは、図8のユニバース内でも明確に表示されています。

 図8のユニバースには、細長いColorCubeオブジェクトで構成される、2組の3D軸(1組に付き3オブジェクト)が表示されます。1組の3D軸は、図8のユニバースイメージの右上の部分にあります。もう1組は、前面が青色、左面が赤色、上面が紫色の大きなColorCubeオブジェクトの内側から突き出ています。

 残りのColorCubeオブジェクトは、赤色の面を見せて、3D空間の原点を中心にして配置されています。

太字で示されている変換

 図7の中で、括弧で囲まれた太字は、プログラムによって実行される変換を示しています。赤色の太字で示されている4つの変換は、図8の入力GUIへのパラメータ入力を通じてユーザーが制御できる変換を表します(これらは、図8のGUIの左端の列に示されている変換に相当します)。

 図7で黒色の太字で示されている変換は、ユーザーの制御が及ばないハードコードパラメータを使用します。たとえば、図7の先頭と末尾の近くにある2つの黒色の拡大縮小(scale)変換は、ColorCubeオブジェクトの寸法を拡大縮小して、図8に示されている3D軸として使用する細長いオブジェクトを作成するのに使用されます。同様に、図7の中央近くにある黒色の回転(rotate)変換は、ColorCubeオブジェクトが画面上に最初に表示されるときに、その青色の面がユーザーに向くように(z軸に対して垂直になるように)、オブジェクトを回転させるために使用されます。

redCubeオブジェクト

 図7の先頭近くにあるredCubeという名前のColorCubeオブジェクトは、階層内のすべての変換の上に位置しています。そのため、どの変換の影響も受けずに、図8のユニバースの中心に本来の状態で表示されます。その本来の状態では、赤色の面がz軸に対して垂直であり、原点に配置されます。

赤色の平行移動変換および回転変換

 図7の上部近くにある赤色の平行移動(translate)変換および回転(rotate)変換は、図8の入力GUIの左端の列に示されている上の2つの変換に対応します。この2つの変換は、階層のLeafにあたる残りの7個のColorCubeオブジェクトに影響します。この7個のLeafオブジェクトはすべて、rotatedGroupという名前のグループの子孫(子、孫、ひ孫など)であるTransformGroupオブジェクトに属するためです。

一番上の黒色の拡大縮小変換

 しかし、図7でplainAxisGroupを使用する黒色の拡大縮小(scale)変換は、plainAxisGroupの子である3つのColorCubeオブジェクトにのみ影響します。この変換はそのグループのプロパティであり、他のColorCubeオブジェクトはそのグループの子ではありません。

赤色の平行移動変換および拡大縮小変換

 図7の中央近くにある赤色の平行移動(translate)変換および拡大縮小(scale)変換は、図8の入力GUIの左端の列に示されている下の2つの変換に対応します。この2つの変換は、残りの4つのColorCubeオブジェクトに影響します。これらのオブジェクトがすべて、scaledGroupという名前のグループの子孫にあたるためです。階層の上にある4つのColorCubeオブジェクトには影響しません。

この階層関係を念頭に置く

 プログラムを実行し、図7に赤色の太字で示されている4つの変換の動作を制御する入力パラメータを操作する際には、この階層の関係を念頭に置く必要があります。

十分な背景知識の次は、いよいよコードへ

 背景となる知識はこの辺にして、いよいよプログラムを操作してみましょう。操作は簡単です。Java 3Dでの変換を作成し、使用する方法についても簡単に理解できることを願っています。

プレビュー

 このレッスンでは、Java3D010という名前のJava 3Dプログラムを紹介します。このプログラムは、以前のレッスン「Understanding transforms in Java 2D」(参考資料」を参照)に出てきたJava2D001という名前のJava 2Dプログラムによく似ています。

目的

 このプログラムの目的は、次の4つの変換を順番に簡単に実行することです。

  • 平行移動
  • 回転
  • 平行移動
  • 拡大縮小

操作

 プログラムが作成するユーザー入力GUIを使用すると、上記の一連の変換(図8を参照)で使用されるパラメータを変化させることができます。ユニバースには8個のColorCubeオブジェクトがあります。

 その中の2つのColorCubeオブジェクトは、赤色の四角形、および赤色と青色の立方体として図8に示されています。残りの6個のColorCubeオブジェクトは、それぞれが3つから成る2組に分けられ、細長いColorCubeオブジェクトに調整されて、2組の可視3D軸をシミュレートします。

 ColorCubeオブジェクトは、BranchGroup階層の異なるレベルでTransformGroupオブジェクトの子として挿入されます(図7を参照)。階層のレベルがそれぞれ異なるため、階層内の位置に応じて、異なるオブジェクトが異なる変換の対象になります。青色の面を見せている立方体と関連するその可視軸だけが、上のリスト内の4つのすべての変換の対象です。

 [Replot]ボタンをクリックすると、入力パラメータの変更、変換の再計算、および新しい出力の生成を行うことができます。

 入力フィールドの1つに、数値型に変換できないStringデータが含まれている状態で[Replot]ボタンをクリックすると、NumberFormatExceptionが発生してプログラムが異常終了します。たとえば、空白のフィールドがこの状態に該当します。

 プログラムのテストは、Windows XPの環境でJava SE 6およびJava 3D 1.5.0を使用して行いました。

操作結果

平行移動、次に回転

 このプログラムの主な目的の1つは、Java 3Dの変換を操作するツールを提供することです。いくつかの操作を通して、その目的に向かって進んでいきましょう。最初に、回転と平行移動の順番を変えるとどうなるかを確認します。まず平行移動、次に回転の順です。

 図7から明らかなように、ユニバースには8個のColorCubeオブジェクトが含まれています。図9の左上のイメージは、右側のGUIの一番上の行にある入力パラメータ値で表される距離と方向で、7個のオブジェクトを平行移動した結果です。この時点で、これらのオブジェクトに回転は適用されていません。

図9 平行移動、次に回転
図9 平行移動、次に回転

影響を受けるオブジェクト

 図7の階層で確認すると、この平行移動変換が、redCubeオブジェクト以外のすべてのオブジェクトに影響することがわかります。つまり、redCubeオブジェクトは原点を中心とした位置のまま変わりません。一方、他のすべてのオブジェクトは、右、上、 およびユーザーに向かって前方に平行移動されます。

回転変換の追加

 図9の左下のイメージは、右上のGUIで指定される距離と方向で平行移動された7個のオブジェクトに対して、右下のGUIの2行目のパラメータで指定される角度と軸の回転が適用された結果です。

 この回転が行われる前は、7個の各オブジェクトに属する軸は、ユニバースに属する対応する軸に平行でした。しかし、いったんこの回転変換が実行されると、オブジェクトに属する軸は、ユニバースに属する軸に並行でなくなり、大きな立方体から突き出ている短い突起に整列されます(これらの各突起自体が、細長いColorCubeオブジェクトです)。

 青色の面からの突起は、大きな立方体に属するz軸の新しい正の方向を表します。紫色の面からの突起は、y軸の新しい正の方向を表します。他の2つの突起は、z軸の新しい正の方向と負の方向を表します。

図9のパラメータを使用する変換行列

 図10は、図9の2つのGUIに示されているパラメータを使用する変換行列です。図10の左側の行列は図9の右上のGUIに対応し、図10の右側の行列は図9の右下のGUIに対応します。

図10 図9のパラメータを使用する変換行列
図10 図9のパラメータを使用する変換行列

個々の変換行列

 図10に示した各行列は、個々の変換行列です。各グループの先頭の行列は、他の変換が実行されない場合に適用される既定の変換行列に相当します。グループ内の残りの4つの行列は、ユーザー入力GUIにリストされている変換の1つにそれぞれ対応します。

 図10の変換行列の表示の順序は、図9のGUIにリストされている変換の順序を反転させたものです。グループの最後の行列が、GUIの先頭にある最初の変換に対応します。既定の行列のすぐ下にある行列は、GUIの下部にある最後の変換に対応します。

合成変換行列

 7個のColorCubeオブジェクトに適用される実際の変換は、グループ内のいくつかの行列の積です。

 図10の行列の中で、注目すべき値を太字で示してあります。図10の左側の行列を上から順に見ていくと、最後の行列を除くすべての行列が、先頭にある既定の行列と同じであることがわかります。既定の行列は、「恒等行列」です。2つ以上の恒等行列の積は、やはり恒等行列になります。

 図10の左側にある行列グループの一番下の行列によってのみ、行列の積が恒等行列と異なるものになります。実際、最後の行列が恒等行列と異なる唯一の行列であるため、結果の行列は最後の行列と同じになります。

変換値

 最後の行列内で太字で示されている3つの値が、変換値tx、ty、およびtz(図6を参照)です。この3つの値は、図9の右上のGUIにおける最初の平行移動の3つのパラメータに一致します。

回転変換の追加

 図10の右側の行列グループには、恒等行列とは異なる2つの変換行列が含まれています。1つは、右下の変換行列です。これは、左下の変換行列と同じです。恒等行列とは異なるもう1つの行列は、右側の下から2番目の回転行列です。

 この回転行列は、実際には、図6に示されているx、y、zの各軸を中心とする回転を表す、3つの個別の変換行列の積に相当します。もし実際にやってみるならば、目的の角度のサイン値とコサイン値を求め、その値を図6の行列方程式を使用して3つの変換行列に入力し、それらを乗算すると、図10の値が得られます。

回転、次に平行移動

 次に、平行移動と回転の順番を逆にしてみましょう。図11は、図9と同じパラメータ値を使用して、順番を逆にした場合の、7個のオブジェクトの回転と平行移動の結果です。

図11 回転、次に平行移動
図11 回転、次に平行移動

2つの立方体が同じ位置にある

 図11の左上のイメージは、最初に平行移動を適用せずに回転のみを適用した結果です。このイメージに最初は違和感を覚えるかもしれませんが、よく考えてみれば、これは当然の状態です。図11の左上のイメージには、同じサイズの2つの立方体が表示されており、両方とも3D空間の原点が中心です(原点を中心にして6個の細長いColorCubeオブジェクトも存在します)。

 青色の面を持つ立方体は、3本の軸すべてについて回転されています。赤色の面を持つ立方体は、回転されていません。その結果、このイメージに示されているように、2つの立方体が交差します。青色の立方体から突き出た赤色の立方体の部分のみが、可視状態です(Java 3Dでは、2つのオブジェクトがまったく同じ空間を占有することを回避する手段はありません)。

7個の立方体の平行移動

 図11の左下のイメージは、回転に続いて、図9と同じ距離および同じ方向で平行移動を適用した場合の結果です。

 図11の左下のイメージと図9の左下のイメージを比べると、2つのイメージは大きく異なることがわかります。

 平行移動を適用すると、各立方体(赤色の立方体を除く)は、立方体に属する軸に沿って平行移動されます。平行移動が行われたときに、図9および図11内の軸はそれぞれ異なる方向を指していたため、2つの各ケースにおいて、立方体は3D空間内の異なる位置に移動したのです。

変換行列

 コマンド行画面に表示される変換行列の解釈については、学習者の課題とします。

平行移動、回転、平行移動、および拡大縮小

 図12は、次の操作の結果です。

  • 7個の立方体の平行移動
  • 7個の立方体の回転
  • 7個の立方体のうちの4つを再び平行移動
  • その4つの拡大縮小

 したがって、図12は、このレッスンのトピックである4種類すべての変換を示しています。

図12 平行移動、回転、平行移動、および拡大縮小
図12 平行移動、回転、平行移動、および拡大縮小

平行移動および回転

 図12の左上のイメージは、平行移動に続けて回転を実行した結果です。図7に示されていたように、このユニバースには8個のColorCubeオブジェクトがあります。1つは、z軸に対して垂直な(ユーザーの視点に一致)赤色の面を持つオブジェクトです。図7から明らかなように、このオブジェクトには平行移動は適用されません。このため、常に原点を中心とする赤色の四角形のように見えます。

 残りの7個のColorCubeオブジェクトは、それぞれが3つのオブジェクトから成る2つのグループ(2組の3D軸のシミュレートに使用)と、青色の面がz軸に対して垂直になるように最初は方向付けられている1つのオブジェクトに分けられます。

平行移動変換および回転変換は8個のオブジェクトのうち7個に影響する

 図7からわかるように、最初の平行移動と回転は、この7個すべてのColorCubeオブジェクトに同じように影響します。その結果、最初の平行移動と回転の後、7個のオブジェクトはすべて、3D空間の同じ位置に同じ方向で配置されます。

 この時点で、3D軸をシミュレートする6個のColorCubeオブジェクトは(ペアで)同じ空間を占有しているため、互いのペアを視覚的に区別することはできません。さらにこれらのオブジェクトは、青色の面を持つ立方体と同じ点を中心とするため、図12の上側のイメージに示されているように、立方体の4つの面から突き出ているように見えます。

7個のオブジェクトのうち4つを平行移動および拡大縮小

 図12の下のイメージは、x軸に沿って新たな平行移動を行ったうえで、x次元で拡大縮小する拡大縮小変換を行った結果です。

 図7からわかるように、この2つの変換は、4つのColorCubeオブジェクトにのみ影響します。影響を受けるのは、青色の面を持つオブジェクトと、1組の3D軸をシミュレートするのに使用される、1組の3オブジェクトです。この4つのオブジェクトへの変換は、まったく同じように行われます。つまり、x軸に沿って平行移動が行われ、x次元で拡大縮小が行われます。

3つのオブジェクトはそのまま残る

 3つのColorCubeオブジェクトから成る1組はこの2つの変換の影響を受けないため、この3つのオブジェクトは、図12の左下のイメージに示すように、現在のサイズと方向のまま、現在の位置にそのまま残ります(イメージの右上部分にある3つの細長いColorCubeオブジェクトが、これに該当します)。

4つのオブジェクトは平行移動される

 他の4つのオブジェクトは、それぞれのx軸方向に平行移動されます。4つのオブジェクトの方向はすべて同じであるため、すべてのオブジェクトが同じ方向に同じ距離だけ平行移動されます。したがって、これらはひとかたまりのまま、3D空間の同じ位置が引き続き中心となります。

重要

 Java 3Dでオブジェクトを平行移動または回転する場合、その平行移動または回転は、目的のオブジェクトに属する軸を基準に行われます。ユニバースに属する軸を基準にしては行われません。したがって、オブジェクトが回転されて、その軸がユニバースの軸に平行でなくなった状態で、続けてx方向に平行移動を行うと、オブジェクトはユニバースのx軸に対して平行には移動しません。

 このケースでは、4つのColorCubeオブジェクトは、下、左、そしてユーザーに向かってわずかに前方に移動します。x方向に最大の長さを持つ、平行移動後の細長いColorCubeオブジェクトが、平行移動されずにイメージの右上にそのまま残っている対応する細長いColorCubeオブジェクトと一直線上に並ぶことに注意してください。これは、この2つのオブジェクトに属するx軸が一致するためです。以前に同一の平行移動および回転が行われているため、これらは一致するのです。

説明およびサンプルコード

個別説明

 以降では、このプログラムを部分ごとに区切って説明します。完全なプログラムリストについては、リスト15を参照してください。

 プログラムの本体に取り掛かる前に、プログラムの本体を通じて呼び出されるいくつかのユーティリティメソッドについて個別に説明します。最初に説明するユーティリティメソッドはtranslateメソッドです。リスト1は、このメソッド全体を示しています。

translateメソッド

 これは、特定のノードとそのすべての子を平行移動するTransformGroupオブジェクトを作成する必要がある場合に、プログラム本体から呼び出されるメソッドです。

リスト1 translateメソッド
//Given an incoming node object and a vector object,
// this method will return a transform group designed
// to translate that node according to that vector.
TransformGroup translate(Node node,Vector3f vector){
      
    Transform3D transform3D = new Transform3D();
    transform3D.setTranslation(vector);
    TransformGroup transformGroup = 
                                     new TransformGroup();
    transformGroup.setTransform(transform3D);

    transformGroup.addChild(node);
    return transformGroup;
}//end translate

Node型のパラメータ

 このメソッドは、2つの入力パラメータを受け取ります。最初のパラメータは、Node型オブジェクトへの参照です。すでに学習したように、Nodeは、LeafとGroupのスーパークラスです。したがって、最初のパラメータは、ColorCubeなどの可視オブジェクト、または別のTransformGroupオブジェクトへの参照です。

Vector3f型のパラメータ

 2番目のパラメータは、Vector3f型オブジェクトへの参照です。このオブジェクトは、x、y、zの各軸に沿った目的の平行移動距離を表す、float型の3つの数値をカプセル化します。

メソッドの目的

 メソッドの目的は、ノードとそのすべての子(後でグループに追加される可能性がある他の子も含む)を、ベクトルにカプセル化された距離に応じて平行移動させるTransformGroupオブジェクトを作成し、返すことです。

新しいTransformGroupオブジェクト

 このメソッドは、最初に新しいTransform3Dオブジェクトを作成し、そのtranslationプロパティを、平行移動距離を含むベクトルオブジェクトに設定します。次に、新しいTransformGroupオブジェクトを作成し、そのtransformプロパティを、新しいTransform3Dオブジェクトに設定します。

2つの代替のプログラミング手法

 最初のパラメータを削除し、この時点で新しいTransformGroupメソッドを単に返すという方法もあります。このようにプログラミングすると、メソッドを呼び出したコードが、ノードを子としてTransformGroupに即時に追加します。

 しかし、私は、そのステップをメソッドに組み込むことを選択しました。入力ノードオブジェクトは子として新しいTransformGroupオブジェクトに追加され(addChildメソッドを呼び出すことにより)、ノードがすでに子として追加されたTransformGroupオブジェクトが返されます。

scaleメソッド

 これは、特定のノードとそのすべての子を拡大縮小するTransformGroupオブジェクトを作成する必要がある場合に、プログラム本体から呼び出されるメソッドです。

リスト2 scaleメソッド
//Given an incoming node object and a vector object,
// this method will return a transform group designed
// to scale that node according to that vector.
TransformGroup scale(Node node,Vector3d vector){
      
    Transform3D transform3D = new Transform3D();
    transform3D.setScale(vector);
    TransformGroup transformGroup = 
                                 new TransformGroup();
    transformGroup.setTransform(transform3D);

    transformGroup.addChild(node);
    return transformGroup;
}//end scale

 上記のtranslateメソッドの場合と同様、このメソッドもNodeパラメータとVector3fパラメータを受け取ります。この場合の目的は、特定のノードとそのすべての子に加えて、グループに子として追加される可能性がある他の子についても拡大縮小を行うTransformGroupオブジェクトを作成し、返すことです。

 このメソッドのコードは、translateメソッドのコードとほぼ同じです。ただし、translateメソッドが、新しいTransform3DオブジェクトでsetTranslationメソッドを呼び出してそのtransformプロパティを設定するのに対して、scaleメソッドは新しいTarnsform3DオブジェクトでsetScaleメソッドを呼び出してそのscaleプロパティを設定します。

tiltTheAxesメソッド

 このメソッドの目的は、特定のノードの子孫であるすべてのLeafオブジェクト(および、後でTransformGroupオブジェクトの子として追加される可能性がある他のノードの子孫であるすべてのLeafオブジェクト)を、x、y、zの各軸を中心に回転させるTransformGroupオブジェクトを作成し、返すことです。

 このメソッドは、1つの変数の名前が異なることを除けば、「Combining Rotation and Translation in Java 3d」(稿末の「参考資料」を参照)というレッスンで説明した、同じ名前のメソッドと同一です。そのため、このレッスンでその説明を繰り返すことはしません。

displayMatrixメソッド

 このメソッドは、Transform3Dオブジェクトへの参照である入力パラメータを受け取り、そのTransform3Dオブジェクトに含まれる4x4行列の内容を表示します。

リスト3 displayMatrixメソッド
void displayMatrix(Transform3D transform){
  //Retrieve the contents of the matrix into an array.
  // The first four elements of the array contain the
  // first row of the matrix, etc.
  double[] matrixData = new double[16];
  transform.get(matrixData);

  for(int outCnt = 0;outCnt < 16;outCnt += 4){
    for(int cnt = 0;cnt < 4;cnt++){
      System.out.printf(
                   "%6.2f ",matrixData[cnt + outCnt]);
    }//end inner loop
    System.out.println();
  }//end outer loop
}//end displayMatrix

 このメソッドのコードは非常に単純です。リスト3のコメントを読めば大体の内容はわかるでしょう。

getAxesGroupメソッド

 これは、3本の直交軸のように見えるセットから成るTransformGroupオブジェクトを作成し、返す便利なメソッドです(図12の左下のイメージの右上の部分を参照)。3本の個々の軸は、細長いColorCubeオブジェクトでそれぞれ作成されています。

リスト4 getAxesGroupメソッド
TransformGroup getAxesGroup(){
  ColorCube xAxis = new ColorCube();
  //Scale the ColorCube to make it long and skinny
  // in the x dimension.
  TransformGroup xGroup = ''scale''(
                      xAxis,
                      new Vector3d(0.25,0.01,0.01));

  ColorCube yAxis = new ColorCube();
  TransformGroup yGroup = ''scale''(
                      yAxis,
                      new Vector3d(0.01,0.25,0.01));

  ColorCube zAxis = new ColorCube();
  TransformGroup zGroup = ''scale''(
                      zAxis,
                      new Vector3d(0.01,0.01,0.25));

  TransformGroup axesGroup = new TransformGroup();
  axesGroup.addChild(xGroup);
  axesGroup.addChild(yGroup);
  axesGroup.addChild(zGroup);

  return axesGroup;
}//end getAxesGroup

 このメソッドのコードは単純であり、埋め込まれているコメント以上の説明は必要ないでしょう。

Java3D010クラスの開始

 前述のとおり、完全なプログラムリストはリスト15に示してあります。Sceneクラスのコンストラクタが始まるまでのコードはすべて、全体的に単純です。そのコードでは、GUIの作成や[Replot]ボタンの処理などを行っています。

Sceneクラスのコンストラクタの開始

 Sceneクラスは、ユニバースのインスタンス化が行われる内部クラスです。[Replot]ボタンの機能の1つは、古いSceneオブジェクトを廃棄し、新しいSceneオブジェクトを作成することです。新しいSceneオブジェクトを作成するときには、図8の右側のGUIにユーザーが入力した変換パラメータ値に従って、オブジェクトが作成されます。

 リスト5は、Sceneクラスのコンストラクタの開始を示しています。

リスト5 Sceneクラスのコンストラクタの開始
Scene(){//constructor

  //Create a temporary TransformGroup object.
  System.out.println("¥nDefault xform matrix.");
  TransformGroup tempXformGroup = 
                                 new TransformGroup();
  Transform3D tempXform = new Transform3D();
  tempXformGroup.getTransform(tempXform);
  displayMatrix(tempXform);

  //Construct the universe.
  createACanvas();
  createTheUniverse();

一時的なTransformGroupオブジェクト

 リスト5では、最初に、一時的なTransformGroupオブジェクトを作成しています。これは、新しいTransformGroupオブジェクトに含まれる既定の変換行列を表示することだけが目的です。既定の変換行列は、図10の左上に示されているように恒等行列です。

ビッグバン

 リスト5のコードは、次の2つのメソッドを呼び出してユニバースを作成します。

  • createACanvas
  • createTheUniverse

 この2つのメソッドのコードは、Java 3Dに関する以前のレッスンで説明したコードに似ているか、または同一であるため、このレッスンでその説明を繰り返すことはしません。

2つのColorCubeオブジェクトを作成および準備する

 リスト6では、図8に示されている2種類のColorCubeオブジェクトを作成します。そのうちの1つは、青色の面がz軸に対して垂直になるように、垂直軸を中心に回転されます。このオブジェクトは、blueCubeGroupという名前のTransformGroupに子として追加されます。

リスト6 2つのColorCubeオブジェクトを作成および準備する
//The following ColorCube displays its red face
// without being rotated.
ColorCube redCube = new ColorCube(0.125f);

//Create a cube and rotate it to cause it to display
// its blue face.
TransformGroup blueCubeGroup =
                  tiltTheAxes(new ColorCube(0.125f),
                  0.0d,//x-axis
                  3*Math.PI/2.0d,//y-axis
                  0.0d);//z-axis

blueCubeGroupに可視軸を追加する

 リスト7では、getAxesGroupメソッドを呼び出して、1組の可視軸(細長いColorCubeオブジェクトで構成)を取得します。これらはblueCubeGroupに子として追加され、図8に示されているように、青色の立方体から軸が突き出したような状態になります。

リスト7 blueCubeGroupに可視軸を追加する
TransformGroup blueCubeAxes = getAxesGroup();
blueCubeGroup.addChild(blueCubeAxes);

変換階層を構成する

 コース料理で言えば、前菜が終わり、いよいよメイン料理です。この後のコードでは、LeafオブジェクトとTransformGroupオブジェクトの階層を構成します。

 階層内の変換は、図8のGUIにユーザーが入力するパラメータ値を使用して構成されます。このコードは、Leafからルートに向かう階層を構成することに注意してください。これは、GUIにリストされている変換とは逆の順序です。

拡大縮小変換を作成および表示する

 リスト8では、blueCubeGroupに適用される拡大縮小変換を作成します。図7の階層によると、blueCubeGroupは青色の立方体(前面が青色の匿名ColorCube)とblueCubeAxesを含みます。

リスト8 拡大縮小変換を作成および表示する
TransformGroup scaledGroup = scale(
                            blueCubeGroup,
                            new Vector3d(sx,sy,sz));

//Display the scaling transform matrix.
System.out.println("Scaling xform matrix");
scaledGroup.getTransform(tempXform);
displayMatrix(tempXform);

 リスト8は次に、拡大縮小変換行列を表示します。

平行移動変換を作成および表示する

 リスト9では、平行移動変換を作成および表示します(これは、図8のGUIの下部近くにリストされている平行移動変換です)。この変換は、scaledGroupに適用されます。図7の階層によると、scaledGroupはblueCubeGroupを含み、さらに後者は青色の立方体とblueCubeAxesを含みます。この変換の影響を受けるのは、青色の立方体とblueCubeAxesだけです。

リスト9 平行移動変換を作成および表示する
TransformGroup secondTransGroup = translate(
                         scaledGroup,
                         new Vector3f(tx2,ty2,tz2));

//Display the secondTransGroup transform matrix.
System.out.println("secondTransGroup xform matrix");
secondTransGroup.getTransform(tempXform);
displayMatrix(tempXform);

 青色の立方体は、blueCubeGroupの対応する軸で示される方向に平行移動されます。blueCubeGroupの軸が回転している場合も同様です。

回転変換を作成および表示する

 リスト10では、secondTransGroupに適用される回転変換を作成します。図7の階層によると、secondTransGroupはscaledGroupを含み、さらに後者はblueCubeGroupを含みます。

リスト10 回転変換を作成および表示する
TransformGroup rotatedGroup = 
                       tiltTheAxes(secondTransGroup,
                       xAngle,//x-axis
                       yAngle,//y-axis
                       zAngle);//z-axis

//Display the rotation transform matrix.
System.out.println("Rotation xform matrix");
rotatedGroup.getTransform(tempXform);
displayMatrix(tempXform);

新しい3D軸にも回転を適用する

 リスト11に示されているように、plainAxesGroupという名前の新しい3Dシミュレート軸にも回転変換が適用されます。この時点でplainAxesGroupを追加する目的は、最後の変換を実行する直前に青色の立方体の位置と方向を示すことです(図8を参照)。青色の立方体、blueCubeAxes、plainAxesGroupのすべてが回転されます。立方体が平行移動によって原点から離れていても、回転は、ユニバースに属する軸ではなく、ローカル軸に対して行われます。

別の3D軸を作成および追加する

 リスト11では、上記の目的で、plainAxesGroupを作成し、それをrotatedGroupに追加します。

リスト11 別の3D軸を作成および追加する
//Create and add the plainAxesGroup to the
// rotatedGroup.
TransformGroup plainAxesGroup = getAxesGroup();
rotatedGroup.addChild(plainAxesGroup);

もう1つの平行移動変換を作成および表示する

 リスト12では、図8のGUIの一番上に示されている平行移動変換を作成および表示します。この平行移動は、rotatedGroupに適用されます。図7の階層によると、rotatedGroupはplainAxesGroupとsecondTransGroupを含み、さらに後者はscaledGroupを含みます。scaledGroupは、blueCubeGroupを含みます。したがって、plainAxesGroup、青色の立方体、blueCubeAxesのすべてがこの変換によって平行移動されます。

リスト12 もう1つの平行移動変換を作成および表示する
TransformGroup firstTransGroup = translate(
                         rotatedGroup,
                         new Vector3f(tx1,ty1,tz1));

//Display the firstTransGroup transform matrix.
System.out.println("firstTransGroup xform matrix");
firstTransGroup.getTransform(tempXform);
displayMatrix(tempXform);

階層の完了

 リスト13では、firstTransGroupをmainBranchGroupに追加することによって、階層を完了します。

リスト13 階層の完了
//Construct the main group.
mainBranchGroup.addChild(firstTransGroup);
//Add the redCube to mark the origin. None of the
// transforms are applied to the redCube.
mainBranchGroup.addChild(redCube);

 最後に、リスト13のコードでは、赤色の面を見せた状態のColorCubeオブジェクトをBranchGroupオブジェクトの子として追加します。この立方体は、どの変換の対象でもありません。したがって、ユニバースのレンダリング時にユニバースの原点を中心として配置され、原点の位置を示します。

コンストラクタとクラスの完了

 リスト14では、Sceneクラスのコンストラクタを完了します。SceneクラスとJava3D010クラスの終了も通知します。したがって、リスト14はプログラムの最後です。

リスト14 コンストラクタとクラスの完了
      //Populate the universe by adding the branch group
      // that contains the objects.
      simpleUniverse.addBranchGraph(mainBranchGroup);

      //Do the normal GUI stuff.
      setTitle("Copyright 2007, R.G.Baldwin");
      setBounds(0,0,235,235);
      setVisible(true);

      //This listener is used to terminate the program 
      // when the user clicks the X-button on the Frame.
      addWindowListener(
        new WindowAdapter(){
          public void windowClosing(WindowEvent e){
            System.exit(0);
          }//end windowClosing
        }//end new WindowAdapter
      );//end addWindowListener

    }//end constructor
    //--------------------------------------------------//

  }//end inner class Scene

}//end class Java3D010

プログラムの実行

 リスト15のコードをコンパイルおよび実行することをお勧めします。変更を加え、その変更の結果を確認するなどして、実際にコードを操作してみてください。

 このプログラムをコンパイルおよび実行するには、Java 3D APIに加えて、Microsoft DirectXまたはOpenGLをダウンロードし、インストールする必要があります。これらをダウンロードできるWebサイトのリンクについては、下記の「ダウンロード」の項を参照してください。

まとめ

 このレッスンでは、Java 3Dでの変換について理解し、その理解に基づいて、Java 3Dコードを作成する方法について学習しました。

次の課題

 今後のレッスンでは、対話型のJava 3Dプログラム、高度なアニメーション、およびサーフェスを扱います。

ダウンロード

参考資料

完全なプログラムリスト

 このレッスンで説明したプログラムの完全なリストを、以下のリスト15に示します。

リスト15 Java3D010プログラムのリスト
/*File Java3D010.java
Copyright 2007, R.G.Baldwin

This program is very similar to the Java2D program named
 Java2D001.
 
The purpose of this program is to make it easy to
experiment with the following four transforms executed
in sequence:

translate
rotate
another translate
scale

The program creates a user input GUI that can be used to 
vary the parameters used for the sequence of transforms

Two ColorCube objects and two sets of three long skinny 
ColorCube objects arranged to simulate a visible set of
3D axes are added as children at different levels of the 
branch group hierarchy. The different objects are 
subjected to different transforms depending on their 
position in the hierarchy.  Only the cube with the blue 
face showing and its associated visible axes are subjected
to all four transforms in the above list.

A Replot button allows the user to modify input 
parameters, recompute the transforms, and produce a new
output by clicking the button.  Clicking the Replot button
when one of the input fields contains String data that
can't be converted to a numeric type will cause the
program to abort with a NumberFormatException.  For 
example, a blank field falls in this category.

Tested using Java SE 6, and Java 3D 1.5.0 running under
Windows XP.
*********************************************************/
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.geometry.ColorCube;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.Node;
import javax.vecmath.Vector3f;
import javax.vecmath.Vector3d;
import java.awt.Frame;
import java.awt.Panel;
import java.awt.Label;
import java.awt.TextField;
import java.awt.Button;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

//This is the top-level driver class for this program.
public class Java3D010 extends Frame{
  Scene scene = new Scene();

  TextField sxTxt = new TextField("1.0");
  TextField syTxt = new TextField("1.0");
  TextField szTxt = new TextField("1.0");

  TextField tx2Txt = new TextField("0.0");
  TextField ty2Txt = new TextField("0.0");
  TextField tz2Txt = new TextField("0.0");

  TextField xAngleTxt = new TextField("0.0");
  TextField yAngleTxt = new TextField("0.0");
  TextField zAngleTxt = new TextField("0.0");

  TextField tx1Txt = new TextField("0.0");
  TextField ty1Txt = new TextField("0.0");
  TextField tz1Txt = new TextField("0.0");

  //These instance variables are accessed by the
  // constructor for a Scene object when it is
  // instantiated. They are defined in this enclosing
  // class rather than in the Scene class because they
  // need to be set before the constructor for the Scene
  // object is called.

  //Scaling parameters.
  double sx = 1.5; //scale X
  double sy = 1.0; //scale Y
  double sz = 1.0; //scale Z

  //Translation parameters for first translation.
  float tx1 = 0.7f; //translate X
  float ty1 = 0.0f; //translate Y
  float tz1 = 0.0f; //translate Z

  //Rotation parameters.
  double xAngle = 0.0;//rotate around X
  double yAngle = 0.0;//rotate around Y
  double zAngle = Math.PI/4.0d;//rotate around z

  //Translation parameters for second translation.
  float tx2 = 0.3f;//translate X again
  float ty2 = 0.0f;//translate Y again
  float tz2 = 0.0f;//translate Z again
  //----------------------------------------------------//

  public static void main(String[] args){
    Java3D010 thisObj = new Java3D010();
  }//end main
  //----------------------------------------------------//

  public Java3D010(){//top-level constructor
    setTitle("Copyright 2007, R.G.Baldwin");
    setBounds(236,0,235,235);

    //Construct the user input panel and add it to the
    // CENTER of the Frame. Arrange the fields from top to
    // bottom in the order that the transforms are
    // actually executed.
    Panel inputPanel = new Panel();
    inputPanel.setLayout(new GridLayout(0,4));

    inputPanel.add(new Label("Xform",Label.CENTER));
    inputPanel.add(new Label("X",Label.CENTER));
    inputPanel.add(new Label("Y",Label.CENTER));
    inputPanel.add(new Label("Z",Label.CENTER));

    //Input data for the first translation.
    inputPanel.add(new TextField("Trans"));
    inputPanel.add(tx1Txt);
    inputPanel.add(ty1Txt);
    inputPanel.add(tz1Txt);

    //Input data for the rotation.
    inputPanel.add(new TextField("Rot (deg)"));
    inputPanel.add(xAngleTxt);
    inputPanel.add(yAngleTxt);
    inputPanel.add(zAngleTxt);

    //Input data for the second translation.
    inputPanel.add(new TextField("Trans"));
    inputPanel.add(tx2Txt);
    inputPanel.add(ty2Txt);
    inputPanel.add(tz2Txt);

    //Input scaling data.
    inputPanel.add(new TextField("Scale"));
    inputPanel.add(sxTxt);
    inputPanel.add(syTxt);
    inputPanel.add(szTxt);

    add(inputPanel,BorderLayout.CENTER);


    //Add a button that allows the user to change the
    // input parameter values, recompute the transforms,
    // and replot the cube.
    Button button = new Button("Replot");
    button.addActionListener(
      new ActionListener(){
        public void actionPerformed(ActionEvent e){

          //Get user input values from the text fields,
          // convert them to numeric types, and store them
          // in instance variables that are accessible to
          // the constructor for the Scene object.  Note
          // that the parse methods will cause the program
          // to abort with a NumberFormatException if a
          // field contains a string that cannot be
          // converted to the appropriate numeric type
          // (including an empty string caused by a blank
          // field).

          //Data for the first translation.
          tx1 = Float.parseFloat(tx1Txt.getText());
          ty1 = Float.parseFloat(ty1Txt.getText());
          tz1 = Float.parseFloat(tz1Txt.getText());

          //Data for the rotation.  Convert the rotation
          // angles from degrees to radians.
          xAngle = (Double.parseDouble(
                    xAngleTxt.getText()) * Math.PI/180.0);
          yAngle = (Double.parseDouble(
                    yAngleTxt.getText()) * Math.PI/180.0);
          zAngle = (Double.parseDouble(
                    zAngleTxt.getText()) * Math.PI/180.0);

          //Data for the second translation.
          tx2 = Float.parseFloat(tx2Txt.getText());
          ty2 = Float.parseFloat(ty2Txt.getText());
          tz2 = Float.parseFloat(tz2Txt.getText());

          //Scaling data
          sx = Double.parseDouble(sxTxt.getText());
          sy = Double.parseDouble(syTxt.getText());
          sz = Double.parseDouble(szTxt.getText());

          //Instantiate a new Scene object.  Dispose of
          // the old object to conserve resources.
          // Otherwise the program aborts after clicking
          // the Replot button 32 times with a message
          // regarding a limit on the number of allowable
          // Canvas objects that can be rendered.
          scene.dispose();
          scene = new Scene();
        }//end actionPerformed
      }//end new ActionListener
    );//end addActionListener

    //Finish constructing the GUI.
    add(button,BorderLayout.SOUTH);

    setVisible(true);

    //This window listener is used to terminate the
    // program when the user clicks the X button.
    addWindowListener(
      new WindowAdapter(){
        public void windowClosing(WindowEvent e){
          System.exit(0);
        }//end windowClosing
      }//end new WindowAdapter
    );//end addWindowListener

  }//end constructor
  //----------------------------------------------------//

  //This is an inner class, from which the universe will
  // be instantiated.
  class Scene extends Frame{

    //Declare instance variables that are used later by
    // the program.
    Canvas3D canvas3D;
    SimpleUniverse simpleUniverse;
    BranchGroup mainBranchGroup = new BranchGroup();
    //--------------------------------------------------//

    Scene(){//constructor

      //Create a temporary TransformGroup object for the
      // sole purpose of displaying the default transform
      // matrix that is contained in a new TransformGroup
      // object.
      System.out.println("¥nDefault xform matrix.");
      TransformGroup tempXformGroup = 
                                     new TransformGroup();
      Transform3D tempXform = new Transform3D();
      tempXformGroup.getTransform(tempXform);
      displayMatrix(tempXform);

      //Construct the universe.
      createACanvas();
      createTheUniverse();

      //Create two different ColorCube objects.  Rotate
      // one of them around its vertical axes so that it
      // will display its blue face.  The names given to
      // the cubes are based on the color of the face that
      // is displayed on the front following this
      // rotation transform.

      //The following ColorCube displays its red face
      // without being rotated.
      ColorCube redCube = new ColorCube(0.125f);

      //Create a cube and rotate it to cause it to display
      // its blue face.
      TransformGroup blueCubeGroup =
                        tiltTheAxes(new ColorCube(0.125f),
                        0.0d,//x-axis
                        3*Math.PI/2.0d,//y-axis
                        0.0d);//z-axis

      //Add a set of axes to the blueCubeGroup .
      TransformGroup blueCubeAxes = getAxesGroup();
      blueCubeGroup.addChild(blueCubeAxes);

      //Now perform a series of transforms based on the
      // user input data.  Note that this code constructs
      // the branch group hierarchy from the leaves toward
      // the root.


      //Create a scaling transform, which will be applied
      // to the blueCubeGroup.  The blueCubeGroup includes
      // the blue cube and the blueCubeAxes.
      TransformGroup scaledGroup = scale(
                                  blueCubeGroup,
                                  new Vector3d(sx,sy,sz));

      //Display the scaling transform matrix.
      System.out.println("Scaling xform matrix");
      scaledGroup.getTransform(tempXform);
      displayMatrix(tempXform);


      //Create a translation transform, which will be
      // applied to the scaledGroup.  The scaledGroup
      // contains the blueCubeGroup, which in turn
      // contains the blue cube and the blueCubeAxes.
      // Only the blue cube and blueCubeAxes will be
      // affected by this transform.
      //Note that the blue cube is translated in a
      // direction indicated by the corresponding axes
      // belonging to the blueCubeGroup, even if the axes
      // belonging to the blueCubeGroup have been rotated.
      TransformGroup secondTransGroup = translate(
                               scaledGroup,
                               new Vector3f(tx2,ty2,tz2));

      //Display the secondTransGroup transform matrix.
      System.out.println("secondTransGroup xform matrix");
      secondTransGroup.getTransform(tempXform);
      displayMatrix(tempXform);


      //Create a rotation transform, which will be applied
      // to the secondTransGroup.  The secondTransGroup
      // contains the scaledGroup, which in turn contains
      // the blueCubeGroup. The transform
      // will also be applied to a new set of axes named
      // plainAxesGroup.
      //The purpose of adding plainAxesGroup at this point
      // is to show the location and orientation of the
      // blue cube immediately before the final
      // translation is executed.

      //The blue cube, blueCubeAxes, and plainAxesGroup
      // will all be rotated.  Rotation takes place around
      // the local axis (rather than the 3D-space axes),
      // even if the cube has been translated away from
      // the origin.
      TransformGroup rotatedGroup =
                             tiltTheAxes(secondTransGroup,
                             xAngle,//x-axis
                             yAngle,//y-axis
                             zAngle);//z-axis

      //Display the rotation transform matrix.
      System.out.println("Rotation xform matrix");
      rotatedGroup.getTransform(tempXform);
      displayMatrix(tempXform);

      //Create and add the plainAxesGroup to the
      // rotatedGroup.
      TransformGroup plainAxesGroup = getAxesGroup();
      rotatedGroup.addChild(plainAxesGroup);


      //Create the first translation transform, which will
      // be applied the rotatedGroup.  The rotatedGroup
      // contains the plainAxesGroup and the
      // secondTransGroup, which in turn contains the
      // scaledGroup. The scaledGroup contains the 
      // blueCubeGroup. Thus, the
      // plainAxesGroup, the blue cube, and blueCubeAxes
      // will all be translated by this transform.
      TransformGroup firstTransGroup = translate(
                               rotatedGroup,
                               new Vector3f(tx1,ty1,tz1));

      //Display the firstTransGroup transform matrix.
      System.out.println("firstTransGroup xform matrix");
      firstTransGroup.getTransform(tempXform);
      displayMatrix(tempXform);
      

      //Construct the main group.
      mainBranchGroup.addChild(firstTransGroup);
      //Add the redCube to mark the origin. None of the
      // transforms are applied to the redCube.
      mainBranchGroup.addChild(redCube);


      //Populate the universe by adding the branch group
      // that contains the objects.
      simpleUniverse.addBranchGraph(mainBranchGroup);

      //Do the normal GUI stuff.
      setTitle("Copyright 2007, R.G.Baldwin");
      setBounds(0,0,235,235);
      setVisible(true);

      //This listener is used to terminate the program 
      // when the user clicks the X-button on the Frame.
      addWindowListener(
        new WindowAdapter(){
          public void windowClosing(WindowEvent e){
            System.exit(0);
          }//end windowClosing
        }//end new WindowAdapter
      );//end addWindowListener

    }//end constructor
    //--------------------------------------------------//

    //Create a Canvas3D object to be used for rendering
    // the Java 3D universe.  Place it in the CENTER of
    // the Frame.
    void createACanvas(){
      canvas3D = new Canvas3D(
              SimpleUniverse.getPreferredConfiguration());
      add(BorderLayout.CENTER,canvas3D);
    }//end createACanvas
    //--------------------------------------------------//

    //Create an empty Java 3D universe and associate it 
    // with the Canvas3D object in the CENTER of the
    // frame.  Also specify the apparent location of the
    // viewer's eye.
    void createTheUniverse(){
      simpleUniverse = new SimpleUniverse(canvas3D);
      simpleUniverse.getViewingPlatform().
                             setNominalViewingTransform();
    }//end createTheUniverse
    //--------------------------------------------------//

    //Given an incoming node object and a vector object,
    // this method will return a transform group designed
    // to translate that node according to that vector.
    TransformGroup translate(Node node,Vector3f vector){

        Transform3D transform3D = new Transform3D();
        transform3D.setTranslation(vector);
        TransformGroup transformGroup = 
                                     new TransformGroup();
        transformGroup.setTransform(transform3D);

        transformGroup.addChild(node);
        return transformGroup;
    }//end translate
    //--------------------------------------------------//

    //Given an incoming node object and a vector object,
    // this method will return a transform group designed
    // to scale that node according to that vector.
    TransformGroup scale(Node node,Vector3d vector){

        Transform3D transform3D = new Transform3D();
        transform3D.setScale(vector);
        TransformGroup transformGroup = 
                                     new TransformGroup();
        transformGroup.setTransform(transform3D);

        transformGroup.addChild(node);
        return transformGroup;
    }//end scale
    //--------------------------------------------------//

    //The purpose of this method is to create and return
    // a transform group designed to perform a counter-
    // clockwise rotation about the x, y, and z axes 
    // belonging to an incoming node.  The three incoming
    // angle values must be specified in radians. Don't
    // confuse this with a RotationInterpolator.  This is
    // not an interpolation operation.  Rather, it is a
    // one-time transform.
    TransformGroup tiltTheAxes(Node node,
                               double xAngle,
                               double yAngle,
                               double zAngle){

      Transform3D tiltAxisXform = new Transform3D();
      Transform3D tempTiltAxisXform = new Transform3D();

      //Construct and then multiply two rotation transform
      // matrices.
      tiltAxisXform.rotX(xAngle);
      tempTiltAxisXform.rotY(yAngle);
      tiltAxisXform.mul(tempTiltAxisXform);

      //Construct the third rotation transform matrix and
      // multiply it by the result of previously 
      // multiplying the two earlier matrices.
      tempTiltAxisXform.rotZ(zAngle);
      tiltAxisXform.mul(tempTiltAxisXform);


      TransformGroup rotatedGroup = new TransformGroup(
                                           tiltAxisXform);
      rotatedGroup.addChild(node);

      return rotatedGroup;
    }//end tiltTheAxes
    //--------------------------------------------------//

    //This method displays the contents of the 4x4 matrix
    // contained in a Transform3D object.
    void displayMatrix(Transform3D transform){
      //Retrieve the contents of the matrix into an array.
      // The first four elements of the array contain the
      // first row of the matrix, etc.
      double[] matrixData = new double[16];
      transform.get(matrixData);

      for(int outCnt = 0;outCnt < 16;outCnt += 4){
        for(int cnt = 0;cnt < 4;cnt++){
          System.out.printf(
                       "%6.2f ",matrixData[cnt + outCnt]);
        }//end inner loop
        System.out.println();
      }//end outer loop
    }//end displayMatrix
    //--------------------------------------------------//

    //This method constructs and returns a TransformGroup
    // object containing a set of what look like three
    // orthogonal axes.  Each of the three individual axes
    // is constructed from a long skinny ColorCube
    // object.
    TransformGroup getAxesGroup(){
      ColorCube xAxis = new ColorCube();
      TransformGroup xGroup = scale(
                          xAxis,
                          new Vector3d(0.25,0.01,0.01));

      ColorCube yAxis = new ColorCube();
      TransformGroup yGroup = scale(
                          yAxis,
                          new Vector3d(0.01,0.25,0.01));

      ColorCube zAxis = new ColorCube();
      TransformGroup zGroup = scale(
                          zAxis,
                          new Vector3d(0.01,0.01,0.25));

      TransformGroup axesGroup = new TransformGroup();
      axesGroup.addChild(xGroup);
      axesGroup.addChild(yGroup);
      axesGroup.addChild(zGroup);

      return axesGroup;
    }//end getAxesGroup
    //--------------------------------------------------//

  }//end inner class Scene

}//end class Java3D010

著作権情報

 Copyright 2007, Richard G. Baldwin.

 Richard Baldwinにより書面で明確に許可された場合を除き、本稿の一部またはすべてを複製することは、手段や媒体にかかわらず一切禁じられています。

著者紹介

Richard Baldwin(Richard Baldwin)
大学教授(テキサス州オースティン、Austin Community College)、およびプライベートコンサルタント。Java、C#、およびXMLの組み合わせに特に注目している。JavaアプリケーションとC#アプリケーションの、プラットフォームや言語に依存しない多くの利点に加えて、Java、C#、およびXMLの組み合わせは、Web上で構造化情報を提供するうえでの主要な原動力になると信じている。
これまで多くのコンサルティングプロジェクトに関与し、テキサス州オースティン近郊のハイテク会社ではオンサイトトレーニングを多数提供。彼が作成した「Baldwin's Programming Tutorials」は、世界中の上級プログラマや向上心に燃えるプログラマの支持を受けている。JavaProマガジンにも記事が掲載されている。
プログラミングの専門家であることに加えて、DSP(Digital Signal Processing)に関しても実際に数年に及ぶ経験を有する。学士号取得後の初めての仕事は、Texas Instruments社のSeismic Research DepartmentでDSPを行うことであった(TIは現在でもDSPに関して世界最高水準を誇っている)。その後の数年間、プログラミングとDSPの専門知識を、ソナーや水中音響など、興味ある他の分野に応用した。
Southern Methodist大学のMSEEの学位を取得しており、コンピュータテクノロジを現実の世界の問題に適用することに関して、長年の経験を有する。
Baldwin@DickBaldwin.com
海外のインターネットコムアメリカ韓国ドイツトルコ
Copyright 2008 Jupitermedia Corporation All Rights Reserved.http://www.internet.com/