立方体を回転させる

前回はNetBeans OpenGL Packにより生成された雛型を元にJOGL(JSR 231)の基本的な使い方を紹介した。今回はそれを少し発展させて、OpenGLで立方体を描画しボタンのクリックに合わせてそれを回転させるようなプログラムを作ってみたい。

まずはGLEventListenerインタフェースを実装した描画用のクラスを作成する。ここでは"My3DGraphic"という名前でリスト1のようにした。My3DGraphicにはフィールドとしてX方向およびY方向の角度を保持し、それぞれに対するGetter/Setterを用意する。このフィールド値によって立方体の回転量が決まる。

displayメソッドでは座標系を移動/回転した上で、そこに立方体を描画する。座標系の回転はGLクラスのglRotatefメソッドにより行うが、その前にglPushMatrixメソッドで現在の座標系を保存しておく。glRotatefメソッドでは、第1引数に回転させる角度を、第2~第4引数に回転の中心とする軸のx,Y,Z座標を指定する。その後立方体を描画し、最後にglPopMatrixメソッドを用いて座標系を保存しておいたものに戻す。

initメソッド、reshapeメソッド、displayChangedメソッドは前回用いたものと同じなので今回は省略する。

リスト1 My3DGraphic.java - 回転の処理を追加

package apisample;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

public class My3DGraphic implements GLEventListener {
    private int angleX = 0;  // X方向の(Y軸に沿った)角度
    private int angleY= 0;   // Y方向の(X軸に沿った)角度

    public void display(GLAutoDrawable drawable) {
        GL gl = drawable.getGL();

        // 描画領域をクリア
        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
        // カーソルを最初の描画位置に移動
        gl.glLoadIdentity();

        // 現在の座標系を保存
        gl.glPushMatrix();

        // 座標系を移動してX方向およびY方向に回転
        gl.glTranslatef(0.0f, 0.0f, -10.0f);
        gl.glRotatef(this.angleX, 0.0f, 1.0f, 0.0f);
        gl.glRotatef(this.angleY, 1.0f, 0.0f, 0.0f);


        // 立方体の描画
        gl.glBegin(GL.GL_QUADS);
            // 左面
            gl.glColor3f(1.0f, 0.0f, 0.0f);
            gl.glVertex3f(-1.0f, 1.0f, 1.0f);
            gl.glVertex3f(-1.0f, 1.0f, -1.0f);
            gl.glVertex3f(-1.0f, -1.0f, -1.0f);
            gl.glVertex3f(-1.0f, -1.0f, 1.0f);
            // 右面
            gl.glColor3f(0.0f, 1.0f, 0.0f);
            gl.glVertex3f(1.0f,  1.0f, 1.0f);
            gl.glVertex3f(1.0f,  1.0f, -1.0f);
            gl.glVertex3f(1.0f, -1.0f, -1.0f);
            gl.glVertex3f(1.0f, -1.0f, 1.0f);
            // 上面
            gl.glColor3f(0.0f, 0.0f, 1.0f);
            gl.glVertex3f(1.0f, 1.0f, 1.0f);
            gl.glVertex3f(1.0f, 1.0f, -1.0f);
            gl.glVertex3f(-1.0f, 1.0f, -1.0f);
            gl.glVertex3f(-1.0f, 1.0f, 1.0f);
            // 下面
            gl.glColor3f(1.0f, 1.0f, 1.0f);
            gl.glVertex3f(1.0f, -1.0f, 1.0f);
            gl.glVertex3f(1.0f, -1.0f, -1.0f);
            gl.glVertex3f(-1.0f, -1.0f, -1.0f);
            gl.glVertex3f(-1.0f, -1.0f, 1.0f);
        gl.glEnd();

        // 座標系を戻す
        gl.glPopMatrix();

        // 全ての描画を終了
        gl.glFlush();
    }

        // Getter/Setter
    public int getAngleX() {
        return this.angleX;
    }
    public void setAngleX(int angle) {
        this.angleX = angle;
    }
    public int getAngleY() {
        return this.angleY;
    }
    public void setAngleY(int angle) {
        this.angleY = angle;
    }

    public void init(GLAutoDrawable drawable) {
                // 省略
    }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
                // 省略
    }

    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
    }
}

続いて、Swingを利用したGUIプログラムの方を作成する。NetBeans OpenGL Packを使っている場合、MatisseのパレットにGLAutoDrawableインタフェースを実装したGLCanvasとGLJPanelという2つのコンポーネントが追加される。したがって通常のSwingプログラムと同様に、Matisseを用いたレイアウトデザインが可能である。たとえば今回の場合、上下左右に回転させるための各ボタンと、立方体を描画するためのGLCancasを図1のような感じに配置すればよい。

図1 MatisseでGLCanvasを利用できる

プログラムのソースはリスト2のようになる(Matisseにより自動生成されるコードは一部を除き省略)。まずMy3DGraphicのインスタンスを生成し、それをGLCanvasに追加する。ボタンが押された際にはMy3DGraphicのangleXまたはangleYの値を増減させる。

リスト2 JoglSampe2.java - ソース側を修正

apisample package;

import com.sun.opengl.util.Animator;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

public class JoglSampe2 extends javax.swing.JFrame {
    My3DGraphic myGraphic = new My3DGraphic();  // OpenGLキャンバス

    public JoglSampe2() {
        initComponents();

        // キャンバスにリスナーをセット
        this.canvas.addGLEventListener(this.myGraphic);

        // Animatorを生成
        final Animator animator = new Animator(this.canvas);

        // ウィンドウのクローズ処理
        this.addWindowListener(new WindowAdapter() {
            @Override
            public void windowClosing(WindowEvent e) {
                new Thread(new Runnable() {
                    public void run() {
                        animator.stop();
                        System.exit(0);
                    }
                }).start();
            }
        });

        // アニメーションをスタート
        animator.start();
    }


    private void upRotateButtonActionPerformed(java.awt.event.ActionEvent evt) {
            this.myGraphic.setAngleY(this.myGraphic.getAngleY() - 5);
    }

    private void rightRotateButtonActionPerformed(java.awt.event.ActionEvent evt) {
            this.myGraphic.setAngleX(this.myGraphic.getAngleX() + 5);
    }

    private void downRotateButtonActionPerformed(java.awt.event.ActionEvent evt) {
            this.myGraphic.setAngleY(this.myGraphic.getAngleY() + 5);
    }

    private void leftRotateButtonActionPerformed(java.awt.event.ActionEvent evt) {
            this.myGraphic.setAngleX(this.myGraphic.getAngleX() - 5);
    }


    // 以下、NetBeansによる自動生成部分

    private void initComponents() { ... }

    public static void main(String args[]) { ... }

    private javax.media.opengl.GLCanvas canvas;
    private javax.swing.JButton downRotateButton;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JButton leftRotateButton;
    private javax.swing.JButton rightRotateButton;
    private javax.swing.JButton upRotateButton;
}

このプログラムを実行すると図2のように表示される。方向ボタンをクリックするとその分だけ立方体が回転する。

図2 ボタンを押した分け立方体が回転する

GLJPanelを使う

次に、図3のように3Dグラフィックの描画領域の中に、Swingによる2Dのボタンを配置してみる。ここではボタンをクリックしたら立方体が左回転/左回転でアニメーションするようなプログラムを考える。

図3 3Dパネル内に2Dのボタンを表示する

まず3Dコンポーネントを描画するMy3DGraphic.javaには、左右の回転のon/off状態を保持するフィールドと、それを制御するメソッドをそれぞれリスト3のように追加しておく。

リスト3 My3DGraphic.java - 回転の状態を保持するフィールドと、on/offを制御するメソッドを追加

    private boolean isRightRotating = false;  // 右回転on/off
    private boolean isLeftRotating = false;   // 左回転on/off

        ......

        // 右回転on
    public void startRightRotate() {
        this.isLeftRotating = false;
        this.isRightRotating = true;
    }
        // 左回転on
    public void startLeftRotate() {
        this.isRightRotating = false;
        this.isLeftRotating = true;
    }
        // 停止
    public void stopRotate() {
        this.isLeftRotating = false;
        this.isRightRotating = false;
    }

また、displayメソッドの先頭に、回転がonの場合に角度を変化させるようリスト4の処理を追加する。

リスト4 My3DGraphic.java - displayメソッドの先頭に回転の処理を追加

        // 回転がonのとき
        if (this.isRightRotating) {
            angleX += 1;
        } else if (this.isLeftRotating) {
            angleX -= 1;
        }

続いてJoglSampe2.javaだが、今回はGLCanvasに代わってGLJPanelクラスを利用する。GLJPaneはjavax.swing.JPanelを継承したクラスだが、GLCanvasと同様にGLAutoDrawableインタフェースをimplementsしているため3Dグラフィックの描画に対応している。NetBeans OpenGL Packを使っている場合はGLCanvasのときと同じようにMatisseを利用してデザインできる。今回は図4のように配置すればよい。

図4 GLJPanelの上にJButtonを配置する

ソースコード側は、まずGLJPanelを利用するようにコンストラクタを修正する (リスト5)。

リスト5 JoglSampe2.java - コンストラクタを修正

        // キャンバスにリスナーをセット
        //this.canvas.addGLEventListener(this.myGraphic);
        // GLパネルにリスナーをセット
        this.glPanel.addGLEventListener(this.myGraphic);

ボタンを押した際のイベント処理部分はリスト2.6のようになる。

リスト6 JoglSampe2.java - ボタンを押したときの処理

    private void startRightRotateActionPerformed(java.awt.event.ActionEvent evt) {
        this.myGraphic.startRightRotate();
   }

    private void startLeftRotateActionPerformed(java.awt.event.ActionEvent evt) {
        this.myGraphic.startLeftRotate();
    }

    private void stopRotateActionPerformed(java.awt.event.ActionEvent evt) {
        this.myGraphic.stopRotate();
    }

これで実行すれば図3のように表示されるはずだ。

まとめ

最初に書いたように、JOGLはその名の通りJavaとOpenAPIのバインディングを定義したAPIであり、メソッド名などはOpenGL APIのものをほぼそのまま引き継いでいるため、基本的な部分さえ理解しておけば残りはOpenGLの知識だけでプログラミングできてしまう。本連載は3Dプログラミングの方法を解説することが本質ではないため、非常に基礎的な部分しか紹介できなかったが、3Dグラフィックが扱えることでアプリケーションの表現の幅が大きく広がるので、ぜひ積極的に使ってみてほしいと思う。