(字数オーバーとなった)前回からの続き。


        ////////////////////////
        //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)へ変更)します。(以下は修正後)

 

 

そして、本プログラムは大団円を迎えに行きます。