前々回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で遊んでみた!」をやってみようかと思います。