【3D 描画 Step3】元素名を 2D 描画する OpenGL には文字描画の概念がないこと、元素名は回転してほしくないことの 2 点の理由から、元素 名は 2D 描画する。そのために、原子の 3 次元空間の座標から、文字を描画するウィンドウ上の座標を求 める。 <座標変換について> ※座標変換について、まとまった詳しい解説は「OpenGL による 3D 描画の基礎知識」の 3 ページ以 降に記述してあるので、参照のこと。 y軸 104.45° θ z軸 x軸 ワールド座標系の軸 ◆オブジェクト座標系とワールド座標系 O 原子も H 原子も描画するときは glut.glutSolidSphere(0.2f, div, div); ………(*) と記述する。引数は順に半径、Z 軸まわりの分割数、Z 軸に沿った分割数で、球の中心座標を設定する引 数はない。これは、常にこの関数が球の中心を原点として描画するからである。この場合の座標系が「オ ブジェクト座標系」となる。 しかし、図のように実際にワールド座標系で考えれば O 原子の中心座標は (0, 0.4, 0) だし、右側の H 原子は (cosθ, 0.4-sinθ, 0) [※θ=(180°-104.45°)/2] である。このような位置に描画するため には、描画したい球の中心が原点になるよう事前に glTranslatef や glRotatef で座標変換しておく。 例えば、O 原子の場合、 gl.glTranslatef(0.0f, 0.4f, 0.0f); // y 軸正方向に 0.4 平行移動 θに相当する を実行した後で(*)の球の描画命令を書けばよい。また、右側の H 原子は 上記の 0.4 の平行移動が利いた状態でさらに以下を実行する gl.glRotatef(-degree, 0.0f, 0.0f, 1.0f); // 座標軸を z 軸周りに-degree 度回転 gl.glTranslatef(1.0f, 0.0f, 0.0f); // x 軸正方向に 1 だけ平行移動 -1- このような、回転や平行移動による座標変換の情報は 「ModelView 行列」 が持っている。ModelView 行列は、gl.glPushMatrix()でいったん保存しておき、回転や平行移動を重ね て描画した後、gl.glPopMatrix()で保存した状態に戻すことができる。 ◆ワールド座標系とウィンドウ座標系 3 次元空間であるワールド座標から、2 次元の表示領域に対する座標をもとめるには、 「Projectioin 行列」 を使う。この行列は、3D からどのように 2D に投影するかを決めるものである。 また、パネルに対する表示領域を「ビューポート」といい、4 次元の配列で表現する。 ビューポート=[左下の点の x 座標, y 座標, 幅, 高さ] ◆オブジェクト座標系からウィンドウ座標系への変換 OpenGL には便利な関数があって、 glu.gluProject(objx, objy, objz, modelMatrix, 0, projMatrix, 0, viewport, 0, winPos, 0); objx, objy, objz:3D 空間の x 座標、y 座標、z 座標(オブジェクト座標系) modelMatrix:ModelView 行列 projMatrix:Projection 行列 viewport:ビューポート を呼び出すと winPos(double の配列)にビューポートの左下を原点とする、右・上が正方向の 2 次元 ピクセルの単位での座標がはいる。つまりオブジェクト座標系からウィンドウ座標系に変換してくれる。 しかし、描画するにはパネルの左上を原点とした、下向きが正の座標でなければならないので、今回 は上記の関数で求めた座標をさらに変換する。下記のソースコードはこの変換部分が空欄になっている ので、下図を参考に考えて埋めなさい。 描画で必要な 座標の原点 viewport[2] viewport[0] ビューポート viewport[3] glu.gluProject で求まる 座標の原点 viewport[1] <作成手順> 1.WaterPanel.java を編集する。 2.実行して元素名 O,H が正常に描画されることを確認する -2- getHeight() 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 WaterPanel.java package chemical.water; import import import import import import import java.awt.Color; java.awt.Font; java.awt.Graphics; java.awt.Point; java.awt.event.MouseEvent; java.awt.event.MouseListener; java.awt.event.MouseMotionListener; import import import import import javax.media.opengl.GL; javax.media.opengl.GLAutoDrawable; javax.media.opengl.GLEventListener; javax.media.opengl.GLJPanel; javax.media.opengl.glu.GLU; import com.sun.opengl.util.GLUT; public class WaterPanel extends GLJPanel implements GLEventListener, MouseListener, MouseMotionListener { private private private private GL gl; // OpenGL の関数群をもつオブジェクトその1 GLU glu; // OpenGL の関数群をもつオブジェクトその2 GLUT glut; // OpenGL の関数群をもつオブジェクトその3 int div = 20; // 球、円柱の分割数 // 軸と O-H のなす角(※水の O-H がなす角は 104.45 度) private float degree = (180-104.45f)/2.0f; private private private private float rotx = 0.0f, roty = 0.0f; float sRotx, sRoty; Point startPoint, endPoint; boolean defaultFlg = true; // // // // 回転量 ドラッグ開始時の回転量 ドラッグの開始点と終了点 true だったら回転量を 0 にする private private private private private private double[] model = new double[16]; // ModelView 行列 double[] proj = new double[16]; // Projection 行列 int[] view = new int[4]; // ビューポート double[] pointO = new double[3]; // O 原子のウィンドウ座標 double[] pointH1 = new double[3]; // H 原子 1 のウィンドウ座標 double[] pointH2 = new double[3]; // H 原子 2 のウィンドウ座標 /* 光 ----------------------------------------------------*/ // 光の位置 {x 座標, y 座標, z 座標, 光源までの距離(0 は無限円)} private float[] lPosition = { -10.0f, 10.0f, 10.0f, 0.0f }; // 光の色 {R, G, B, アルファ(透明度)} ※数値は 0 から 1 まで private float[] lSpecular = { 0.8f, 0.8f, 0.8f, 1.0f }; // 鏡面 private float[] lDiffuse = { 0.8f, 0.8f, 0.8f, 1.0f }; // 拡散 private float[] lAmbient = { 0.4f, 0.4f, 0.4f, 1.0f }; // 環境 /* 物体の反射率 -------------------------------------------*/ // {R, G, B, アルファ(透明度)} ※数値は 0 から 1 まで private float[] mSpecular = { 0.3f, 0.3f, 0.3f, 1.0f }; // 鏡面 private float[] mDiffuse = { 0.2f, 0.2f, 0.2f, 1.0f }; // 拡散 // 鏡面係数:きらめきの度合い(0~128) private float mShininess = 10.0f; /* 色の定義 -----------------------------------------------*/ // {R, G, B, アルファ(透明度)} ※数値は 0 から 1 まで private float[] blue = { 0.0f, 0.0f, 1.0f, 1.0f }; // 青 private float[] red = { 1.0f, 0.0f, 0.0f, 1.0f }; // 赤 private float[] green = {0.0f, 1.0f, 0.5f, 1.0f }; // 緑 /** * コンストラクタ -3- 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 WaterPanel.java */ public WaterPanel () { // MouseEvent を受け取れるようにする addMouseListener(this); // MouseMotionEvent を受け取れるようにする addMouseMotionListener(this); // 3D 描画できるようにリスナ登録 addGLEventListener(this); } /* 2D 描画 ************************************************************/ @Override public void paintComponent(Graphics g) { // 親クラスの paintComponent 呼び出し super.paintComponent(g); // フォント設定 ※Dialog は Windows では MS ゴシック g.setFont(new Font("Dialog", Font.BOLD, 24)); // 白色設定 g.setColor(Color.WHITE); // 文字位置の微調整用 int tune = 8; // 元素名を描画 g.drawString("O", (int)pointO[0]-tune, (int)pointO[1]+tune); g.drawString("H", (int)pointH1[0]-tune, (int)pointH1[1]+tune); g.drawString("H", (int)pointH2[0]-tune, (int)pointH2[1]+tune); } /* 3D 描画 ************************************************************/ /** * x 軸周りに円柱を描画 * @param r 円柱の半径 * @param length 円柱の長さ * @param offset offset ≦x≦offset+length に描かれる */ public void drawCylinder(float r, float length, float offset) { double t; // 要素として連続する四角形を使う gl.glBegin(GL.GL_QUAD_STRIP); for (int i=0; i<=div; i++) { // 角度を計算(ラジアン) t = 2.0 * Math.PI * i / div; // 光の反射の法線ベクトル gl.glNormal3f(0, (float)Math.cos(t), (float)Math.sin(t)); // 頂点を設定 gl.glVertex3f(offset, (float)(r*Math.cos(t)), (float)(r*Math.sin(t))); gl.glVertex3f(offset+length, (float)(r*Math.cos(t)), (float)(r*Math.sin(t))); } gl.glEnd(); } /** * オブジェクト座標から、ウィンドウ座標を求める */ public boolean gluProject(double objx, double objy, double objz, double[] modelMatrix, double[] projMatrix, int[] viewport, double[] winPos ) { /* gluProject:3D空間のx座標,y座標,z座標,ModelView行列,Projection行列,ビューポートを使って * ウィンドウ座標を求める。 * ただし、ビューポート(描画領域)の左下が原点、y 座標上向き */ -4- 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 WaterPanel.java boolean bl = glu.gluProject(objx, objy, objz, modelMatrix, 0, projMatrix, 0, viewport, 0, winPos, 0); winPos[0] += viewport[0]; // パネルの左上を原点とする y 座標下向きに変換 // ※viewport の中身は[左下の x 座標, y 座標, ビューポート(描画領域)の幅, 高さ] winPos[1] = getHeight() - viewport[1] - winPos[1]; return bl; } /** * GLEventListener のメソッド:3D 描画メソッド */ @Override public void display(GLAutoDrawable drawable) { /* deaultFlg=true なら最初かリセットボタンが押された状態 * なので、回転量を 0 にするs */ if (defaultFlg) { rotx = 0.0f; roty = 0.0f; defaultFlg = false; } // 背景色で塗りつぶし gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT); // 現時点のマトリクス(Modelview 行列)を保存 ※必ず glPopMatrix()と対で使用する /* glPushMatrix と glPopMatrix()は、座標を回転したり平行移動したり * したものを、する前の状態を覚えておくために使う。 * これを使わないと、回転などがどんどん重なっていってしまう。 */ gl.glPushMatrix(); // z軸上 15 の位置から原点を見る,上方向は y 軸 // glulookat(視点の位置 x,y,z, 視点から見る座標 x,y,z, 上方向ベクトル x,y,z) glu.gluLookAt(0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // マウスの移動量に応じて回転 gl.glRotatef(rotx, 1.0f, 0.0f, 0.0f); gl.glRotatef(roty, 0.0f, 1.0f, 0.0f); // y 軸正方向に少しずらす gl.glTranslatef(0.0f, 0.4f, 0.0f); /* 棒 1 を描画(z 軸周りに-degree 度回転)----------*/ // ModelView 行列保存 gl.glPushMatrix(); // 色指定 // ※環境光の反射率とは、つまり物体の色になる gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, green, 0); // 座標軸を z 軸周りに-degree 度回転 // ※2,3,4 番目の引数は回転軸を表す(順に x, y, z) gl.glRotatef(-degree, 0.0f, 0.0f, 1.0f); // 半径 0.07 の円柱を 0≦x≦1 に描画 drawCylinder(0.07f, 1.0f, 0.0f); // 行列を保存した時の状態に戻す gl.glPopMatrix(); /* 棒 2 を描画(z 軸周りに degree 度回転)----------*/ gl.glPushMatrix(); gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, green, 0); gl.glRotatef(degree, 0.0f, 0.0f, 1.0f); drawCylinder(0.07f, 1.0f, -1.0f); // -1≦x≦0 gl.glPopMatrix(); /* O 原子を原点に描画 -------------------------*/ gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, red, 0); // 塗りつぶしの球を描画 // glutSolidSphere(半径, Z 軸まわりの分割数, Z 軸に沿った分割数) glut.glutSolidSphere(0.3, div, div); -5- 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 WaterPanel.java // ModelView 行列を取得 gl.glGetDoublev(GL.GL_MODELVIEW_MATRIX, model, 0); // O 原子のウィンドウ座標を取得 gluProject(0.0, 0.0, 0.0, model, proj, view, pointO); // H 原子 1 を描画 -----------------------------*/ gl.glPushMatrix(); gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, blue, 0); gl.glRotatef(-degree, 0.0f, 0.0f, 1.0f); gl.glTranslatef(1.0f, 0.0f, 0.0f); // x 方向に 1 だけ平行移動 glut.glutSolidSphere(0.2, div, div); gl.glGetDoublev(GL.GL_MODELVIEW_MATRIX, model, 0); // ModelView 行列を取得 gluProject(0.0, 0.0, 0.0, model, proj, view, pointH1); gl.glPopMatrix(); // H 原子 2 を描画 -----------------------------*/ gl.glPushMatrix(); gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, blue, 0); gl.glRotatef(degree, 0.0f, 0.0f, 1.0f); gl.glTranslatef(-1.0f, 0.0f, 0.0f); // x 方向に-1 だけ平行移動 glut.glutSolidSphere(0.2, div, div); gl.glGetDoublev(GL.GL_MODELVIEW_MATRIX, model, 0); // ModelView 行列を取得 gluProject(0.0, 0.0, 0.0, model, proj, view, pointH2); gl.glPopMatrix(); // glPushMatrix()を呼ぶ前の行列の状態に戻す gl.glPopMatrix(); } /** * GLEventListener のメソッド:表示の切り替えが発生した場合に呼ばれる * 今回は特に記述することはない */ @Override public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { } /** * GLEventListener のメソッド:初期化時に呼ばれる */ @Override public void init(GLAutoDrawable drawable) { // OpenGL の関数群をもつオブジェクトを取得 gl = drawable.getGL(); glu = new GLU(); glut = new GLUT(); // 背景色を黒に設定 gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 影に隠れて見えないものは表示しない gl.glEnable(GL.GL_DEPTH_TEST); // 光を有効にする gl.glEnable(GL.GL_LIGHTING); gl.glEnable(GL.GL_LIGHT0); // 光の反射の法線ベクトルを正規化 gl.glEnable(GL.GL_NORMALIZE); // 光の位置を設定 /* 4 番目の数字はオフセット。 * JOGL は c++のソースを JAVA に自動変換している。 * c++では配列はポインタなので、オフセットの概念がある * JAVA にポインタはないので引数が増やされているのだが、 * オフセットの数字は 0 でよい。 */ gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, lPosition, 0); // 光の色を設定 -6- 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 WaterPanel.java gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, lSpecular, 0); // gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, lDiffuse, 0); gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, lAmbient, 0); // 物体の反射率を設定 gl.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, mSpecular, 0); // gl.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, mDiffuse, 0); // gl.glMaterialf(GL.GL_FRONT, GL.GL_SHININESS, mShininess); // 鏡面 // 拡散 // 環境 鏡面 拡散 鏡面係数 } /** * GLEventListener のメソッド:表示領域が変更されたときに呼ばれる */ @Override public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { // ビューポート(描画領域)を設定 gl.glViewport(0, 0, width, height); // マトリクスとして Projection 行列を対象にする gl.glMatrixMode(GL.GL_PROJECTION); // マトリクスを単位行列で初期化 gl.glLoadIdentity(); // 視点の設定(参考資料を参照) // gluPerspactive(視野角, 縦横比, near, far); glu.gluPerspective(10.0, (double)width/(double)height, 1.0, 100.0); // マトリクスとして ModelView 行列を対象にする gl.glMatrixMode(GL.GL_MODELVIEW); // Projection 行列取得 gl.glGetDoublev(GL.GL_PROJECTION_MATRIX, proj, 0); // ビューポート取得 gl.glGetIntegerv(GL.GL_VIEWPORT, view, 0); } /* MouseListener のメソッド **************************************************/ @Override public void mouseClicked(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseEntered(MouseEvent e) { // TODO Auto-generated method stub } @Override public void mouseExited(MouseEvent e) { // TODO Auto-generated method stub } /** * マウスを押したらスタートポイントに設定 */ @Override public void mousePressed(MouseEvent e) { // マウスを押した場所を取得 startPoint = e.getPoint(); // 現時点での回転量を保存 sRotx = rotx; sRoty = roty; } -7- 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 WaterPanel.java @Override public void mouseReleased(MouseEvent e) { // TODO Auto-generated method stub } /* MouseMotionListener のメソッド *************************************************/ /** * ドラッグで回転量を計算 */ @Override public void mouseDragged(MouseEvent e) { // マウスの場所を取得 endPoint = e.getPoint(); // 回転量を計算(パネルの端から端で一回転) rotx = sRotx + 360.0f*(float)(endPoint.y - startPoint.y)/(float)getWidth(); roty = sRoty + 360.0f*(float)(endPoint.x - startPoint.x)/(float)getHeight(); // 再描画 repaint(); } @Override public void mouseMoved(MouseEvent e) { // TODO Auto-generated method stub } /* defaultFlg の setter ***************************************************************/ public void setDefaultFlg(boolean defaultFlg) { this.defaultFlg = defaultFlg; } } -8-
© Copyright 2024 ExpyDoc