(字数オーバーとなった) 前回 からの続き。
////////////////////////
//GLControlのLoad時処理
////////////////////////
private void glControl_Load(object sender, EventArgs e)
{
GL.ClearColor(glControl.BackColor); //画面消去色の設定
GL.Enable(EnableCap.DepthTest); //深度バッファの使用
GL.Enable(EnableCap.Normalize); //法線正規化
}
//////////////////////////////
//GLControlのサイズ変更時処理
//////////////////////////////
private void glControl_Resize(object sender, EventArgs e)
{
glControl.Refresh(); //再描画(解説:描画処理で種々の設定も更新するようにしています。)
}
/////////////////////////////
//GLControlの描画処理
//ここに図形の描画処理を配置
/////////////////////////////
private void glControl_Paint(object sender, PaintEventArgs e)
{
//色と深度バッファーを初期化
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
//表示設定
ChangeView();
//ライトの設定
glControl_SetLight();
//以下に描画処理を行う
double Ix = (double)(mzWhereIam.X - mzWidth / 2);
double Iy = (double)(mzWhereIam.Y - mzHeight / 2);
if(!mzIs3D) //鳥瞰表示の場合のみ表示
{
GL.PushMatrix();
{
//質感の設定(解説:これは「太陽、地球、月の回転」サンプルプログラムの太陽と同じです。)
//鏡面光成分のセット
GL.Material(MaterialFace.Front, MaterialParameter.Specular, Color4.White);
GL.Material(MaterialFace.Front, MaterialParameter.Shininess, 32.0f);
GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color4.OrangeRed);
GL.Translate(Ix, Iy, 0.0);
//球を描画
Sphere(true, 0.4, 36);
}
GL.PopMatrix();
}
//解説:以下に柱(壁)をXY平面上にZ軸に沿って描画します。
for(int y = 0; y < mzHeight; y++)
{
for(int x = 0; x < mzWidth; x++)
{
double xx = (double)(x - mzWidth / 2);
double yy = (double)(y - mzHeight / 2);
if(MazeData[x, y] == 1)
{
GL.PushMatrix();
{
//質感の設定
GL.Material(MaterialFace.Front, MaterialParameter.Diffuse, Color4.Yellow);
GL.Translate(xx, yy, 0.0);
//柱を描画
Cube(true, 1.0, 1.0, 2.5); //解説:柱の中心が(0,0,0)となる幅1.0 X 奥行1.0 X 高さ1.0の直方体です。
}
GL.PopMatrix();
}
}
}
//バッファー変換
glControl.SwapBuffers();
}
///////////////////////////
//迷路内表示・鳥瞰表示切替
///////////////////////////
private void ChangeView()
{
if(mzIs3D) //trueの場合、迷路内を表示
{
//視体積の設定(迷路探索者の位置前から迷路長)
glControl_SetProjection(1.0f, 41.0f);
//視点座標(迷路探索者の位置-壁面中央の高さ(Z)が0なのでZを0とする-Z軸がUp)
pers = new Vector3((float)(mzWhereIam.X - mzWidth / 2), (float)(mzWhereIam.Y - mzHeight / 2), 0.0f);
switch (mzWhereToGo) //絶対方向(mzWhereToGo)に基づく方向ベクトル
{
case 0: target = new Vector3(0.0f, 1.0f, 0.0f); break;
case 1: target = new Vector3(1.0f, 0.0f, 0.0f); break;
case 2: target = new Vector3(0.0f, -1.0f, 0.0f); break;
case 3: target = new Vector3(-1.0f, 0.0f, 0.0f); break;
default: //発生しないが...
MessageBox.Show("方向が異常です。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
//注視点の設定(迷路探索者の位置と方向ベクトルの和)
target += pers;
//モデルビューの設定
glControl_SetModelView();
}
else //falseの場合、鳥瞰表示
{
//視体積の設定(デフォルトのzNear = 10.0f、zFar = 64.0f)
glControl_SetProjection();
//視点座標設定
pers = new Vector3(0.0f, 0.0f, 50.0f); //迷路の中心位置で、Z = 50.0fから垂直に見下ろす(Y軸がUp)
//注視点の設定(target = (0, 0, 0)で迷路中央を見る)
target = new Vector3(0.0f, 0.0f, 0.0f);
//モデルビューの設定
glControl_SetModelView();
}
}
//////////////////////////////
//GLControl上のビューポート、
//視野空間、透視投影設定
//////////////////////////////
private void glControl_SetProjection(float zNear = 10.0f, float zFar = 64.0f)
{
//GLControlのビューポートの設定
GL.Viewport(0, 0, glControl.Size.Width, glControl.Size.Height);
//透視投影視野空間の設定
GL.MatrixMode(MatrixMode.Projection);
//透視投影-尚、glControl.AspectRatioは、(float)glControl.Width / (float)glControl.Heightと等価
Matrix4 projection = Matrix4.CreatePerspectiveFieldOfView((float)Math.PI / 4, glControl.AspectRatio, zNear, zFar);
GL.LoadMatrix(ref projection);
}
////////////////////////////////
//GLControl上のモデルビュー設定
////////////////////////////////
void glControl_SetModelView()
{
//モデルビューを選択
GL.MatrixMode(MatrixMode.Modelview);
//視点をpersで設定してtargetを見る
Matrix4 modelview;
if(mzIs3D) //迷路内表示
modelview = Matrix4.LookAt(pers, target, Vector3.UnitZ); //迷路を水平に見るのでZ軸を上(Up)にする
else //鳥瞰表示
modelview = Matrix4.LookAt(pers, target, Vector3.UnitY); //迷路を垂直に見下ろすのでY軸を上(Up)にする
GL.LoadMatrix(ref modelview); //現在の変換マトリックスに設定
}
////////////////////////
//GLControl上の光源設定
////////////////////////
void glControl_SetLight()
{
//光源の使用
GL.Enable(EnableCap.Lighting);
//ライト0の設定
GL.Enable(EnableCap.Light0);
//Chat-GPT推奨の「ちょっと盛り」
float[] amb = {0.35f,0.35f,0.35f,1.0f};
GL.Light(LightName.Light0, LightParameter.Ambient, amb);
float[] pos0 = new float[] {lPos0.X, lPos0.Y, lPos0.Z, 0.0f };
GL.Light(LightName.Light0, LightParameter.Position, pos0);
// //ライト1の設定 (解説:迷路内の証明問題でライトを一つ増やしましたが...)
// GL.Enable(EnableCap.Light1);
// float[] pos1 = new float[] {lPos1.X, lPos1.Y, lPos1.Z, 0.0f };
// GL.Light(LightName.Light1, LightParameter.Position, pos1);
}
//解説:以下は昔作ったOpenGL_Primitives.dllのソースから借用
/////////////////////////////////////////////////////////////
//立(直)方体(引数:ソリッドまたはワイアー、横、縦、高さ)
/////////////////////////////////////////////////////////////
public void Cube(bool SorW = true, double hor = 1.0, double ver = 1.0, double dep = 1.0)
{
//立(直)方体データ
double[][] Qvertex = new double[8][]; //四角形の8頂点(奥左下から反時計回り4点、手前左下から反時計回り4点)
Qvertex[0] = new double[3] {-hor / 2, -ver / 2, -dep / 2};
Qvertex[1] = new double[3] { hor / 2, -ver / 2, -dep / 2};
Qvertex[2] = new double[3] { hor / 2, ver / 2, -dep / 2};
Qvertex[3] = new double[3] {-hor / 2, ver / 2, -dep / 2};
Qvertex[4] = new double[3] {-hor / 2, -ver / 2, dep / 2};
Qvertex[5] = new double[3] { hor / 2, -ver / 2, dep / 2};
Qvertex[6] = new double[3] { hor / 2, ver / 2, dep / 2};
Qvertex[7] = new double[3] {-hor / 2, ver / 2, dep / 2};
int[][] Qedge = new int[12][]; //頂点を結ぶ12線分
Qedge[0] = new int[2] {0, 1};
Qedge[1] = new int[2] {1, 2};
Qedge[2] = new int[2] {2, 3};
Qedge[3] = new int[2] {3, 0};
Qedge[4] = new int[2] {4, 5};
Qedge[5] = new int[2] {5, 6};
Qedge[6] = new int[2] {6, 7};
Qedge[7] = new int[2] {7, 4};
Qedge[8] = new int[2] {0, 4};
Qedge[9] = new int[2] {1, 5};
Qedge[10] = new int[2] {2, 6};
Qedge[11] = new int[2] {3, 7};
int[][] Qface = new int[6][]; //四角形6面
Qface[0] = new int[4] {0, 1, 2, 3}; //奥面
Qface[1] = new int[4] {1, 5, 6, 2}; //右面
Qface[2] = new int[4] {5, 4, 7, 6}; //手前面
Qface[3] = new int[4] {4, 0, 3, 7}; //左面
Qface[4] = new int[4] {4, 5, 1, 0}; //底面
Qface[5] = new int[4] {3, 2, 6, 7}; //天面
Vector3[] vecNormal = new Vector3[6] {-Vector3.UnitZ, Vector3.UnitX, Vector3.UnitZ, -Vector3.UnitX, -Vector3.UnitY, Vector3.UnitY};
//立(直)方体描画
GL.PushMatrix();
if(SorW)
{
GL.Begin(PrimitiveType.Quads); //四角面を描画(Solid)
for(int j = 0; j < 6; j++)
{
GL.Normal3(vecNormal[j]); //法線の設定
for(int i = 0; i < 4; i++)
GL.Vertex3(Qvertex[Qface[j][i]][0], Qvertex[Qface[j][i]][1], Qvertex[Qface[j][i]][2]);
}
GL.End();
}
else
{
GL.Begin(PrimitiveType.Lines); //四角面を描画(Wire)
for(int j = 0; j < 12; j++)
{
for(int i = 0; i < 2; i++)
GL.Vertex3(Qvertex[Qedge[j][i]][0], Qvertex[Qedge[j][i]][1], Qvertex[Qedge[j][i]][2]);
}
GL.End();
}
GL.PopMatrix();
}
//////////////////////////////////////////////////////////
//球(引数:ソリッドまたはワイアー、半径、360°の分割数)
//////////////////////////////////////////////////////////
public void Sphere(bool SorW = true, double radius = 0.5, int slices = 36)
{
double angle = Math.PI / slices * 2; //angle(radian) = (360 / slices) * (Math.PI / 180);
if(SorW)
GL.Begin(PrimitiveType.TriangleStrip); //球体を描画(Solid)
else
GL.Begin(PrimitiveType.LineStrip); //球体を描画(Wire)
{
for(int j = -slices / 2; j < slices / 2; j++) //slices along Z軸
{
for(int i = 0; i <= slices; i++) //slices around Z軸
{
Vector3d vec1 = new Vector3d(radius * Math.Cos(angle * j) * Math.Cos(angle * i), radius * Math.Cos(angle * j) * Math.Sin(angle * i), radius * Math.Sin(angle * j));
GL.Normal3(Vector3d.Normalize(vec1)); //2026/02/19、正規化がないというChat-GPTの指摘によりGL.Normal3(vec1);から修正
GL.Vertex3(vec1);
Vector3d vec2 = new Vector3d(radius * Math.Cos(angle * (j - 1)) * Math.Cos(angle * i), radius * Math.Cos(angle * (j - 1)) * Math.Sin(angle * i), radius * Math.Sin(angle * (j - 1)));
GL.Normal3(Vector3d.Normalize(vec2)); //2026/02/19、正規化がないというChat-GPTの指摘によりGL.Normal3(vec2);から修正
GL.Vertex3(vec2);
}
}
}
GL.End();
}
//解説:前に作ったMaze.exeのソースから借用
/////////////////////////////
//3Maze_Sample.mazファイル
//の読み込みと迷路の初期化
/////////////////////////////
private bool IniMaze()
{
string data = "";
//ファイル(UTF-8:Encoding.UTF8)を読む
try
{
using(StreamReader file = new StreamReader(mzFileName, Encoding.UTF8))
{
data = file.ReadToEnd();
// ファイルを閉じる
file.Close();
}
}
catch(Exception e) //FileNotFoundException e
{
//Debug用
MessageBox.Show("エラーコード:" + e.ToString(), "初期化ファイル読み込みエラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
//文字列の行分割
string[] lines = data.Split(new[]{"\r\n","\n","\r"},StringSplitOptions.None);
//第1行(迷路の幅と高さ)の語分割
string[] widthheight = lines[0].Split(',');
if(!Int32.TryParse(widthheight[0], out mzWidth))
{
MessageBox.Show("mzWidthの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if(!Int32.TryParse(widthheight[1], out mzHeight))
{
MessageBox.Show("mzHeightの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
//迷路の作成
MazeData = new int[mzWidth, mzHeight];
//第2行(UNIT、mzIs3D、mzWhereToGo)の語分割
string[] others = lines[1].Split(',');
if(!Int32.TryParse(others[0], out UNIT))
{
MessageBox.Show("UNITの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if(!bool.TryParse(others[1], out mzIs3D))
{
MessageBox.Show("mzIs3Dの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if(!Int32.TryParse(others[2], out mzWhereToGo))
{
MessageBox.Show("mzWhereToGoの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
//第3行(迷路探索者の位置)の語分割
string[] where = lines[2].Split(',');
mzWhereIam = new Point();
int x, y; //TryParse用変数
if(Int32.TryParse(where[0], out x))
mzWhereIam.X = x;
else
{
MessageBox.Show("mzWhereIam.Xの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if(Int32.TryParse(where[1], out y))
mzWhereIam.Y = y;
else
{
MessageBox.Show("mzWhereIam.Xの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
//第4行(迷路出口の位置)の語分割
string[] exitpos = lines[3].Split(',');
mzExit = new Point();
if(Int32.TryParse(exitpos[0], out x))
mzExit.X = x;
else
{
MessageBox.Show("mzExit.Xの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
if(Int32.TryParse(exitpos[1], out y))
mzExit.Y = y;
else
{
MessageBox.Show("mzExit.Yの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
//第5行以降(迷路データ)の語分割
for(int i = 0; i < mzHeight; i++)
{
string[] dt = lines[4 + i].Split(',');
for(int j = 0; j < mzWidth; j++)
{
if(!Int32.TryParse(dt[j], out MazeData[i, j]))
{
MessageBox.Show("MazeDataの初期化に失敗しました。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error);
return false;
}
}
}
return true;
}
}
}
//解説:以上で"3DMaze.cs"の解説を終えます。この後、 Chat-GPT君と実りなき論議 を行い、一晩頭を冷やして、Chat-GPT君の言う「ZUp統一」が誤りであること、また迷路の見え方がおかしいことから初期位置(X「0~40」とY「0~40」座標からVector3(1.0, 1.0, 0.0)となっていた)で首を伸ばして視点位置が間違っていたことに気づき、修正(X「-20~20」とY「-20~20」座標でVector3(-19.0, -19.0, 0.0)へ変更)します。(以下は修正後)
そして、本プログラムは大団円を迎えに行きます。