●BrightObjectClassLib
「BrightObjectClassLib」は、BrightObjectで作ったアニメーションを再生するためのライブラリです。
今回はこのライブラリを使ってアニメーションさせるプログラムを紹介します。

○BrightObjectClassLibを使う
BrightObjectClassLibは「スタティックライブラリ」と呼ばれる種類のライブラリです。
静的ライブラリとも呼ばれるかもしれません。
DLLとは違い、コンパイル時にリンクを行うライブラリです。
作成・動作確認はVisualStudio 2010 Professionalで行っています。

使うためには「BrightObjectClass.h」「LibBrightObject.lib」をダウンロードしてください。
BrightObjectClassLibをダウンロード(2012/11/28版)

ダウンロードしたら適当な場所に解凍します。
BrightObjectClass.hはVisualStudioのプロジェクトと同じ場所がいいかもしれません。

ヘッダファイルとライブラリファイルは以下のようにしてプロジェクトに追加することができます。


windows.hはWindows APIを使うため、stdio.hはC言語標準ライブラリを使うため、
GL.hとGLU.hはOpenGL APIを使うためにincludeします。
その次にBrightObjectClass.hをincludeします。
プロジェクトにもBrightObjectClass.hを追加します。


LibBrightObject.libは、絶対パス指定で、

    #pragma comment(lib, "(絶対パス)/LibBrightObject.lib")

と指定します。プロジェクトに追加はしなくてもこのようにすればOKです。
上の画像の例では「D:\lib」のフォルダの中にLibBrightObject.libを入れておいた場合を示しています。

BrightObjectClassLibを使うためにはもう1つ設定する箇所があります。
ソリューションエクスプローラのプロジェクト名を右クリックし、「プロパティ」を選びます。
その画面で以下の「ランタイムライブラリ」の部分を「マルチスレッド(/MT)」にします。



このようにしないとBrightObjectClassLibをリンクする際にエラーとなります。


これでBrightObjectClassLibを使う準備が整いました。
最初にBrightObjectClassLibを使ったソースコードを示して簡単に解説していきます。


実行ファイルの全ソース:
#include <windows.h>
#include <stdio.h>

#include<gl/GL.h>
#include<gl/GLU.h>

#include"BrightObjectClass.h"
#pragma comment(lib, "D:/Programming/OpenGL/myProgramData/UseTLOOJP/LibBrightObject.lib")

#include"Charactor.h"
#include"Keyboard.h"
#include"Camera.h"


//CTLO cTlo;            //CTLOクラスオブジェクト
COJP cOjp/*, cOjp2*/;   //COJPクラスオブジェクト

//キャラクタークラスオブジェクト
CCharactor cChara;

//キーボードクラスオブジェクト
CKeyboard cKeybrd;

//カメラクラスオブジェクト
CCamera cCam;

//プロトタイプ宣言
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
ATOM InitRegWindowClass(HINSTANCE);


char szClassName[] = "UseTLOOJP"; //ウインドウクラス

BOOL MOUSE_R_DOWN;
POINTS mouseMovePoint;      //WM_MOUSEMOVEで取得する現在のマウス座標
POINTS mouseMovePoint2;   //MOUSEMOVE前のマウス座標

//マウス右ドラッグ用
GLdouble degX, degY;            //ウインドウ上でのx,yの移動量

GLdouble angleX, angleY;    //X軸、Y軸を回転させる角度


//キーボードで入力されたキーを判定するための変数
char str[2];

//キー状態をあらわす列挙変数
#define KEYMODIFY_UP        100
#define KEYMODIFY_DOWN    101
#define KEYMODIFY_LEFT    102
#define KEYMODIFY_RIGHT      103
#define KEYMODIFY_NONE    104

int kModify;

DWORD dwTime;
DWORD slpStart, slpEnd;

//WinMain関数
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow)
{
//この関数で行うこと
//ウインドウクラスの登録
//メインウインドウの生成
//メッセージループ

    MSG msg;
    HWND hWnd;

    if(!InitRegWindowClass(hCurInst))
        return FALSE;
    //f(!InitWindow(hCurInst, nCmdShow))
    //  return FALSE;

    RECT rc = {0, 0, 1024, 768};

    //クライアント領域とウインドウスタイルを与えてウインドウサイズを設定する。
    AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_SYSMENU | WS_BORDER, FALSE, 0);

    //ウインドウの作成
    hWnd = CreateWindow(szClassName,
        "UseTLO_OJP",
        WS_OVERLAPPEDWINDOW,    //ウインドウの種類
        CW_USEDEFAULT,      //X座標
        CW_USEDEFAULT,      //Y座標
        1024,   //幅
        768,            //高さ
        NULL,     //親ウインドウのハンドル。親自体を作るときはNULL
        NULL,     //メニューハンドル。クラスメニューを使うときはNULL
        hCurInst,     //インスタンスハンドル
        NULL);

    if(!hWnd)
        return FALSE;

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    

    
    timeBeginPeriod(1);         //時間取得する関数の結果の精度を上げるため
    dwTime = timeGetTime() + ONE_FRAME_TIME;    //処理開始時刻

    //メッセージループ(ループを抜けたらプログラム終了)
    while(TRUE)
    {
        slpStart = 0;
        slpEnd = 0;

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
            if (msg.message == WM_QUIT)
                return EXIT_SUCCESS;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        if(dwTime > timeGetTime())  //ゲームメインループ。ここにキー入力とかを入力する
        {
            slpStart = timeGetTime();
            Sleep(1);
            slpEnd = timeGetTime();
            continue;
        }

        dwTime = timeGetTime() + ONE_FRAME_TIME + (slpEnd - slpStart);
        SendMessage(hWnd, WM_APP+1, 0, 0);
    }

    timeEndPeriod(1);
    return (int)msg.wParam;
}


//ウインドウクラスの登録関数
ATOM InitRegWindowClass(HINSTANCE hInst)
{
    WNDCLASSEX wc;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = CS_HREDRAW | CS_VREDRAW;   //ウインドウの幅か高さを変更したら再描画させる
    wc.lpfnWndProc = WndProc; //ウインドウプロシージャ名
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInst; //インスタンス(WinMainから受け取ったもの)
    wc.hIcon = (HICON)LoadImage(NULL, 
        MAKEINTRESOURCE(IDI_APPLICATION), 
        IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED);
    wc.hCursor = (HCURSOR)LoadImage(NULL, 
        MAKEINTRESOURCE(IDC_ARROW),
        IMAGE_CURSOR, 0, 0,
        LR_DEFAULTSIZE | LR_SHARED);
    wc.hbrBackground = (HBRUSH)GetStockObject(RGB(255, 255, 255));  //こうするとOpenGLで描画したときに、ちらつきがかなり低減する。
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szClassName;
    wc.hIconSm = (HICON)LoadImage(NULL, 
        MAKEINTRESOURCE(IDI_APPLICATION),
        IMAGE_ICON, 0, 0, LR_DEFAULTSIZE | LR_SHARED);

    return (RegisterClassEx(&wc));
}


char fileName[MAX_PATH];    //コモンダイアログで開く時のファイルネーム。GetOpenFileName関数を呼ぶと、フルパス付きファイルネームが格納される。
char fileTitle[64];      //パス無しファイル名



//Windows側のデバイスコンテキスト
HDC WinDC;

//OpenGLを描画するレンダリングコンテキスト
HGLRC hRC;

DWORD timeSum;  //キャラクターの描画と補間にかかった時間の合計
DWORD dw, dw2;

//ウインドウプロシージャ
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp)
{
    int pixelFormat;

    //wgl使用するときに使う構造体
    PIXELFORMATDESCRIPTOR pfd = { 
        sizeof(PIXELFORMATDESCRIPTOR),   // size of this pfd 
        1,                     // version number 
        PFD_DRAW_TO_WINDOW |   // support window 
        PFD_SUPPORT_OPENGL |   // support OpenGL 
        PFD_DOUBLEBUFFER |     // double buffered 
        PFD_SUPPORT_GDI,
        PFD_TYPE_RGBA,         // RGBA type 
        24,                    // 24-bit color depth 
        0, 0, 0, 0, 0, 0,      // color bits ignored 
        0,                     // no alpha buffer 
        0,                     // shift bit ignored 
        0,                     // no accumulation buffer 
        0, 0, 0, 0,            // accum bits ignored 
        32,                    // 32-bit z-buffer 
        1,                     // stencil buffer bit size 
        0,                     // no auxiliary buffer 
        PFD_MAIN_PLANE,        // main layer 
        0,                     // reserved 
        0, 0, 0                // layer masks ignored 
    };


    switch(msg){
        case WM_CREATE:
            //WGL設定
            WinDC = GetDC(hwnd);
            pixelFormat = ChoosePixelFormat(WinDC, &pfd);
            SetPixelFormat(WinDC, pixelFormat, &pfd);
            hRC = wglCreateContext(WinDC);    //hRC作成
            wglMakeCurrent(WinDC,hRC);

            kModify = KEYMODIFY_NONE;

            //OpenGL初期化
            cOjp.init(1024, 768);
            
            

            // -----## OJPファイル表示 開始##-----         
            BOOL b;

            // -----## ファイルを直接指定して開く場合 開始##-----
            
            //b = cOjp.LoadOJP("pensan_asuka_complite2_weight0410_2.OJP");

            //if(b == FALSE)
            //{
            //  MessageBox(hwnd, "OJP開けません。その1", "OJP", MB_OK);
            //}
            ////アニメーションの開始時刻と終了時刻を決定する
            //(cOjp.GetKeyFrameObject())->beforeAnimetionSetting((cOjp.GetKeyFrameObject())->GetFirstKeyFrameTime(), (cOjp.GetKeyFrameObject())->GetFinalKeyFrameTime());
            

            // ## 2体目を開く時は、1体目とは違うCOJPクラスオブジェクトを用意する ##
            //b = cOjp2.LoadOJP("BrightObjectChara_10_kf.OJP");
            //if(b == FALSE)
            //{
            //  MessageBox(hwnd, "OJP開けません。その2", "OJP", MB_OK);
            //}
            //(cOjp2.GetKeyFrameObject())->beforeAnimetionSetting((cOjp2.GetKeyFrameObject())->GetFirstKeyFrameTime(), (cOjp2.GetKeyFrameObject())->GetFinalKeyFrameTime());
            
            // -----## ファイルを直接指定して開く場合 終了##-----


            // -----# コモンダイアログを使用する場合 開始#-----
            OPENFILENAME ofn; //ファイルを開く為に使う構造体
                
            memset(&ofn, 0, sizeof(OPENFILENAME));  //OPENFILENAME構造体を0で初期化

            ofn.lStructSize = sizeof(OPENFILENAME);   //この構造体のサイズ
            ofn.hwndOwner = hwnd; //コモンダイアログの親ウインドウハンドル
            ofn.lpstrFilter = "OJPファイル(*.OJP)\0*.OJP\0\0";  //ダイアログに指定ファイル形式のみ表示するフィルタをかける。
            ofn.lpstrFile = fileName; //GetOpenFileName関数を呼ぶとフルパス付きファイルネームが格納される
            ofn.nMaxFile = MAX_PATH;    //lpstrFileの大きさ(byte数)
            ofn.Flags = OFN_FILEMUSTEXIST;  //存在しないファイル名を入力したら警告を出すようにする
            ofn.lpstrDefExt = "";  //ファイル名のみ入力した場合、拡張子としてこの文字をファイル名に追加する。NULLで省略する。
            ofn.nMaxFileTitle = 64;      //パス無しファイル名を格納する配列の長さ
            ofn.lpstrFileTitle = fileTitle;   //パス無しファイル名を格納する配列の先頭アドレス
            ofn.lpstrTitle = "TLO・OJPファイルを開く";  //ダイアログのタイトル

            if(GetOpenFileName(&ofn) == TRUE)
            {
                b = cOjp.LoadOJP(fileTitle);
                if(b == TRUE)
                {
                    //アニメーションの開始時刻と終了時刻を決定する(拡張子がojpの場合のみにする)
                    (cOjp.GetKeyFrameObject())->beforeAnimetionSetting((cOjp.GetKeyFrameObject())->GetFirstKeyFrameTime(), (cOjp.GetKeyFrameObject())->GetFinalKeyFrameTime());


                    //##シャドウマッピングの準備

                    cOjp.SetShadowDiffuse(0.0, 0.0, 0.0, 0.3);    //影の拡散光をセット
                    cOjp.SetShadowSpecular(0.0, 0.0, 0.0, 1.0);      //影の鏡面光をセット

                    //シャドウマッピング用光源の位置ベクトルを設定
                    Vector sdwLight;
                    sdwLight.x = 0.0;
                    sdwLight.y = 1.0;
                    sdwLight.z = 0.5;
                    cOjp.SetEasyShadowLightPos(sdwLight);

                    //基準ジョイントの位置をTLOクラス側へ渡す(これが無いと停止時に影が上手く作られなくなる)
                    cOjp.GetBasicJointPosUseShadowMapToTLO();

                    //シャドウマップを行うように設定
                    cOjp.SetEasyShadowMappingFlag(TRUE);
                    
                    //## GLSL ##
                    //GLSLの初期化
                    cOjp.initUseGlslClass();

                    //トゥーンシェーディングを行うための設定
                    //GLSLソースファイルを指定
                    cOjp.filePathVert("D:/Programming/toon.vert");
                    cOjp.filePathFrag("D:/Programming/toon.frag");

                    //シェーダプログラムのコンパイル・リンク
                    cOjp.CreateShaderProgram();

                    //OpenGLのTEXTURE1(テクスチャユニットの1番)にトゥーンシェーディング用のbmpファイルを読み込む
                    cOjp.LoadToonTexture_TEXTURE1("D:/Programming/toon.bmp");
                }   
            }
            else
            {
                MessageBox(hwnd, "プログラムを終了します。", "終了メッセージ", MB_OK);
                SendMessage(hwnd, WM_CLOSE, 0, 0);
            }
            // -----# コモンダイアログを使用する場合 終了#-----
            // -----## OJPファイル表示 終了##-----



            // -----## TLOファイル表示 開始##-----

            //tloFileData = cTlo.LoadTLO("pogi2.TLO");

            // -----## TLOファイル表示 終了##-----

            angleX = 0.0;
            angleY = 0.0;
            break;

        case WM_RBUTTONDOWN:
            if(MOUSE_R_DOWN == FALSE)
            {
                MOUSE_R_DOWN = TRUE;    //マウス左が押されたらTRUE
                SetCapture(hwnd);  //マウスキャプチャの開始

                mouseMovePoint2.x = LOWORD(lp);
                mouseMovePoint2.y = HIWORD(lp);
            }
            break;

        //マウス右を離した時
        case WM_RBUTTONUP:
            if(MOUSE_R_DOWN == TRUE)
            {
                MOUSE_R_DOWN = FALSE; //マウス左が離されたらFALSEに
                ReleaseCapture();  //マウスキャプチャの終了
            }
            break;

        //マウスの移動に関する処理
        case WM_MOUSEMOVE:

            if(MOUSE_R_DOWN == TRUE)    //マウス右が押されている時
            {   
                mouseMovePoint.x = LOWORD(lp);
                mouseMovePoint.y = HIWORD(lp);
            
                //## 空間を回転させる処理 (OpenGLは上方向にyが増えるので注意)##
                //ウインドウ上で、端から端までマウスが動いたら1回転と決め、
                //回転角度を算出する
                degX = 360.0 * (mouseMovePoint2.y - mouseMovePoint.y) / 1024;
                degY = 360.0 * (mouseMovePoint.x - mouseMovePoint2.x) / 768;

                //回転角度の更新
                angleX -= degX;
                angleY += degY;

            }

            //次のマウスムーブで、今回の座標を計算に使う
            mouseMovePoint2 = mouseMovePoint;
            break;

        //ゲームメインループの処理内容を記述する
        case WM_APP+1:
            //OpenGL初期化
            cOjp.init(1024, 768);

            //###  キャラクターの描画と補間
            //キャラクターの描画と補間の処理にかかった時間の合計
            timeSum = 0;    //0で初期化
            
            //アニメーション再生をするか、アニメーションしないキャラを描画するか判定
            if((GetKeyState(VK_UP) & 0x8000) || (GetKeyState(VK_DOWN) & 0x8000) || (GetKeyState(VK_LEFT) & 0x8000) || (GetKeyState(VK_RIGHT) & 0x8000))
                cOjp.SetAnimationPlayingFlag();
            else
                cOjp.ResetAnimationPlayingFlag();

            //1体目を描画
            if(cOjp.GetAnimationPlayingFlag() == TRUE)    //マウスクリックでcOjp.SetAnimationPlayingFlag()が実行されたらアニメーション描画処理
            {
                glPushMatrix();

                GLfloat lightPos[] = {0.0, 0.0, 1.0, 0.0};    //光源の位置
                glLightfv(GL_LIGHT0, GL_POSITION, lightPos);    //光源LIGHT0の位置を決める

                //座標系をz軸方向-move_Z(つまりmove_Z分手前)に平行移動させる(ビューポートに入れるため)
                glTranslatef(-1.0, 0.0, -7.0);

                //カメラの移動(実際は回転)
                cCam.position(angleX, angleY);
                                
                
                //1フレームの処理の間に移動する量をセット
                cChara.setMove(0.02, 0.0, 0.02, cKeybrd.GetPushedKey());

                ////移動量で指定された場所にキャラを描画
                glTranslatef(cChara.move.x, cChara.move.y, cChara.move.z);

                //キー入力によってキャラクタの向きを変える
                cKeybrd.inputKey();

                //1フレーム分のキャラクターとそのときの動きを描画する
                cOjp.playOJPAnimationOneFrame(WinDC);

                glPopMatrix();


                dw = timeGetTime();
                timeSum = dwTime - dw;
                if(timeSum < ONE_FRAME_TIME)
                {
                    timeSum = ONE_FRAME_TIME;
                }
                else    //1フレーム時間を越えた場合
                {
                    timeSum = dw - dwTime;  //どのくらいの時間オーバーしたか
                    timeSum  += ONE_FRAME_TIME;
                }

                cOjp.SetAppointedTime(timeSum);
                cOjp.ChangeKeyFrame();    //足された時間によってキーフレームの入れ替えが発生する場合はここで入れ替え
            }

            //### アニメーションしないとき。停止した姿勢を描画する(初期姿勢の描画)
            else if(cOjp.GetAnimationPlayingFlag() == FALSE/* || cOjp2.GetAnimationPlayingFlag() == FALSE*/)
            {
                //デプスの有効化
                glEnable(GL_DEPTH_TEST);
                GLfloat lightPos[] = {0.0, 0.0, 1.0, 0.0};    //光源の位置
                glLightfv(GL_LIGHT0, GL_POSITION, lightPos);    //光源LIGHT0の位置を決める

                glEnable(GL_LIGHTING);    //シェーディング有効化
                glEnable(GL_LIGHT0);        //0番目の光源を使用
                glShadeModel(GL_SMOOTH);

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

                //座標系をz軸方向-move_Z(つまりmove_Z分手前)に平行移動させる(ビューポートに入れるため)
                glTranslatef(-1.0, 0.0, -7.0);

                //カメラの移動(実際は回転)
                cCam.position(angleX, angleY);
                
                
                //移動量で指定された場所にキャラを描画
                glTranslatef(cChara.move.x, cChara.move.y, cChara.move.z);

                //キー入力によってキャラクタの向きを変える
                cKeybrd.RotateByPushedKey();

                //TLOファイルの内容を描画
                cOjp.DisplayTLO_GL(angleX, angleY);

                glPopMatrix();

                glDisable(GL_DEPTH_TEST);
                glDisable(GL_LIGHT0);
                glDisable(GL_LIGHTING);
            }

            glFinish();
            SwapBuffers(WinDC);   //前後の描画領域を入れ替え
            break;

        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;

        case WM_DESTROY:
            wglMakeCurrent(WinDC, 0);
            wglDeleteContext(hRC);      

            PostQuitMessage(0);
            break;

        default:
            return (DefWindowProc(hwnd, msg, wp, lp));
    }

    return 0;
}


ポイントとなる部分は以下です。

○WinMain()
//WinMain関数
int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow)
{
    MSG msg;
    HWND hWnd;

    if(!InitRegWindowClass(hCurInst))
        return FALSE;

    RECT rc = {0, 0, 1024, 768};

    //クライアント領域とウインドウスタイルを与えてウインドウサイズを設定する。
    AdjustWindowRectEx(&rc, WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_SYSMENU | WS_BORDER, FALSE, 0);

    //ウインドウの作成
    hWnd = CreateWindow(szClassName,
        "UseTLO_OJP",
        WS_OVERLAPPEDWINDOW,    //ウインドウの種類
        CW_USEDEFAULT,      //X座標
        CW_USEDEFAULT,      //Y座標
        1024,   //幅
        768,            //高さ
        NULL,     //親ウインドウのハンドル。親自体を作るときはNULL
        NULL,     //メニューハンドル。クラスメニューを使うときはNULL
        hCurInst,     //インスタンスハンドル
        NULL);

    if(!hWnd)
        return FALSE;

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    

    
    timeBeginPeriod(1);         //時間取得する関数の結果の精度を上げるため
    dwTime = timeGetTime() + ONE_FRAME_TIME;    //処理開始時刻

    //メッセージループ(ループを抜けたらプログラム終了)
    while(TRUE)
    {
        slpStart = 0;
        slpEnd = 0;

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
            if (msg.message == WM_QUIT)
                return EXIT_SUCCESS;
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        if(dwTime > timeGetTime())  //ゲームメインループ。ここにキー入力とかを入力する
        {
            slpStart = timeGetTime();
            Sleep(1);
            slpEnd = timeGetTime();
            continue;
        }

        dwTime = timeGetTime() + ONE_FRAME_TIME + (slpEnd - slpStart);
        SendMessage(hWnd, WM_APP+1, 0, 0);
    }

    timeEndPeriod(1);
    return (int)msg.wParam;
}
メッセージループです。
画面を更新しつつ、キー入力などのイベントも拾います。

InitRegWindowClass(hCurInst)でウインドウクラス登録し、
CreateWindow()でウインドウを作成し、ShowWindow()・UpdateWindow()でウインドウ表示します。

timeBeginPeriod()〜timeEndPeriod()で囲み、時間の精度を指定します。
こうすると、どうやらtimeGetTime()で取得できる時間の精度が向上するようです。

timeGetTime()とフレームレート(ONE_FRAME_TIME。20fpsとしています)数を足して、
現在時刻から1フレームの処理が終了するまでの時間をdwTimeに格納します。

while文では、「ウインドウメッセージの処理」「ゲーム用のメインループ処理」の2つを行っています。
最初のif文がウインドウメッセージの処理ですが、PeekMessage()でプログラムにメッセージが送られているかどうか調べます。
メッセージがあったら、普通のWindowsプログラムのようにWndProc()で、そのメッセージに対応する処理が行われます。

次のif文では1フレームにONE_FRAME_TIME(#define で20と定義)以下の処理時間だった場合の処理です。
1msスリープした後、continueでwhileの最初から処理が始まります。
待ち時間の間にウインドウメッセージがあればその処理が行えます。
「dwTime > timeGetTime()」がFALSEを返すまで1ms待ちます。

1フレームの処理に与えられた時間(ONE_FRAME_TIME)が過ぎたら
dwTimeに次のフレームの処理終了時間が入ります(今の時間+ONE_FRAME_TIME)。
そして、SendMessage()で「WM_APP+1」というイベントを発生させます。
WndProc()のswitch文の中に「WM_APP+1」があると思いますが、
このイベントが実質のゲームループとなります。



○「WM_APP+1」イベント
        //ゲームメインループの処理内容を記述する
        case WM_APP+1:
            //OpenGL初期化
            cOjp.init(1024, 768);

            //###  キャラクターの描画と補間
            //キャラクターの描画と補間の処理にかかった時間の合計
            timeSum = 0;    //0で初期化
            
            //アニメーション再生をするか、アニメーションしないキャラを描画するか判定
            if((GetKeyState(VK_UP) & 0x8000) || (GetKeyState(VK_DOWN) & 0x8000) || (GetKeyState(VK_LEFT) & 0x8000) || (GetKeyState(VK_RIGHT) & 0x8000))
                cOjp.SetAnimationPlayingFlag();
            else
                cOjp.ResetAnimationPlayingFlag();

            //1体目を描画
            if(cOjp.GetAnimationPlayingFlag() == TRUE)    //マウスクリックでcOjp.SetAnimationPlayingFlag()が実行されたらアニメーション描画処理
            {
                glPushMatrix();

                GLfloat lightPos[] = {0.0, 0.0, 1.0, 0.0};    //光源の位置
                glLightfv(GL_LIGHT0, GL_POSITION, lightPos);    //光源LIGHT0の位置を決める

                //座標系をz軸方向-move_Z(つまりmove_Z分手前)に平行移動させる(ビューポートに入れるため)
                glTranslatef(-1.0, 0.0, -7.0);

                //カメラの移動(実際は回転)
                cCam.position(angleX, angleY);
                                
                
                //1フレームの処理の間に移動する量をセット
                cChara.setMove(0.02, 0.0, 0.02, cKeybrd.GetPushedKey());

                ////移動量で指定された場所にキャラを描画
                glTranslatef(cChara.move.x, cChara.move.y, cChara.move.z);

                //キー入力によってキャラクタの向きを変える
                cKeybrd.inputKey();

                //1フレーム分のキャラクターとそのときの動きを描画する
                cOjp.playOJPAnimationOneFrame(WinDC);

                glPopMatrix();


                dw = timeGetTime();
                timeSum = dwTime - dw;
                if(timeSum < ONE_FRAME_TIME)
                {
                    timeSum = ONE_FRAME_TIME;
                }
                else    //1フレーム時間を越えた場合
                {
                    timeSum = dw - dwTime;  //どのくらいの時間オーバーしたか
                    timeSum  += ONE_FRAME_TIME;
                }

                cOjp.SetAppointedTime(timeSum);
                cOjp.ChangeKeyFrame();    //足された時間によってキーフレームの入れ替えが発生する場合はここで入れ替え
            }

            //### アニメーションしないとき。停止した姿勢を描画する(初期姿勢の描画)
            else if(cOjp.GetAnimationPlayingFlag() == FALSE/* || cOjp2.GetAnimationPlayingFlag() == FALSE*/)
            {
                //デプスの有効化
                glEnable(GL_DEPTH_TEST);
                GLfloat lightPos[] = {0.0, 0.0, 1.0, 0.0};    //光源の位置
                glLightfv(GL_LIGHT0, GL_POSITION, lightPos);    //光源LIGHT0の位置を決める

                glEnable(GL_LIGHTING);    //シェーディング有効化
                glEnable(GL_LIGHT0);        //0番目の光源を使用
                glShadeModel(GL_SMOOTH);

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

                //座標系をz軸方向-move_Z(つまりmove_Z分手前)に平行移動させる(ビューポートに入れるため)
                glTranslatef(-1.0, 0.0, -7.0);

                //カメラの移動(実際は回転)
                cCam.position(angleX, angleY);
                
                
                //移動量で指定された場所にキャラを描画
                glTranslatef(cChara.move.x, cChara.move.y, cChara.move.z);

                //キー入力によってキャラクタの向きを変える
                cKeybrd.RotateByPushedKey();

                //TLOファイルの内容を描画
                cOjp.DisplayTLO_GL(angleX, angleY);

                glPopMatrix();

                glDisable(GL_DEPTH_TEST);
                glDisable(GL_LIGHT0);
                glDisable(GL_LIGHTING);

            }

            glFinish();
            SwapBuffers(WinDC);   //前後の描画領域を入れ替え
            break;
ここでは、「WM_APPP+1」イベントをゲームのグラフィック処理をするイベントとしています。
この例では、BrightObjectLibをここで使用しています。

まず、あらかじめ作成したcojpを初期化します。
cojpオブジェクトはCOJPクラスオブジェクトです。
「cojp.メソッド名」という部分がBrightObjectClassLibのメソッドを使っているところです。
init()の引数はウインドウサイズです。

timeSumは、後で出てきますが、1フレームの描画処理にかかった時間を持ちます。
今回のプログラムでは1フレーム=20ms(#define で ONE_FRAME_TIME として定義)
描画処理開始前にはとりあえず0で初期化しておきます。

その次の処理は、キーボードの上下左右キーが押されていたら
アニメーション再生フラグをTRUE(SetAnimationPlayingFlag()を実行する)にし、
押されていない場合はFALSE(ResetAnimationPlayingFlag())にします。
TRUEにすると開いたOJPファイルのアニメーションが可能となります。

GetAnimationPlayingFlag()を使って、アニメーションフラグがTRUEかどうかをチェックします。
TRUEであれば描画処理と描画処理にかかった時間を計測します。
以下がBrightObjectClassのオリジナルメソッドです。

----------------------------------------
playOJPAnimationOneFrame(HDC hdc)
引数:ウインドウハンドル
動作:1フレーム分のアニメーションを再生する。再生する部分(時刻)はSetAppointedTime()で設定ができます。

SetAppointedTime(DWORD time)
引数:時間(ミリ秒)
動作:引数の時間分だけアニメーションを再生開始する時刻をすすめまる。
例)
アニメーション再生開始時間:0ミリ秒(コンストラクタ内で0に初期化しています)

playOJPAnimationOneFrame()の処理に20ミリ秒かかったとします。

SetAppointedTime(20)と指定します。
ここで、かかった時間の合計(0+20=20)が計算されます。

次にplayOJPAnimationOneFrame()を実行すると、
20ミリ秒の姿勢から1フレーム分のアニメーションを再生することができます。

ChangeKeyFrame()
引数:無し。
動作:キーフレームの入れ替え処理が発生するかどうかを判断し、発生する場合はキーフレームを入れ替えます。

アニメーションはあるキーフレームの間で、姿勢を補間することで実現させています。
アニメーション全体が1000ミリ秒分で、キーフレームが0ミリ秒、500ミリ秒、1000ミリ秒としているアニメーションで、
例えばアニメーション再生開始から500ミリ秒までは、0ミリ秒と500ミリ秒のキーフレームで姿勢を補間します。
これが、アニメーション再生開始から600ミリ秒経つと、500ミリ秒と1000ミリ秒のキーフレームで姿勢を補間するので、
計算するキーフレームの対象を変えなければなりません。 キーフレームの対象を変えるのがChangeKeyFrame()です。
このメソッドは「キーフレームの対象を変えるべきかどうか」も判断するため、
1回の描画毎に呼び出していれば大丈夫です。
----------------------------------------


cCam、cKeybrd等はカメラの位置やキーボードの入力キー判別等を行う別クラスオブジェクトです。
ここでは割愛させていただきます。
キャラを動かしながらアニメーションをさせる手順としては、

1 SetAnimationPlayingFlag()を実行する。
2 キャラクターを移動させる。(glTranslatef()等を使って)
3 playOJPAnimationOneFrame()を呼び出して1フレーム分のアニメーションをする。
4 playOJPAnimationOneFrame()の実行が終わるのにかかった時間を計算する。
5 SetAppointedTime()の引数にかかった時間を与える。
6 ChangeKeyFrame()を実行する。

です。

playOJPAnimationOneFrame()を実行した後は、
timeGetTime()で現在時間(ミリ秒)を取得し、WM_APP+1イベント開始前の時間との差を算出します。
ONE_FRAME_TIMEよりも小さい場合は、ONE_FRAME_TIMEミリ秒かかったものとしてtimeSumに格納します。
ONE_FRAME_TIME以上の時間がかかった場合はオーバーした時間を計算してtimeSumに格納します。
SetAppointedTime()にtimeSumを渡すことで、次にアニメーションさせるフレームが指定されます。
最後にChangeKeyFrame()を実行して、アニメーションの一連処理は終了です。


単純に、BrightObjectで作ったままのアニメーションを再生するために、
playOJPAnimation()というものもあります。

----------------------------------------
playOJPAnimation(HDC hdc, GLsizei width, GLsizei height)
引数:デバイスコンテキスト、ウインドウサイズ横、ウインドウサイズ縦
動作:アニメーションの最初から最後まで一通り再生する。
----------------------------------------

このメソッドを使う場合は、

1 SetAnimationPlayingFlag()を実行する。
2 playOJPAnimation()を呼び出してアニメーション再生する。

です。


【2011/09/03 機能追加】
全ソースコード例を変更し、LibBrightObject.libを使って簡易的なシャドウマッピングができるようにしました。
LibBrightObject.libを使って簡易的なシャドウマッピングを行う際には、WM_CREATEで以下のようにしていします。
//##シャドウマッピングの準備

cOjp.SetShadowDiffuse(0.0, 0.0, 0.0, 0.3);    //影の拡散光をセット
cOjp.SetShadowSpecular(0.0, 0.0, 0.0, 1.0);      //影の鏡面光をセット

//シャドウマッピング用光源の位置ベクトルを設定
Vector sdwLight;
sdwLight.x = 0.0;
sdwLight.y = 1.0;
sdwLight.z = 0.5;
cOjp.SetEasyShadowLightPos(sdwLight);

//基準ジョイントの位置をTLOクラス側へ渡す(これが無いと停止時に影が上手く作られなくなる)
cOjp.GetBasicJointPosUseShadowMapToTLO();
//シャドウマップを行うように設定
cOjp.SetEasyShadowMappingFlag(TRUE);

cOjp.SetShadowDiffuse:OpenGL上で影の拡散光を与えています。
cOjp.SetShadowSpecular:OpenGL上で影の鏡面光を与えています。

cOjp.SetEasyShadowLightPos(sdwLight);で、Vector構造体を与えています。
与える内容は「影を作るための光源の位置」を与えています。

cOjp.GetBasicJointPosUseShadowMapToTLO();と、
cOjp.SetEasyShadowMappingFlag(TRUE);についてですが、
cOjp.SetEasyShadowMappingFlag(TRUE);でシャドウマッピングを行うかどうかを決定します。
cOjp.GetBasicJointPosUseShadowMapToTLO();は、アニメーションを行わない(停止している)場合にも影を移すためのメソッドです。
停止姿勢でも影をつけたい場合はこのメソッドを使用します。


【2012/11/28 GLSL対応】
GLSLが使えるようになりました。
とは言っても、GLSLソースに値を渡すようなものには対応していません。
トゥーンシェーディングを行うために実装したものです。
WM_CREATEで以下のように書きます。
                    //## GLSL ##
                    //GLSLの初期化
                    cOjp.initUseGlslClass();

                    //トゥーンシェーディングを行うための設定
                    //GLSLソースファイルを指定
                    cOjp.filePathVert("D:/Programming/toon.vert");
                    cOjp.filePathFrag("D:/Programming/toon.frag");

                    //シェーダプログラムのコンパイル・リンク
                    cOjp.CreateShaderProgram();

                    //OpenGLのTEXTURE1(テクスチャユニットの1番)にトゥーンシェーディング用のbmpファイルを読み込む
                    cOjp.LoadToonTexture_TEXTURE1("D:/Programming/toon.bmp");

○参考サイト
フレームレートを固定化したゲームループの作り方
スタティックライブラリリンク時の注意
inserted by FC2 system