Java言語での制御・自動処理と直接的な3D描画

リニアングラフ3Dは、Ver.5.6以降において、Java※1言語での制御もサポートしています。 ここでは、実際にJava言語のコードから3Dグラフをプロットさせたり、3D図形を描画させたりする方法を解説します。
※ 現時点では、Java9 から採用されたモジュール機能の使用はサポートしていません。将来的なバージョンにおいて対応する予定です。

- 目次 -

開発の準備とコンパイル・実行

Java言語でリニアングラフ3Dを制御するには、別途Java言語の開発環境(JDK)が必要です。 JDKの入手やインストールについては、Java言語の解説書や解説サイトなどをご参照ください。 なお、ここでは解説の単純化のためIDEは使用せず、コンパイルと実行はコマンドラインで行います。

はじめに、環境が揃っている事の確認として、リニアングラフ3Dを起動するための簡単なサンプルを作成し、コンパイル・実行してみましょう。 「 Sample0.java 」という名前で、以下のコードを記述したテキストファイルを作成してください:

code/Sample0.java

続いて、リニアングラフ3Dの配布パッケージ内にある「 RinearnGraph3D.jar (JARファイル) 」を、 上のコードと同じ場所に置いてください。 そして、コマンドライン端末上でその場所に cd などで移動し、以下のように入力してコンパイルしてください (Microsoft Windows※2 をご使用の場合と Linux※3 等をご使用の場合で異なります):

javac -classpath ".;RinearnGraph3D.jar" Sample0.java      (Windowsの場合)
javac -classpath ".:RinearnGraph3D.jar" Sample0.java      (Linux等の場合)
(※ 上の二つは、 -classpath の後の部分での区切り文字が、前者が「 ; 」、後者が「 : 」になっています。)

エラー無くコンパイルできた事を確認した上で、そのまま以下のように入力して実行してください:

java -classpath ".;RinearnGraph3D.jar" Sample0      (Windowsの場合)
java -classpath ".:RinearnGraph3D.jar" Sample0      (Linux等の場合)

実行の結果、グラフ画面が起動すれば成功です。 以降のサンプルコードでは、上のコマンドのSample0の部分をSample1〜Sample6に置き換えて、同様にコンパイル・実行できます。 その際、ファイル名は必ず「 クラス名.java 」とするようにご注意ください。

この解説で使用するリニアングラフ3DのJava言語用APIライブラリは、以下のURLで仕様書を参照できます。 この解説では触れきれない細かい機能の一覧や詳細などは、そちらをご参照ください:

リニアングラフ3D API仕様書 ( Java言語用 )
https://www.rinearn.com/ja-jp/graph3d/api/

APIライブラリの中でも特に使用するRinearnGraph3Dクラスの仕様書は、以下で参照できます:

RinearnGraph3Dクラス API仕様書
https://www.rinearn.com/ja-jp/graph3d/api/com/rinearn/graph3d/RinearnGraph3D

以下では、いくつかの基本となる使い方について、サンプルコードを例示しながら解説していきます。 なお、以下で掲載するサンプルコードは、配布パッケージ内にも同梱されています。

ファイルのプロット

まずは、実用における最も単純な例として、CSVファイルに記載された座標値データをプロットしてみましょう。以下のサンプルデータファイルを使用します:

- SampleDataFile1.csv -

0.0,0.0,0.0
1.0,1.0,2.0
2.0,4.0,6.0
3.0,9.0,12.0
4.0,16.0,20.0
5.0,25.0,30.0
6.0,36.0,42.0
7.0,49.0,56.0
8.0,64.0,72.0
9.0,81.0,90.0
10.0,100.0,110.0

この通り、3カラム書式(左から x y z )で、0 <= x <= 10の範囲で適当に座標値が書き出されています。 このファイルを開いてグラフにプロットするプログラムは以下の通りです:

code/Sample1.java
実行結果

上のコードでは、まずRinearnGraph3Dクラスのインスタンスを生成し(この時点でグラフ画面が起動します)、そして openDataFile メソッドを呼び出して、開きたいファイルを引数に渡しています。 ファイルを開く過程での例外については、必要に応じて適切に処理してください。

配列データのプロット

ファイルを介さず、配列に格納された座標値データを、直接渡してプロットする事もできます:

code/Sample2.java
実行結果

このように、先ほどファイルを開いていた openDataFile メソッドの代わりに、 setData メソッドで配列データを渡します。

配列データは、ここでは単系列なので x[ 座標点インデックス ] の形ですが、 系列を分けたい場合は x[ 小系列インデックス ][ 座標点インデックス ]x[ 大系列インデックス ][ 小系列インデックス ][ 座標点インデックス ] の形の多次元配列にします(y, zも同様)。 線プロット時には、小系列の区切りで線が切れ、大系列の区切りで色が変わります。

なお、メッシュプロットや曲面プロットの場合は、 x[ メッシュ縦方向インデックス ][ メッシュ横方向インデックス ] のように、メッシュの縦横2方向のインデックスを持たせてください(y, zも同様)。 その際、どちらが縦か横かは気にしなくても構いません。具体例は「範囲やオプションなどの詳細設定」の項をご参照ください。

画像の出力

グラフを画像ファイルに出力するには、RinearnGraph3Dクラスの exportImageFile メソッドを使用します。 例えば、これまで登場したサンプルコードにおいて、mainメソッドの末尾に以下の一行を追加すれば、画像ファイル「graph.png」が出力されます:

code/ExportImage.java

画像形式は拡張子から自動で判断されますが、現時点ではJPEGとPNG形式にのみ対応しています。 2番目の引数は画質で、1.0で最高、0.0で最低となります(JPEG形式のみで有効です)。

なお、getImage メソッドにより、グラフ画像をjava.awt.Imageクラスのインスタンスとして取得する事もできます。 これにより、グラフ画像を加工したり、別のGUI画面上に埋め込んだりする事も(それなりの処理を書く必要がありますが)可能です。

範囲やオプションなどの詳細設定

setXRange / setYRange / setZRange メソッドや setOptionSelected メソッドなどを用いて、プロット範囲やオプションなどの設定を行う事ができます。 例として、「配列データのプロット」項の最後で触れたメッシュ状の配列データをプロットし、メッシュプロットオプションを有効にして、範囲も設定してみましょう:

code/Sample3.java
実行結果

上のコードでは、setOptionSelectedメソッドでプロットオプションの有効/無効状態を操作しています。 操作するオプションの項目はRinearnGraph3DOptionItem列挙子の要素で指定します。 上では初期状態で有効な点プロットオプションを無効化し、メッシュプロットオプションを有効化しています。 また、setXRange / setYRange / setZRange メソッドでそれぞれX/Y/Z軸方向のプロット範囲を設定しています。

設定系のメソッドは他にも存在します。詳細については、RinearnGraph3DクラスのAPI仕様書をご参照ください。 なお、細かい設定を一括で行うには、リニアングラフ3Dの設定ファイルを loadConfigurationFile メソッドで読み込む方法もあります。

アニメーションプロット

アニメーションプロットを行うには、スレッドを生成して、その中で 「ファイルのプロット」や「配列データのプロット」 の項で扱ったファイルや配列データのプロットを繰り返し、少しずつ異なる内容を連続で描かせ続ければOKです。

ただし標準では、setData メソッドで配列データを渡した際、データの描画処理が完了するまで、呼び出し元に処理が戻らない事に留意する必要があります。 これは、アニメーションの各画面をコマ落ちなく画像に出力したい場合などには便利です。 しかし、例えば外部の装置などから入力されるデータをリアルタイムでプロットしたい場合などには、 次々と入ってくるデータに対して描画速度が追い付かない、つまり描画がボトルネックになってしまう可能性もあります。

そのような場合には、 setAsynchronousPlottingEnabled メソッドの引数にtrueを指定すると、 setData メソッド呼び出し時はすぐに処理が戻り、描画処理は別スレッドで非同期に、適当なタイミングで行われるようになります。 これは、一般にアニメーション速度を描画所要時間に依存させたくない場合にも有効です。

実際に、毎時刻の配列データを計算で生成し、非同期描画でアニメーションさせてみましょう:

code/Sample4.java
実行結果
(曲面グラフがプロットされ、形状がアニメーションで時間変化します。)

描画エンジンの直接操作による自由な3D描画

ここまで、座標値データから、点や線および面などを組み合わせて3Dのグラフを描く処理は、全てリニアングラフ3D側が自動で行っていました。 これは手軽なので便利ですが、用途によっては、自分で自由に、3D空間内に点や線および面などを描きたい場合もあるでしょう。

そのような場合には、リニアングラフ3Dの描画エンジンのAPIを使用します。 これは RinearnGraph3DRenderer クラスの各メソッドとして提供されています。このクラスの仕様書は下記で参照できます:

RinearnGraph3DRendererクラス API仕様書
https://www.rinearn.com/ja-jp/graph3d/api/com/rinearn/graph3d/renderer/RinearnGraph3DRenderer

このAPIを用いると、リニアングラフ3Dを通常のグラフソフトとしてだけではなく、プログラミングでの簡易3D描画環境としても活用する事ができます。 特に、表示ウィンドウやマウスでの視点操作機能などが最初からトータルで備わっているので、「 とにかく即席で3D図形を描画したい 」といった用途などに便利かもしれません。

リニアングラフ3Dの描画エンジンの基本特性

リニアングラフ3Dでは、全処理をJava言語で実装した、ソフトウェアレンダリング式の3D描画エンジンを採用しています。 これにより、動作環境に関する要求は特に無く、どこでも概ね同様の描画結果が得られます。

その半面として、描画速度は数十万ポリゴン/秒程度と、そう速くありません。また、描画形式はシンプルなZソート形式のフラットシェーディングのみとなっています。 遠近判定はピクセル単位ではなくポリゴン単位で行われるため、ポリゴン同士が交差・めり込んでいたり、遠近方向にかなり近接していたり、巨大なポリゴンがある箇所などでは、描画上の手前/奥の関係がラフになります。 そのため、細かく遠近判定させたい箇所では、ポリゴンも細かく分割してください。

簡単な例

さて、描画エンジンの処理は、もちろんリニアングラフ3D側からも呼び出されます。 そのため、ここで扱う描画エンジンの直接操作と、リニアングラフ3Dの他の機能(ファイルを開いてプロットしたり、メニューから手動でグラフ範囲やプロットオプションを変更したり、等々)とを併用したい場合には、少し工夫が必要です。 詳しくは次の項で説明しますが、とりあえずそのような事を一切考えずに、単に3D空間に図形を描きたいだけであれば、非常に単純にコードを書く事ができます。

まずは最も簡単な例として、3D空間内の自由な位置に、点と線、三角形、および四角形を1個ずつ描いてみましょう:

code/Sample5.java
実行結果

上のコードでは、まずグラフを生成して getRenderer メソッドで描画エンジンを取得し、グラフ空間の範囲を設定した上で、描画エンジンの各描画メソッドを呼び出して点や線などを描いています。

最後に、render メソッドでスクリーンの再描画を行わせていますが、この段階で初めて、3D空間内に描いた立体が、平面のグラフ画面に射影されて(2次元の絵として)描画されます。いわゆる3DCGのレンダリング処理です。 マウスで視点を操作した際などには自動で再レンダリングされますが、点や線の描画メソッドを呼ぶ度に毎回自動で再レンダリングされたりはしないため(処理コストが大きいためです)、このように最後に明示的に再レンダリングさせています。

drawPoint などの各描画メソッドの引数に渡す座標は、標準ではグラフ空間における座標と見なされます。 そのため、描画される絶対的な位置はグラフの範囲設定によって変化します。 また、グラフの範囲より外側にはみ出した内容は、標準では描画されません。このような細かい挙動を変更するには、各描画メソッドの引数で色を指定している部分で、 代わりに RinearnGraph3DDrawingParameter クラスのインスタンスを渡してください。 このクラスには、描画時のグラフ範囲に応じたスケーリングの有無や、はみ出した部分の除去の有無、および色設定や自動彩色の有無などを細かく設定できます。

なお、最初にRinearnGraph3DRendererクラスの clear メソッドを呼び出しておくと、グラフ範囲の目盛りや枠線を非表示にする事もできます。

グラフ範囲やプロットオプションを変更しても描画内容が消えないようにするには

ところで、上のサンプルコードでは、後からグラフ範囲やプロットオプションの変更操作などを行うと、描画内容が消えてしまいます。 そのような操作は、グラフ全体の立体形状の造りなおしが必要になるため、最初にリニアングラフ3D側から描画内容をリセットする処理が呼ばれるからです。

といっても、別にグラフ範囲やプロットオプションの変更を行う必要が無く、そもそもユーザーに画面上でそのような操作をさせたくない場合であれば、 RinearnGraph3Dクラスの setMenuVisibleメソッドの引数にfalseを指定して、メニューを非表示にする事もできます。

一方で、ちゃんとグラフ範囲の変更などに対応したい場合は、グラフの再描画が必要なタイミングをイベントとして受け取って、その度に描画しなおす事もできます。以下がその例です:

code/Sample6.java

このコードを実行した結果、描画される内容は、先ほどのSample5と全く同様です。 しかし、メニューバーから 「編集」 > 「範囲の設定」 などを選んでグラフの範囲を変更しても、内容が消えてしまう事は無く、ちゃんと正しいグラフ範囲で表示されます。

実行結果
(左:起動時の状態、右:グラフの範囲を手動で変更した後)

これを実現するために、上のコードではまず、グラフの再描画が必要な(今の描画内容が消えてしまうような)タイミングをイベントとして受け取れるように、 イベントリスナーの RinearnGraph3DPlottingListener インターフェースを実装して、 リニアングラフ3Dに登録しています。そして、実際にイベントが発生した際に呼ばれる plottingRequested メソッド内で、3D描画処理を再実行して描き直しています。

注意: plottingRequested 等の中で RinearnGraph3D クラスのメソッドは呼べない !

ここで一つ注意が必要です。 RinearnGraph3DPlottingListenerインターフェースを実装する際、plottingRequestedメソッドなどの各イベント処理メソッド内から、 描画エンジンではなくリニアングラフ3D本体側である RinearnGraph3Dクラスのメソッドを呼ばないようにしてください (描画エンジンであるRinearnGraph3DRendererクラスのメソッドは呼んでもOKです)。

というのも、例えば plottingRequested メソッド内から、RinearnGraph3Dクラスの setXRange メソッドでグラフ範囲を変更すると、 それによって再描画が必要になるためplottingRequested が呼ばれ、またその中で setXRange メソッドを呼んで … と無限ループに陥ってしまいます。

他にも、RinearnGraph3Dクラスの多くのメソッドは、処理が中途半端なタイミングで行われてしまうのを避けるために、描画エンジンの操作中は一旦待機して、描画完了してから処理を行うようになっています。 そして、plottingRequested などの実行中は描画エンジンが操作中であると見なされるため、その中でRinearnGraph3Dクラスのメソッドを呼ぶと、描画完了をいつまでも待機してしまい、処理が進まなくなってしまいます。

RinearnGraph3DPlottingListener インターフェースを実装した結果、グラフが動かなくなってしまったら、上の点を踏まえて見直してみてください。

※1: OracleとJavaは、Oracle Corporation 及びその子会社、関連会社の米国及びその他の国における登録商標です。文中の社名、商品名等は各社の商標または登録商標である場合があります。
※2: Windows は、米国 Microsoft Corporation の米国およびその他の国における登録商標です。この記事は独立著作物であり、Microsoft Corporation と関連のある、もしくはスポンサーを受けるものではありません。
※3: Linux は、Linus Torvalds 氏の米国およびその他の国における商標または登録商標です。
※ その他、文中に使用されている商標は、その商標を保持する各社の各国における商標または登録商標です。