OpenGLで物体移動と転がるキューブ

OpenGLでのポリゴン描画

SDL+OpenGLでの描画の基本的な流れは

  1. glBegin(mode)関数で描画開始、引数で描画モードを指定
  2. glVertix関数で頂点を指定する
  3. glEnd(void)関数で描画を終了
  4. SDL_GL_SwapWindow(window)関数で画面に反映

とかだったような。すぐに画面に反映させる場合はglFlush(void)関数を利用する。
ただし、SDLだとメモリ上の画面領域に先にかいて、SwapWindowで画面の入れ替えを行うから、一括でSwapする場合は意味がないかも。

関数全般については以下のサイトが
http://wisdom.sakura.ne.jp/system/opengl/gl3.html
各描画モードでの詳細は以下のサイトが参考になる。
http://www.komoto.org/opengl/sample01.html

オブジェクトの移動

OpenGLでは、オブジェクトの移動は変換行列で表される。線形代数的な話で、アフィン変換とかいうんだったっけ?
とりあえず、座標は
\vec{x}' = A_{n}A_{n-1} \cdots A_{2}A_{1}\vec{x}
のように変化行列を元の座標にかけ合わせていくことで計算される。

OpenGLで使いそうな関数だと

  • glTranslatef(dx,dy,dz) : (dx,dy,dz)だけ移動
  • glRotatef(angle ,vx,vy,vz) : ベクトル(vx,vy,vz)を回転軸としてangleだけ回転
  • glScalef(sx,sy,sz) : (sx,sy,sz)の倍率に拡大・縮小

の3つあたり。この一つ一つの処理がさっきの数式の変換行列Aに対応しているイメージ。
ところで、行列の演算は掛け算する順番が変わると結果も変わるので、適用する順番には注意が必要。

変換行列は呼び出すたびにどんどんスタックに溜まっていくので、毎フレーム描画し直す前に以下の関数で初期化する。

  • glLoadIdentity()

変換行列を初期化する (単位行列を持ってくる) とかそんな意味だった気がする。

また、蓄積するという特性のため、特定のオブジェクトを描画剃る前に今の座標の状態を保存、描画後に座標を復帰、とかが必要。

  • glPushMatrix() : 現在の変換行列を保存
  • glPopMatrix() : 保存していた変換行列を復帰させる

移動関連は以下のサイトが参考になりそう。
http://homepage3.nifty.com/li-chu/OpenGL/OpenGL03.html
http://www.komoto.org/opengl/sample03.html

まとめると、オブジェクトの描画は

// 描画のはじめに変換行列を初期化
glLoadIdentity();

// オブジェクトを描画
// 現在の変換行列を対比
glPushMatrix();
// 移動処理
glTranslatef();
glRotatef();
glBegin(GL_POLYGON);
{
    // 描画処理
    glVertex3f();
}
glEnd();
// 変換行列を復帰
glPopMatrix();

// すべての描画が終わったら画面に反映
glFlush();
SDL_GL_SwapWindow(window);

とかでいいのかな。

<注意>
変換はオブジェクトの描画に近い方から適用されるみたい。
つまり、上の例だとRotateが先に適用されてからTranslateが行われる。
参考
http://verygood.aid.design.kyushu-u.ac.jp/opengl2005/2d_2.html

転がるキューブ

今までのを踏まえて作ったコードを書いてみる。
以下のコードは決められた範囲をキューブがランダムに転がるようなもの。ただし、引き返す方向には行かない。
# 本当は通過したタイルに接触面の色をつけて、時間経過でフェードアウトとかしたかったけど、長くなりそうなので省略

/* -*- coding:utf-8 -*- */
/**
 * opengl_transfer.cpp
 */

#include <SDL.h>
#include <SDL_opengl.h>
#include <OpenGL/glu.h>

#include <iostream>
#include <string>

#include <cmath>
#include <cstdlib>

static const std::string WINDOW_CAPTION = "SDL with OpenGL : transfer test";
static const Uint32 WINDOW_WIDTH = 640;
static const Uint32 WINDOW_HEIGHT = 480;
static const Uint32 WINDOW_BPP = 32;

static const GLdouble SIGHT_ANGLE = 45.0;
static const GLdouble SIGHT_NEAR = 2.0;
static const GLdouble SIGHT_FAR = 200.0;

static const int FIELD_SIZE = 12;

static const Uint32 FPS = 60;

static SDL_Window* window;
static SDL_GLContext context;

// direction of move
typedef enum {
    DIR_Z_PLUS = 0,
    DIR_X_PLUS = 1,
    DIR_X_MINUS = 2,
    DIR_Z_MINUS = 3,
    DIR_STOP
} eDir;

eDir backDir = DIR_STOP;

// color array
static GLfloat red[] = {1.0f, 0.0f, 0.0f};
static GLfloat green[] = {0.0f, 1.0f, 0.0f};
static GLfloat blue[] = {0.0f, 0.0f, 1.0f};
static GLfloat yellow[] = {1.0f, 1.0f, 0.0f};
static GLfloat purple[] = {1.0f, 0.0f, 1.0f};
static GLfloat cyan[] = {0.0f, 1.0f, 1.0f};

// aspect of cube
typedef enum {
    ASPECT_TOP = 0,
    ASPECT_BOTTOM = 1,
    ASPECT_FRONT = 2,
    ASPECT_BACK = 3,
    ASPECT_LEFT = 4,
    ASPECT_RIGHT = 5,
    ASPECT_NUM
} eAspect;

// infomation about moveing cube
typedef struct CubeInfo {
    // pos
    GLfloat posX, posY, posZ;
    // color of each aspect
    GLfloat* color[ASPECT_NUM];
    // moving dir
    eDir dir;
    // moviing angle
    GLfloat dirAngle;

    // rotate cube
    void rotate(eDir rotateDir) {
        GLfloat* tmp = color[ASPECT_TOP];
        switch (rotateDir) {
        case DIR_Z_PLUS:
            posZ += 1.0f;
            color[ASPECT_TOP] = color[ASPECT_BACK];
            color[ASPECT_BACK] = color[ASPECT_BOTTOM];
            color[ASPECT_BOTTOM] = color[ASPECT_FRONT];
            color[ASPECT_FRONT] = tmp;
            break;
        case DIR_Z_MINUS:
            posZ -= 1.0f;
            color[ASPECT_TOP] = color[ASPECT_FRONT];
            color[ASPECT_FRONT] = color[ASPECT_BOTTOM];
            color[ASPECT_BOTTOM] = color[ASPECT_BACK];
            color[ASPECT_BACK] = tmp;
            break;
        case DIR_X_PLUS:
            posX += 1.0f;
            color[ASPECT_TOP] = color[ASPECT_RIGHT];
            color[ASPECT_RIGHT] = color[ASPECT_BOTTOM];
            color[ASPECT_BOTTOM] = color[ASPECT_LEFT];
            color[ASPECT_LEFT] = tmp;
            break;
        case DIR_X_MINUS:
            posX -= 1.0f;
            color[ASPECT_TOP] = color[ASPECT_LEFT];
            color[ASPECT_LEFT] = color[ASPECT_BOTTOM];
            color[ASPECT_BOTTOM] = color[ASPECT_RIGHT];
            color[ASPECT_RIGHT] = tmp;
            break;
        default:
            break;
        }
        dir = DIR_STOP;
        dirAngle = 0.0f;
    }
} CubeInfo;

static CubeInfo cubeInfo;

bool init();
bool finalize();

void update();
void draw();
void drawGrid();
void drawCube();
bool pollingEvent();

int main(int argc, char** argv) {
    // initialize
    if (!init()) {
        std::cerr << "ERROR: failed to initialize SDL" << std::endl;
        exit(1);
    }

    // mainloop
    static const Uint32 interval = 1000 / FPS;
    static Uint32 nextTime = SDL_GetTicks() + interval;
    bool skipDraw = false;
    while (true) {
        // check event
        if (!pollingEvent()) break;

        // update and draw
        update();
        if (!skipDraw) {
            draw();
            SDL_GL_SwapWindow(window);
        }

        int delayTime = (int)(nextTime - SDL_GetTicks());
        if (delayTime > 0) {
            SDL_Delay((Uint32)delayTime);
            skipDraw = false;
        } else {
            // skip next draw step because of no time
            skipDraw = true;
        }

        nextTime += interval;
    }

    // finalize
    finalize();
  
    return 0;
}

bool init() {
    // initialize SDL
    if( SDL_Init(SDL_INIT_VIDEO) < 0 ) return false;

    // enable double buffering
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    // create indow
    // set SDL_WINDOW_OPENGL to use opengl for drawing
    window = SDL_CreateWindow(WINDOW_CAPTION.c_str(),
                              SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
                              WINDOW_WIDTH, WINDOW_HEIGHT,
                              SDL_WINDOW_OPENGL);
    if (!window) return false;

    // create OpenGL context
    context = SDL_GL_CreateContext(window);
    if (!context) return false;

    // setup viewport
    glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT);
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    glEnable(GL_DEPTH_TEST);

    // setup projection matrix
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    GLdouble aspect = (GLdouble) WINDOW_WIDTH / (GLdouble) WINDOW_HEIGHT;
    gluPerspective(SIGHT_ANGLE, aspect, SIGHT_NEAR, SIGHT_FAR);

    // initialize cube info
    cubeInfo.posX = 0.0f;
    cubeInfo.posY = 0.0f;
    cubeInfo.posZ = 0.0f;
    cubeInfo.color[ASPECT_TOP] = red;
    cubeInfo.color[ASPECT_BOTTOM] = cyan;
    cubeInfo.color[ASPECT_FRONT] = green;
    cubeInfo.color[ASPECT_BACK] = purple;
    cubeInfo.color[ASPECT_LEFT] = blue;
    cubeInfo.color[ASPECT_RIGHT] = yellow;
    cubeInfo.dir = DIR_STOP;
    cubeInfo.dirAngle = 0.0f;

    // initialize rand
    srand(0);

    return true;
}

bool finalize() {
  // finalize SDL
  SDL_Quit();

  return true;
}

void update() {
    if (cubeInfo.dir == DIR_STOP) {
        // choose next direction
        while (true) {
            int nextDir = rand() % 4;
            // prevent from returning
            if (backDir == (eDir)nextDir) continue;
            // field size check
            GLfloat transPos = 0.0;
            switch (nextDir) {
            case DIR_Z_PLUS:
                transPos = cubeInfo.posZ + 1.0f;
                break;
            case DIR_Z_MINUS:
                transPos = cubeInfo.posZ - 1.0f;
                break;
            case DIR_X_PLUS:
                transPos = cubeInfo.posX + 1.0f;
                break;
            case DIR_X_MINUS:
                transPos = cubeInfo.posX - 1.0f;
                break;
            default:
                break;
            }
            if (-FIELD_SIZE/2 < transPos && transPos < FIELD_SIZE/2) {
                cubeInfo.dir = (eDir)nextDir;
                break;
            }
        }
    } else {
        // update position
        cubeInfo.dirAngle += 90.0f / 30;
        if (cubeInfo.dirAngle >= 90.0f) {
            backDir = (eDir)(3 - cubeInfo.dir);
            cubeInfo.rotate(cubeInfo.dir);
        }
    }
}

void draw() {
    // clear
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    // setup view
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt( -10.0f, 10.0f, -10.0f,
               0.0f, 0.0f, 0.0f,
               0.5f, 0.5f, 0.5f);

    // draw grid lines
    drawGrid();

    // draw cube
    drawCube();

    glFlush();
}

void drawGrid() {
    const int halfFieldSize = FIELD_SIZE/2;

    // z-axis
    for (GLint x=-halfFieldSize; x<halfFieldSize; x++) {
        GLfloat posX = x * 1.0f + 0.5f;
        glBegin(GL_LINES);
        {
            glColor3f(1.0f, 1.0f, 1.0f);
            glVertex3f(posX, 0.0f, -halfFieldSize+0.5f);
            glVertex3f(posX, 0.0f, halfFieldSize-0.5f);
        }
        glEnd();
    }

    // x-axis
    for (GLint z=-halfFieldSize; z<halfFieldSize; z++) {
        GLfloat posZ = z * 1.0f + 0.5f;
        glBegin(GL_LINES);
        {
            glColor3f(1.0f, 1.0f, 1.0f);
            glVertex3f(-halfFieldSize+0.5f, 0.0f, posZ);
            glVertex3f(halfFieldSize-0.5f, 0.0f, posZ);
        }
        glEnd();
    }
}

void drawCube() {
    glPushMatrix();
    glTranslatef(cubeInfo.posX, cubeInfo.posY, cubeInfo.posZ);
    switch (cubeInfo.dir) {
    case DIR_Z_PLUS:
        glTranslatef(0.0f, 0.0f, 0.5f);
        glRotatef(cubeInfo.dirAngle, 1.0f, 0.0f, 0.0f);
        glTranslatef(0.0f, 0.0f, -0.5f);
        break;
    case DIR_Z_MINUS:
        glTranslatef(0.0f, 0.0f, -0.5f);
        glRotatef(cubeInfo.dirAngle, -1.0f, 0.0f, 0.0f);
        glTranslatef(0.0f, 0.0f, 0.5f);
        break;
    case DIR_X_PLUS:
        glTranslatef(0.5f, 0.0f, 0.0f);
        glRotatef(cubeInfo.dirAngle, 0.0f, 0.0f, -1.0f);
        glTranslatef(-0.5f, 0.0f, 0.0f);
        break;
    case DIR_X_MINUS:
        glTranslatef(-0.5f, 0.0f, 0.0f);
        glRotatef(cubeInfo.dirAngle, 0.0f, 0.0f, 1.0f);
        glTranslatef(0.5f, 0.0f, 0.0f);
        break;
    default:
        break;
    }
    glTranslatef(0.0f, 0.5f, 0.0f);
    glBegin(GL_QUADS);
    {
        // top
        glColor3fv(cubeInfo.color[ASPECT_TOP]);
        glVertex3f(0.5f, 0.5f, 0.5f);
        glVertex3f(0.5f, 0.5f, -0.5f);
        glVertex3f(-0.5f, 0.5f, -0.5f);
        glVertex3f(-0.5f, 0.5f, 0.5f);

        // bottom
        glColor3fv(cubeInfo.color[ASPECT_BOTTOM]);
        glVertex3f(0.5f, -0.5f, 0.5f);
        glVertex3f(-0.5f, -0.5f, 0.5f);
        glVertex3f(-0.5f, -0.5f, -0.5f);
        glVertex3f(0.5f, -0.5f, -0.5f);

        // front
        glColor3fv(cubeInfo.color[ASPECT_FRONT]);
        glVertex3f(0.5f, 0.5f, 0.5f);
        glVertex3f(-0.5f, 0.5f, 0.5f);
        glVertex3f(-0.5f, -0.5f, 0.5f);
        glVertex3f(0.5f, -0.5f, 0.5f);

        // back
        glColor3fv(cubeInfo.color[ASPECT_BACK]);
        glVertex3f(0.5f, 0.5f, -0.5f);
        glVertex3f(0.5f, -0.5f, -0.5f);
        glVertex3f(-0.5f, -0.5f, -0.5f);
        glVertex3f(-0.5f, 0.5f, -0.5f);

        // left
        glColor3fv(cubeInfo.color[ASPECT_LEFT]);
        glVertex3f(0.5f, 0.5f, 0.5f);
        glVertex3f(0.5f, -0.5f, 0.5f);
        glVertex3f(0.5f, -0.5f, -0.5f);
        glVertex3f(0.5f, 0.5f, -0.5f);

        // right
        glColor3fv(cubeInfo.color[ASPECT_RIGHT]);
        glVertex3f(-0.5f, 0.5f, 0.5f);
        glVertex3f(-0.5f, 0.5f, -0.5f);
        glVertex3f(-0.5f, -0.5f, -0.5f);
        glVertex3f(-0.5f, -0.5f, 0.5f);
    }
    glEnd();
    glPopMatrix();
}

// polling event and execute actions
bool pollingEvent()
{
    SDL_Event ev;
    SDL_Keycode key;
    while ( SDL_PollEvent(&ev) )
    {
        switch(ev.type){
        case SDL_QUIT:
            // raise when exit event is occur
            return false;
            break;
        case SDL_KEYDOWN:
        // raise when key down
        {
            key = ev.key.keysym.sym;
            // ESC
            if(key == SDLK_ESCAPE){
                return false;
            }
        }
        break;
        }
    }
    return true;
}

コンパイル

g++ -o opengl_transfer opengl_transfer.cpp `sdl2-config --cflags` `sdl2-config --libs` -framework GLUT -framework OpenGL


これぐらいできれがゲームの一つぐらい作れそうな予感。
しかし、そろそろコードがすごく長くなってきたのでブログへの掲載方法変えないとだめだねぇ。
次回はどうやって公開の仕方を変えるかについて調べますか。