前々回GLUTをクラス化したCGLUT.h、前回はGlut_BCCの宣言部分をカバーしましたので、今回は肝心なGlut_BCCProc.hとGlut_CBFuntion.hをザーッと解説します。(深く読むと疲れると思うので、青字当たりをザーと読み流すことをお勧めします。)

 

【Glut_BCCProc.h】

//////////////////////////////////////////
// Glut_BCCProc.h
// Copyright (c) 10/26/2021 by BCCSkelton
//////////////////////////////////////////

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//ウィンドウメッセージ関数
/////////////////////////////////

bool CMyWnd::OnInit(WPARAM wParam, LPARAM lParam) {

    //IDC_SELOBJの初期化(解説:18の基本図形選択用ドロップダウンリストです。)
    for(int i = 0; i < 18; i++)
        SendItemMsg(IDC_SELOBJ, CB_ADDSTRING, 0, (LPARAM)g_ObjName[i]);
    SendItemMsg(IDC_SELOBJ, CB_SETCURSEL, 0, (LPARAM)0);
    //IDC_SELPROJの初期化(解説:投影方法選択用ドロップダウンリストです。)
    for(int i = 0; i < 3; i++)
        SendItemMsg(IDC_SELPROJ, CB_ADDSTRING, 0, (LPARAM)g_ProjName[i]);
    SendItemMsg(IDC_SELPROJ, CB_SETCURSEL, 0, (LPARAM)0);
    //IDC_SELLIGHTの初期化(解説:照光方法選択用ドロップダウンリストです。)
    for(int i = 0; i < 3; i++)
        SendItemMsg(IDC_SELLIGHT, CB_ADDSTRING, 0, (LPARAM)g_LightName[i]);
    SendItemMsg(IDC_SELLIGHT, CB_SETCURSEL, 0, (LPARAM)0);
    //チェックボックスの初期設定(解説:「常時回転」にチェックを入れます。)
    SendItemMsg(IDC_ROTATE, BM_SETCHECK, BST_CHECKED, (LPARAM)0);
    //スライダーの範囲設定(解説:「回転角」スライダー関連です。)
    SendItemMsg(IDC_ANGLE, TBM_SETRANGE, TRUE, (LPARAM)MAKELONG(0, 360));
    //ページサイズ(マウスクリック時の移動量)設定(解説:同上)
    SendItemMsg(IDC_ANGLE, TBM_SETPAGESIZE, 0, (LPARAM)30);
    //ラインサイズ(矢印キーによる移動量)設定(解説:同上)
    SendItemMsg(IDC_ANGLE, TBM_SETLINESIZE, 0, (LPARAM)10);
    //ポジションの設定(解説:同上)
    SendItemMsg(IDC_ANGLE, TBM_SETPOS, TRUE, (LPARAM)0);
    //目盛りサイズの設定(解説:同上)
    SendItemMsg(IDC_ANGLE, TBM_SETTICFREQ, 10, 0);
    return TRUE;
}

bool CMyWnd::OnHScroll(WPARAM wParam, LPARAM lParam) {    //解説:スライダーメッセージを取得します。

    if(!g_rotate && (HWND)lParam == GetDlgItem(m_hWnd, IDC_ANGLE)) {    //常時回転の場合、何もしない
        if(LOWORD(wParam) == TB_THUMBPOSITION || LOWORD(wParam) == TB_THUMBTRACK)
            g_r = (GLdouble)HIWORD(wParam);
        else
            g_r = (GLdouble)SendItemMsg(IDC_ANGLE, TBM_GETPOS, 0, (LPARAM)0);

            //解説:スライダーからのメッセージでGlut_BCC.hにある外部変数(g_r)に回転角をセットします。
    }
    return FALSE;    //アプリケーションでこのメッセージを処理する場合は、0 を返す必要があります。(MSDN)
}

bool CMyWnd::OnClose(WPARAM wParam, LPARAM lParam) {

    if(MessageBox(m_hWnd, "終了しますか", "終了確認",
                    MB_YESNO | MB_ICONINFORMATION) == IDYES)
        //処理をするとDestroyWindow、PostQuitMessageが呼ばれる
        return TRUE;
    else
        //そうでなければウィンドウではDefWindowProc関数をreturn、ダイアログではreturn FALSEとなる。
        return FALSE;
}

//ダイアログベースの場合はこれが必要
bool CMyWnd::OnDestroy(WPARAM wPram, LPARAM lParam) {

    PostQuitMessage(0);
    return TRUE;
}

/////////////////////////////////
//主ウィンドウCMyWndの関数の定義
//メニュー項目、コントロール関数
/////////////////////////////////

bool CMyWnd::OnSelected(WPARAM wParam) {

    if(HIWORD(wParam) == CBN_SELCHANGE) {    //アイテムが選択された場合
        //IDC_SELOBJの選択内容を確認し、glにセットする
        int sel = SendItemMsg(IDC_SELOBJ, CB_GETCURSEL, 0, (LPARAM)0);
        gl.m_Obj = sel / 2;
        gl.m_sw = (sel % 2 == 1);    //Solid(TRUE)か否か(WireはFALSE)
        return TRUE;
        //解説:18の基本図形はソリッドとワイアー図形の9つのセットになっており、先ず0-8で図形を判断し、奇数か偶数かでソリッドとワイアーの区別をつけています。

    }
    else
        return FALSE;
}

bool CMyWnd::OnSelCol() {    //解説:カラー選択ダイアログを呼び出し、RGB要素を外部変数で記録します。

    CMNDLG cmndlg;
    COLORREF col = cmndlg.GetColor(m_hWnd);
    if(col) {
        g_Red = (GLdouble)GetRValue(col) / 255.0;
        g_Green = (GLdouble)GetGValue(col) / 255.0;
        g_Blue = (GLdouble)GetBValue(col) / 255.0;
        return TRUE;
    }
    else
        return FALSE;
}

bool CMyWnd::OnCreate() {

    //テキストボックスの状態を変更する
    for(int i = 0; i < 9; i++)
        EnableWindow(GetDlgItem(m_hWnd, IDC_EYEX + i), TRUE);
    for(int i = 0; i < 4; i++)
        EnableWindow(GetDlgItem(m_hWnd, IDC_FOV + i), TRUE);
    //コンボボックスの状態を変更する
    EnableWindow(GetDlgItem(m_hWnd, IDC_SELPROJ), TRUE);
    //ラジオボタンの状態を変更する
    EnableWindow(GetDlgItem(m_hWnd, IDC_ROTATE), TRUE);
    EnableWindow(GetDlgItem(m_hWnd, IDC_LIGHT), TRUE);
    //プッシュボタンの状態を変更する
    EnableWindow(GetDlgItem(m_hWnd, IDC_CREATE), FALSE);
    EnableWindow(GetDlgItem(m_hWnd, IDC_SETLA), TRUE);
    EnableWindow(GetDlgItem(m_hWnd, IDC_SETPROJ), TRUE);

    //解説:↑はGLUTウィンドウが作られる際に初期状態で無効なコントロールを有効にします。
    //解説:↓はGLUTでmain()関数で行うGLUTの初期化を行い、GLUTウィンドウを生成します。

    //CGLUTクラスインスタンスの初期設定
    gl.SetPos(100, 100);                    //CGLUTの初期値
    gl.SetSize(640, 480);                    //CGLUTの初期値
    gl.SetTitle("Glut Window");                //GLUTウィンドウタイトル
    //CGLUTクラスインスタンスの生成
    gl.Create();                            //引数でウィンドウタイトル設定可
     glClearColor(0.0, 0.0, 0.0, 0.0);        //黒で画面クリア
    glShadeModel(GL_SMOOTH);                //既定値の滑らかな網かけ(他の選択肢としてフラットシェーディング-GL_FLATがある)
    glEnable(GL_DEPTH_TEST);                //描画オブジェクトの陰面消去を行う為の深度処理
    gl.SetMode(GLUT_DOUBLE | GLUT_RGBA);    //CGLUTの初期値(ダブルバッファリング vs. GLUT_SINGLE)
      //CGLUTコールバック関数の設定
    gl.Idle(gl_Idle);
    gl.Display(gl_Display);
    gl.Reshape(gl_Resize);
    gl.Loop();
    return TRUE;
}

bool CMyWnd::OnSetLA() {    //解説:gluLookAt関数(gl.SetLA)を使って視点の設定を行います。

    GLdouble val[9];
    for(int i = 0; i < 9; i++)
        val[i] = GetDouble(IDC_EYEX + i);
    gl.SetLA(    val[0], val[1], val[2],        //視点のポイント            (GLdouble) eyex, eyey, eyez,
                val[3], val[4], val[5],        //クリップシーンの中心点    (GLdouble) centerx, centery, centerz,
                val[6], val[7], val[8]);    //アップベクター            (GLdouble) upx, upy, upz
    gl.Refresh();    //解説:再描画要求をします。
    return TRUE;
}

bool CMyWnd::OnSelProj(WPARAM wParam) {    //解説:投影方法の選択時にその投影方法のパラメーターを入力するコントロールを有効化させ、他を無効化させます。

    if(HIWORD(wParam) == CBN_SELCHANGE) {    //アイテムが選択された場合
        //IDC_SELPROJの選択内容を確認し、glにセットする
        gl.m_proj = SendItemMsg(IDC_SELPROJ, CB_GETCURSEL, 0, (LPARAM)0);    //投影法式変更
        SetControlState(gl.m_proj);            //投影法式に合わせたコントロールを有効化
        return TRUE;
    }
    else
        return FALSE;
}

bool CMyWnd::OnSetProj() {    //解説:「投影設定」ボタンが押されると、必要なデータをコントロールから読み取り、選択された投影方法に応じてCGLUTクラスインスタンス(gl)の投影メソッドを再描画要求を出して実行します。

    switch(gl.m_proj) {
    default:
    case 0:
        //gluPerspective関数
        gl.m_fov = GetDouble(IDC_FOV);            //視野(Fiield of view)で単位は度
        gl.m_aspect = GetDouble(IDC_ASPECT);    //縦横比(幅 / 高さ)
        gl.m_near = GetDouble(IDC_NEAR);        //視点から近クリップ平面までの距離(正)
        gl.m_far = GetDouble(IDC_FAR);            //視点から遠クリップ平面までの距離(正)
        break;
    case 1:
        //glFrustum関数
        gl.m_left = GetDouble(IDC_LEFT);        //クリップ平面左座標
        gl.m_right = GetDouble(IDC_RIGHT);        //クリップ平面右座標
        gl.m_bottom = GetDouble(IDC_BOTTOM);    //クリップ平面下座標
        gl.m_top = GetDouble(IDC_TOP);            //クリップ平面上座標
        gl.m_near = GetDouble(IDC_NEAR);        //視点から近クリップ平面までの距離(正)
        gl.m_far = GetDouble(IDC_FAR);            //視点から遠クリップ平面までの距離(正)
        break;
    case 2:
        //glOrtho関数
        gl.m_left = GetDouble(IDC_LEFT);        //クリップ平面左座標
        gl.m_right = GetDouble(IDC_RIGHT);        //クリップ平面右座標
        gl.m_bottom = GetDouble(IDC_BOTTOM);    //クリップ平面下座標
        gl.m_top = GetDouble(IDC_TOP);            //クリップ平面上座標
        gl.m_front = GetDouble(IDC_BACK);        //手前クリップ平面座標
        gl.m_back = GetDouble(IDC_FRONT);        //奥クリップ平面座標
        break;
    }
    gl.Refresh();    //解説:上ではデータのセットだけ行い、投影方法の設定はこのメソッドで行う。
    return TRUE;
}

bool CMyWnd::OnRotate() {

    if(SendItemMsg(IDC_ROTATE, BM_GETCHECK, 0, (LPARAM)0) == BST_CHECKED) {    //チェックボックスの状態確認 (解説:「常時回転」のチェックボックスが付いていれば外し、回転フラグ(g_rotate)も偽にし、データ入力コントロールを有効化します。)
        SendItemMsg(IDC_ROTATE, BM_SETCHECK, BST_UNCHECKED, (LPARAM)0);
        g_rotate = FALSE;
        //テキストボックスを有効化する
        EnableWindow(GetDlgItem(m_hWnd, IDC_ENDX), TRUE);
        EnableWindow(GetDlgItem(m_hWnd, IDC_ENDY), TRUE);
        EnableWindow(GetDlgItem(m_hWnd, IDC_ENDZ), TRUE);
        //スライダーを有効化する
        EnableWindow(GetDlgItem(m_hWnd, IDC_ANGLE), TRUE);
    }
    else {    //解説:逆に「常時回転」のチェックが外れていれば、チェックをつけ、フラグを立ててデータ入力を不可にします。
        SendItemMsg(IDC_ROTATE, BM_SETCHECK, BST_CHECKED, (LPARAM)0);
        g_rotate = TRUE;
        //テキストボックスを無効化する
        EnableWindow(GetDlgItem(m_hWnd, IDC_ENDX), FALSE);
        EnableWindow(GetDlgItem(m_hWnd, IDC_ENDY), FALSE);
        EnableWindow(GetDlgItem(m_hWnd, IDC_ENDZ), FALSE);
        //スライダーを無効化する
        EnableWindow(GetDlgItem(m_hWnd, IDC_ANGLE), FALSE);
    }
    return TRUE;
}

bool CMyWnd::OnEndXChanged(WPARAM wParam) {    //解説:テキストボックスからデータを取得して、倍精度実数に変えて外部変数にセットします。

    if(!g_rotate && HIWORD(wParam) == EN_CHANGE) {    //常時回転以外で、データが変更された場合
        g_x = GetDouble(IDC_ENDX);
        return TRUE;
    }
    else
        return FALSE;
}

bool CMyWnd::OnEndYChanged(WPARAM wParam) {    //解説:同上

    if(!g_rotate && HIWORD(wParam) == EN_CHANGE) {    //常時回転以外で、データが変更された場合
        g_y = GetDouble(IDC_ENDY);
        return TRUE;
    }
    else
        return FALSE;
}

bool CMyWnd::OnEndZChanged(WPARAM wParam) {    //解説:同上

    if(!g_rotate && HIWORD(wParam) == EN_CHANGE) {    //常時回転以外で、データが変更された場合
        g_z = GetDouble(IDC_ENDZ);
        return TRUE;
    }
    else
        return FALSE;
}

bool CMyWnd::OnLight() {    //解説:「照光効果」チェックボックスを確認し、入力を可、不可に制御します。

    if(SendItemMsg(IDC_LIGHT, BM_GETCHECK, 0, (LPARAM)0) == BST_UNCHECKED) {    //チェックボックスの状態確認
        SendItemMsg(IDC_LIGHT, BM_SETCHECK, BST_CHECKED, (LPARAM)0);
        g_light = TRUE;    //解説:「照光効果」フラグを真にします。
        //「色」ボタンを無効化する(ダイアログの上にある色選択ボタンです。)
        EnableWindow(GetDlgItem(m_hWnd, IDC_COLOR), FALSE);
        //テキストボックスを有効化する
        EnableWindow(GetDlgItem(m_hWnd, IDC_LIGHTX), TRUE);
        EnableWindow(GetDlgItem(m_hWnd, IDC_LIGHTY), TRUE);
        EnableWindow(GetDlgItem(m_hWnd, IDC_LIGHTZ), TRUE);
        //コンボボックスを有効化する
        EnableWindow(GetDlgItem(m_hWnd, IDC_SELLIGHT), TRUE);
        //光源を有効にする
        gl.Enable(GL_LIGHTING);
        //選択された光源(光源は8個まで設定可能)を有効にする
        gl.Enable(g_lightno[g_selected]);
        //物体の質感を有効にする設定を解除する
        gl.Disable(GL_COLOR_MATERIAL);
    }
    else {    //解説:既に有効であった場合には無効に変えます。
        SendItemMsg(IDC_LIGHT, BM_SETCHECK, BST_UNCHECKED, (LPARAM)0);
        g_light = FALSE;
        //「色」ボタンを有効化する
        EnableWindow(GetDlgItem(m_hWnd, IDC_COLOR), TRUE);
        //テキストボックスを無効化する
        EnableWindow(GetDlgItem(m_hWnd, IDC_LIGHTX), FALSE);
        EnableWindow(GetDlgItem(m_hWnd, IDC_LIGHTY), FALSE);
        EnableWindow(GetDlgItem(m_hWnd, IDC_LIGHTZ), FALSE);
        //コンボボックスを無効化する
        EnableWindow(GetDlgItem(m_hWnd, IDC_SELLIGHT), FALSE);
        //光源を無効にする
        gl.Disable(GL_LIGHTING);
        //現在選択されている光源を無効にする
        gl.Disable(g_lightno[g_selected]);
        //物体の質感を有効にする設定
        gl.Enable(GL_COLOR_MATERIAL);
    }
    return TRUE;
}

bool CMyWnd::OnLightXChanged(WPARAM wParam) {    //解説:テキストボックスからデータを取得して、倍精度実数に変えて外部変数にセットします。

    if(g_light && HIWORD(wParam) == EN_CHANGE) {    //照光効果が有効で、データが変更された場合
        g_lightpos[0] = GetDouble(IDC_LIGHTX);
        gl_Display();    //解説:描画関数を呼び出します。
        return TRUE;
    }
    else
        return FALSE;
}

bool CMyWnd::OnLightYChanged(WPARAM wParam) {    //解説:同上

    if(g_light && HIWORD(wParam) == EN_CHANGE) {    //照光効果が有効で、データが変更された場合
        g_lightpos[1] = GetDouble(IDC_LIGHTY);
        gl_Display();
        return TRUE;
    }
    else
        return FALSE;
}

bool CMyWnd::OnLightZChanged(WPARAM wParam) {    //解説:同上

    if(g_light && HIWORD(wParam) == EN_CHANGE) {    //照光効果が有効で、データが変更された場合
        g_lightpos[2] = GetDouble(IDC_LIGHTZ);
        gl_Display();
        return TRUE;
    }
    else
        return FALSE;
}

bool CMyWnd::OnSelLight(WPARAM wParam) {    //解説:照光方法選択ドロップダウンリストの選択処理です。

    if(HIWORD(wParam) == CBN_SELCHANGE) {    //アイテムが選択された場合
        //現在選択されている光源を無効にする
        gl.Disable(g_lightno[g_selected]);
        //IDC_SELLIGHTの選択内容を確認し、glにセットする
        g_selected = SendItemMsg(IDC_SELLIGHT, CB_GETCURSEL, 0, (LPARAM)0);    //投影法式変更
        //選択された光源を有効にする
        gl.Enable(g_lightno[g_selected]);
        gl_Display();
        return TRUE;
    }
    else
        return FALSE;
}

bool CMyWnd::OnIdok() {

    SendMsg(WM_CLOSE, 0, 0);
    return TRUE;
}

///////////////////
//ユーザー定義関数
///////////////////
//テキストボックスからGLDouble値を取得する

GLdouble CMyWnd::GetDouble(UINT id) {    //解説:テキストボックスのコントロールIDで値を返すように汎用化しました。

    char buff[MAX_PATH];
    SendItemMsg(id, WM_GETTEXT, MAX_PATH, (LPARAM)buff);
    double res = strtod(buff, NULL);    //変換に失敗した場合は0.0を返す筈
    return (GLdouble)res;
}

void CMyWnd::SetControlState(int pattern = 0) {    //解説:3つのパターンで10個のコントロールの有効、無効を変更します。このような「小技」がプログラミングの妙味ですね。

    bool tof[10] = {FALSE};
    switch(pattern) {
    case 0:
        tof[0] = tof[1] = tof[2] = tof[3] = TRUE;
        break;
    case 1:
        tof[2] = tof[3] = tof[4] = tof[5] = tof[6] = tof[7] = TRUE;
        break;
    case 2:
        tof[4] = tof[5] = tof[6] = tof[7] = tof[8] = tof[9] = TRUE;
        break;
    default:
        break;
    }
    EnableWindow(GetDlgItem(m_hWnd, IDC_FOV), tof[0]);
    EnableWindow(GetDlgItem(m_hWnd, IDC_ASPECT), tof[1]);
    EnableWindow(GetDlgItem(m_hWnd, IDC_NEAR), tof[2]);
    EnableWindow(GetDlgItem(m_hWnd, IDC_FAR), tof[3]);
    EnableWindow(GetDlgItem(m_hWnd, IDC_LEFT), tof[4]);
    EnableWindow(GetDlgItem(m_hWnd, IDC_RIGHT), tof[5]);
    EnableWindow(GetDlgItem(m_hWnd, IDC_BOTTOM), tof[6]);
    EnableWindow(GetDlgItem(m_hWnd, IDC_TOP), tof[7]);
    EnableWindow(GetDlgItem(m_hWnd, IDC_BACK), tof[8]);
    EnableWindow(GetDlgItem(m_hWnd, IDC_FRONT), tof[9]);
}

 

【Glut_CBFuntion.h】

/////////////////////
//GLUT Callback 関数
/////////////////////
////////////
//gl.Idle()
////////////

void gl_Idle() {    //解説:glutIdleFuncで設定された、GLUTのお作法にのっとったメッセージ処理がない場合のコールバック関数です。

    gl.ReDisp();        //再描画命令(解説:glPostDisplay関数を呼びます。)
}

///////////////
//gl.Display()
///////////////

void gl_Display() {    //解説:glutDisplayFuncで設定された、GLUTのお作法にのっとったモデルビューモードでのプリミティブ描画関数です。

    const static GLfloat material_ambient[4] = {0.24725, 0.1995, 0.0745, 1.0};        //質感設定(初期値-0.2 , 0.2 , 0.2 , 1)
    const static GLfloat material_diffuse[4] = {0.75164, 0.60648, 0.22648, 1.0};    //質感設定(初期値-0.8 , 0.8 , 0.8 , 1)
    const static GLfloat material_specular[4] = {0.24725, 0.1995, 0.0745, 1.0};        //質感設定(初期値-0 , 0 , 0, 1)
    const static GLfloat material_shininess = 48;
    //解説:上記は私のオリジナルではなく、WEBで見つけた「金色」表現のデータです。


    gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);    //色、深度データを初期化し、ウィンドウを消去する
    glMatrixMode(GL_MODELVIEW);        //描画コマンド設定(https://learn.microsoft.com/ja-jp/windows/win32/opengl/glmatrixmode)
    glLoadIdentity();                //モデルビューの変換行列の初期化

    if(g_light) {    //解説:「照光」効果が有効な場合の処理です。ライト0~2に異なる照光方法をセットしています。
        glLightfv(g_lightno[g_selected], GL_POSITION, g_lightpos);                        //光源の位置を設定
        glLightfv(g_lightno[g_selected], g_kind[g_selected], g_lightcolor[g_selected]);    //RGBA強度設定
    }
    else {    //解説:照光効果がある場合の処理はglColor関数を実行しても無視されるそうです。
        gl.Color(g_Red, g_Green, g_Blue, g_Alpha);    //色の設定(R, G, B, A = 1.0)
    }

    glPushMatrix();
        gl.Rotate(g_r, g_x, g_y, g_z);
        if(g_light) {    //解説:照光効果がある場合、表面の質感を設定しています。
            glMaterialfv(GL_FRONT, GL_AMBIENT, material_ambient);
            glMaterialfv(GL_FRONT, GL_SPECULAR, material_specular);
            glMaterialfv(GL_FRONT, GL_DIFFUSE, material_diffuse);
            glMaterialf(GL_FRONT, GL_SHININESS, material_shininess);
        }
        gl.DrawObject();            //3Dオブジェクトの描画(解説:18の基本図形から選択されたプリミティブを描画します。)
    glPopMatrix();
    glFlush();                        //バッファー コマンド実行と開放
    glutSwapBuffers();                //バッファを交換
    if(g_rotate) {                    //常時回転フラグ(解説:「常時回転」フラグでコントロールしています。)
        g_r += 0.1;
        if(g_r >= 360.0)
            g_r = 0.0;
    }
}

///////////////
//gl.Resize()
///////////////

void gl_Resize(int w, int h) {    //解説:glutReshapeFuncで設定されたウィンドウのサイズ変更時のコールバック関数です。

    gl.Resize(w, h);    //解説:定番のビューポートの設定や投影設定を行います。
}

 

以上でGlut_BCCのソースファイルの解説は終了します。(実に久々のBCCSkeltonプログラムでした。)次回はこのGlut_BCCシリーズの最終回として「Glut_BCCで遊んでみた!」をやってみようかと思います。