先週まで9回に渡ってJOGLを解説してきたのは理由があります。

その理由は,2Dのアプリケーションの中で3Dを使ってもらいたいということです。

皆さんもご存じの3Dデスクトップ環境であるProject Looking Glassは,3D空間の中に2Dアプリケーションを埋め込みます。このコンセプトはとてもおもしろく,わくわくしてきます。しかしながら,ウィンドウ・システムから変えなくてはならないので,ちょっと試してみるにはハードルが高いかもしれません。

ここで解説するのは,Project Looking Glassの方向性とは逆,つまり2Dのアプリケーションの中に3Dを使ってみるということです。

なぜ?

もちろん,見た目をよくするためです。やっぱり,見た目は重要ですよ。

どんなにすばらしい機能を実現したとしても,見た目や使い方が洗練されていないと,それだけで使いたくなくなってしまいませんか。そうなってしまったら,アプリケーションを作った人も,使う人も不幸です。

そこで,3Dを使ってアプリケーションの見た目をクールにしてみましょう。今週はAWT,来週はSwingでJOGLを使ってみます。

GLCanvasクラスを見直す

今までのサンプルはすべてjavax.media.opengl.GLCanvasクラスを使用してきました。

このクラスは,JOGL的にはGLAutoDrawableインタフェースを実装したクラスとして扱います。一方で,Frameクラスのaddメソッドの引数にしていることからもわかるように,java.awt.Canvasクラスの派生クラスでもあります。

Canvasクラスといえば,AWTのコンポーネントの一つですね。つまり,GLCanvasクラスはAWTのコンポーネントとして扱えるのです。

そこで,GLCanvasクラスで描画するものを2Dのアプリケーションっぽくしてみましょう。例えば,ボタンです。ただ単にボタンを作るのではButtonクラスを使うのと何も変りません。そこで,二つの状態を持って,それらを切り替えられるようなボタンを作ってみましょう。名付けてFlipFlapButtonです。

サンプルはここからダウンロードできます: FlipFlapButton.java WalkingRobot.java FlipFlapSample.java

FlipFlapButtonは二つのポリゴンからなり,それぞれにテクスチャを貼ります。これらの二つのポリゴンを交互に表示するようにしました。

ちょうど一枚のパネルの表と裏にテクスチャが貼ってあるような感じです。それを回転させることで,表と裏を切り替えられるようにしています。

FlipFlapButtonクラスはコンポーネントとして扱えるようにGLCanvasクラスから派生させます。また,3Dの描画を行うため,GLEventListenerインタフェースを実装しています。

public class FlipFlapButton extends GLCanvas
                            implements GLEventListener {

コンストラクタを以下に示します。

  public FlipFlapButton(File frontImage, File backImage) 
                               throws FileNotFoundException {
    this.frontImage = frontImage;
    this.backImage = backImage;
  
    // イメージ・ファイルがない場合は,例外発生       
    if (!frontImage.exists()) {
      throw new FileNotFoundException(frontImage.getPath());
    } else if (!backImage.exists()) {
      throw new FileNotFoundException(backImage.getPath());
    }
 
    addGLEventListener(this);
 
    // マウス・イベントを扱えるようにする
    enableEvents(AWTEvent.MOUSE_EVENT_MASK);
 
    animator = new Animator(this);
  }

コンストラクタの引数はテクスチャにするためのイメージ・ファイルです。二つのファイルを指定するのは表と裏があるためです。

GLAutoDrawable#addGLEventListenerメソッドでGLEventListenerオブジェクトを登録するのは今までのサンプルと一緒です。しかし,このサンプルではGLEventListenerインタフェースをインプリメントしているのは自分自身なので,引数をthisにしています。

また,今までのサンプルではマウス・イベントを扱うために,MouseListenerインタフェースを使用していましたが,AWTのコンポーネントとして扱うために,Component#enableEventsメソッドを使用しました。実際のイベント処理はprocessMouseEventメソッドで行います。

  protected void processMouseEvent(MouseEvent event) {
    if (!actionState) {
      if (event.getID() == MouseEvent.MOUSE_PRESSED) {
         pressState = true;
      } else if (event.getID() == MouseEvent.MOUSE_RELEASED) {
         pressState = false;
      } else if (event.getID() == MouseEvent.MOUSE_CLICKED) {
         actionState = true;
         direction = !direction;
      }
             
      super.processMouseEvent(event);
    }
  }

processMouseEventメソッドでは状態の更新を行っています。ボタンなので,押されたときは引っ込むようにし,その後回転させるようにします。

したがって,MOUSE_PRESSEDのときに押された状態を示すフラグpressStateをtrueにし,MOUSE_RELEASEDのときにpressStateをfalseにします。

そして,クリックの後にポリゴンを回転させるため,MOUSE_CLICKEDのときに回転させることを示すフラグactionStateをtrueにします。directionという変数は回転させる方向を示すbooleanの変数です。回転方向はクリックごとに変化させるために,ここで示したようにtrueだったらfalseに,falseだったらtrueにします。

最後に,親クラスのprocessMouseEventメソッドをコールします。この処理をしておかないと,他のリスナにイベントが伝搬しなくてなってしまいます。

さて,ボタンの描画部分です。2DのAWTのコンポーネントであれば,描画処理はpaintメソッドですが,3DなのでGLEventListenerインタフェースのdisplayメソッドに記述します。

  public void display(GLAutoDrawable drawable) {
    gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
 
    gl.glPushMatrix();
 
    // マウス・ボタンが押されている状態であれば,
    // 奥に移動
    if (pressState) {
      gl.glTranslatef(0.0f, 0.0f, PRESS_DIFF);
    }
 
    // 回転
    gl.glRotatef(angle, 1.0f, 0.0f, 0.0f);
 
    // 回転角度の更新
    if (direction) {
      if (angle < 180.0f) {
        angle += DIFF;
      } else {
        // 回転終了
        actionState = false;
      }
    } else {
      if (angle > 0.0f) {
        angle -= DIFF;
      } else {
        // 回転終了
        actionState = false;
      }
    }
  
    // ボタンの描画
    drawButton();
  
    gl.glPopMatrix();
  }

最初にGL#glPushMatrixメソッド,最後にGL#glPopMatrixメソッドがあるのはお約束です。

まず,ボタンが押されたときの移動処理,次に回転処理を記述します。その後に回転角度の更新を行います。角度が0度もしくは180度になったら,回転状態を示すactionStateをfalseにします。

そして,ボタンの描画を行うdrawButtonメソッドをコールします。

  private void drawButton() {
    // 前面のテクスチャを使用可能にする
    frontTexture.enable();
    frontTexture.bind();
 
    // 前面の描画
    gl.glBegin(GL.GL_POLYGON);
    gl.glTexCoord2f(0.0f, 0.0f);
    gl.glVertex3fv(vertices, 0);
    gl.glTexCoord2f(1.0f, 0.0f);
    gl.glVertex3fv(vertices, 3);
    gl.glTexCoord2f(1.0f, 1.0f);
    gl.glVertex3fv(vertices, 6);
    gl.glTexCoord2f(0.0f, 1.0f);
    gl.glVertex3fv(vertices, 9);
    gl.glEnd();
 
    frontTexture.disable();
  
    // 裏面のテクスチャを使用可能にする
    backTexture.enable();
    backTexture.bind();
 
    // 裏面の描画
    gl.glBegin(GL.GL_POLYGON);
    gl.glTexCoord2f(1.0f, 0.0f);
    gl.glVertex3fv(vertices, 12);
    gl.glTexCoord2f(1.0f, 1.0f);
    gl.glVertex3fv(vertices, 15);
    gl.glTexCoord2f(0.0f, 1.0f);
    gl.glVertex3fv(vertices, 18);
    gl.glTexCoord2f(0.0f, 0.0f);
    gl.glVertex3fv(vertices, 21);
    gl.glEnd();
 
    backTexture.disable();
  }
実行結果
図1 実行結果
ボタン回転中
図2 ボタン回転中

テクスチャは表と裏の2枚あるので,使用するときにTexture#enableメソッド,Texture#bindメソッドをコールして使用可能にしておく必要があります。

その後は前回と同じようにテクスチャ座標を指定し,3Dの座標を指定していくだけです。

これでFlipFlapButtonクラスはできました。本来ならば,ボタンに状態を持っているのでItemEventを発生させるのがいいのですが,今回は省略しました。

単にボタンだけあってもしかたないので,第7回で作成したロボットに再び登場してもらいましょう。ボタンをクリックしたら歩き始め,もう一度クリックしたら止まるようにしました。このために,WalkingRobotクラスはアニメーションの開始/終了ができるように変更してあります。

実行するのはFlipFlapSampleクラスです。図1に実行結果を示しました。特に違和感もなく,AWTのコンポーネントとして扱えることがおわかりでしょうか。図2には回転中のボタンを示しました。

しかし,今さらAWTという感じはありますね。そこで,来週は3DのSwingコンポーネントを作ってみましょう。

著者紹介 櫻庭祐一

横河電機の研究部門に勤務。同氏のJavaプログラマ向け情報ページ「Java in the Box」はあまりに有名