OpenTKを使って迷路をつくります。下側の画像は迷路を上から眺めた画像です。
まず迷路を作るためには壁が必要です。壁を描画するメソッドを作成します。
WallDirect.Eastとあるのは壁の面ではなく壁がどちら側に広がっているかを示すものです(WallDirect.Eastなら面は南北方向)。第1引数と第2引数は起点になる点のX座標とZ座標です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
public partial class Form1 : Form { public enum WallDirect { Sorth, East, } void DrawWall(double x, double z, WallDirect direct) { GL.PushMatrix(); { if(direct == WallDirect.East) { GL.Translate(x + 0.5, 0, z); } if(direct == WallDirect.Sorth) { GL.Translate(x, 0, z + 0.5); GL.Rotate(90, Vector3.UnitY); } Vector3 frontLeftTop = new Vector3(-0.5f, 0.5f, 0.05f); Vector3 frontLeftBottom = new Vector3(-0.5f, -0.5f, 0.05f); Vector3 frontRightTop = new Vector3( 0.5f, 0.5f, 0.05f); Vector3 frontRightBottom = new Vector3( 0.5f, -0.5f, 0.05f); Vector3 backLeftTop = new Vector3(-0.5f, 0.5f, -0.05f); Vector3 backLeftBottom = new Vector3(-0.5f, -0.5f, -0.05f); Vector3 backRightTop = new Vector3( 0.5f, 0.5f, -0.05f); Vector3 backRightBottom = new Vector3( 0.5f, -0.5f, -0.05f); GL.Begin(BeginMode.Quads); { GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color.Yellow); { // 表 GL.Normal3(Vector3.UnitZ); GL.Vertex3(frontLeftTop); GL.Vertex3(frontLeftBottom); GL.Vertex3(frontRightBottom); GL.Vertex3(frontRightTop); } { // 裏 GL.Normal3(-Vector3.UnitZ); GL.Vertex3(backLeftTop); GL.Vertex3(backLeftBottom); GL.Vertex3(backRightBottom); GL.Vertex3(backRightTop); } { // 底 GL.Normal3(-Vector3.UnitY); GL.Vertex3(frontLeftBottom); GL.Vertex3(backLeftBottom); GL.Vertex3(backRightBottom); GL.Vertex3(frontRightBottom); } { // 上面 GL.Normal3(Vector3.UnitY); GL.Normal3(-Vector3.UnitY); GL.Vertex3(frontLeftTop); GL.Vertex3(backLeftTop); GL.Vertex3(backRightTop); GL.Vertex3(frontRightTop); } { // 左 GL.Normal3(-Vector3.UnitX); GL.Vertex3(frontLeftTop); GL.Vertex3(backLeftTop); GL.Vertex3(backLeftBottom); GL.Vertex3(frontLeftBottom); } { // 右 GL.Normal3(Vector3.UnitX); GL.Normal3(-Vector3.UnitX); GL.Vertex3(frontRightTop); GL.Vertex3(backRightTop); GL.Vertex3(backRightBottom); GL.Vertex3(frontRightBottom); } } GL.End(); } GL.PopMatrix(); } } |
次に迷路を描画するメソッドを作成します。迷路を作るために必要な壁の座標は自作メソッド GetWallPositionsToEast()とGetWallPositionsToSouth()で取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
public partial class Form1 : Form { void DrawMaze() { List<WallPosition> wallPositionsToEast = GetWallPositionsToEast(); List<WallPosition> wallPositionsToSouth = GetWallPositionsToSouth(); GL.PushMatrix(); { GL.Translate(X, Y, Z); GL.Rotate(RotateX, 1, 0, 0); GL.Rotate(RotateY, 0, 1, 0); GL.Rotate(RotateZ, 0, 0, 1); foreach(WallPosition pos in wallPositionsToEast) DrawWall(pos.X, pos.Z, WallDirect.East); foreach(WallPosition pos in wallPositionsToSouth) DrawWall(pos.X, pos.Z, WallDirect.Sorth); } GL.PopMatrix(); } List<WallPosition> WallPositionsToEast = new List<WallPosition>(); List<WallPosition> WallPositionsToSouth = new List<WallPosition>(); public class WallPosition { public WallPosition(double x, double z) { X = x; Z = z; } public double X { private set; get; } public double Z { private set; get; } } List<WallPosition> GetWallPositionsToEast() { if(WallPositionsToEast.Count == 0) { WallPositionsToEast.Add(new WallPosition(-4.5, -0.5)); WallPositionsToEast.Add(new WallPosition(-3.5, -0.5)); WallPositionsToEast.Add(new WallPosition(-2.5, -0.5)); WallPositionsToEast.Add(new WallPosition(-1.5, -0.5)); WallPositionsToEast.Add(new WallPosition(+0.5, -0.5)); WallPositionsToEast.Add(new WallPosition(+1.5, -0.5)); WallPositionsToEast.Add(new WallPosition(+2.5, -0.5)); WallPositionsToEast.Add(new WallPosition(+3.5, -0.5)); WallPositionsToEast.Add(new WallPosition(-3.5, -1.5)); WallPositionsToEast.Add(new WallPosition(-2.5, -1.5)); WallPositionsToEast.Add(new WallPosition(-0.5, -1.5)); WallPositionsToEast.Add(new WallPosition(+1.5, -1.5)); WallPositionsToEast.Add(new WallPosition(+2.5, -1.5)); WallPositionsToEast.Add(new WallPosition(-4.5, -2.5)); WallPositionsToEast.Add(new WallPosition(-1.5, -2.5)); WallPositionsToEast.Add(new WallPosition(0.5, -2.5)); WallPositionsToEast.Add(new WallPosition(3.5, -2.5)); WallPositionsToEast.Add(new WallPosition(-3.5, -3.5)); WallPositionsToEast.Add(new WallPosition(-1.5, -3.5)); WallPositionsToEast.Add(new WallPosition(0.5, -3.5)); WallPositionsToEast.Add(new WallPosition(2.5, -3.5)); WallPositionsToEast.Add(new WallPosition(-4.5, -6.5)); WallPositionsToEast.Add(new WallPosition(-3.5, -6.5)); WallPositionsToEast.Add(new WallPosition(-2.5, -6.5)); WallPositionsToEast.Add(new WallPosition(-1.5, -6.5)); WallPositionsToEast.Add(new WallPosition(+0.5, -6.5)); WallPositionsToEast.Add(new WallPosition(+1.5, -6.5)); WallPositionsToEast.Add(new WallPosition(+2.5, -6.5)); WallPositionsToEast.Add(new WallPosition(+3.5, -6.5)); } return WallPositionsToEast; } List<WallPosition> GetWallPositionsToSouth() { if(WallPositionsToSouth.Count == 0) { WallPositionsToSouth.Add(new WallPosition(-4.5, -6.5)); WallPositionsToSouth.Add(new WallPosition(-4.5, -5.5)); WallPositionsToSouth.Add(new WallPosition(-4.5, -4.5)); WallPositionsToSouth.Add(new WallPosition(-4.5, -3.5)); WallPositionsToSouth.Add(new WallPosition(-4.5, -2.5)); WallPositionsToSouth.Add(new WallPosition(-4.5, -1.5)); WallPositionsToSouth.Add(new WallPosition(-3.5, -5.5)); WallPositionsToSouth.Add(new WallPosition(-3.5, -4.5)); WallPositionsToSouth.Add(new WallPosition(-2.5, -6.5)); WallPositionsToSouth.Add(new WallPosition(-2.5, -5.5)); WallPositionsToSouth.Add(new WallPosition(-2.5, -3.5)); WallPositionsToSouth.Add(new WallPosition(-2.5, -2.5)); WallPositionsToSouth.Add(new WallPosition(-1.5, -5.5)); WallPositionsToSouth.Add(new WallPosition(-1.5, -3.5)); WallPositionsToSouth.Add(new WallPosition(-0.5, -6.5)); WallPositionsToSouth.Add(new WallPosition(-0.5, -4.5)); WallPositionsToSouth.Add(new WallPosition(-0.5, -2.5)); WallPositionsToSouth.Add(new WallPosition(+4.5, -6.5)); WallPositionsToSouth.Add(new WallPosition(+4.5, -5.5)); WallPositionsToSouth.Add(new WallPosition(+4.5, -4.5)); WallPositionsToSouth.Add(new WallPosition(+4.5, -3.5)); WallPositionsToSouth.Add(new WallPosition(+4.5, -2.5)); WallPositionsToSouth.Add(new WallPosition(+4.5, -1.5)); WallPositionsToSouth.Add(new WallPosition(+3.5, -5.5)); WallPositionsToSouth.Add(new WallPosition(+3.5, -4.5)); WallPositionsToSouth.Add(new WallPosition(+2.5, -6.5)); WallPositionsToSouth.Add(new WallPosition(+2.5, -5.5)); WallPositionsToSouth.Add(new WallPosition(+2.5, -3.5)); WallPositionsToSouth.Add(new WallPosition(+2.5, -2.5)); WallPositionsToSouth.Add(new WallPosition(+1.5, -5.5)); WallPositionsToSouth.Add(new WallPosition(+1.5, -3.5)); WallPositionsToSouth.Add(new WallPosition(+0.5, -6.5)); WallPositionsToSouth.Add(new WallPosition(+0.5, -4.5)); WallPositionsToSouth.Add(new WallPosition(+0.5, -2.5)); } return WallPositionsToSouth; } } |
これで迷路の描画はできますが、このときにどのように見せるかを考える必要があります。自分の位置を移動させるメソッドと光源も指定するためのメソッドが必要です。
まず光源は自分が向いている方向によって逆行にならないように常に自分の真後ろから光が照射されているようにします。そのために自分が向いている角度を保存するためのフィールド変数とプロパティを作成します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
public partial class Form1 : Form { double _eyeAngle = 90; double EyeAngle { get { return _eyeAngle; } set { if(value > 360) _eyeAngle = value - 360; else if(value < 0) _eyeAngle = value + 360; else _eyeAngle = value; } } } |
次に常に自分の真後ろから光が照射されているように光源をセットします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
public partial class Form1 : Form { void Lighting() { // 光源の使用 GL.Enable(EnableCap.Lighting); SetLightPosision(); GL.Enable(EnableCap.Light0); } void SetLightPosision() { // ライト 0 の設定と使用 float[] position = new float[] { 1.0f * (float)-CosFromInt((int)EyeAngle), 0f, 1.0f * (float)SinFromInt((int)EyeAngle), 0.0f }; GL.Light(LightName.Light0, LightParameter.Position, position); } double CosFromInt(int i) { return Math.Cos(GetRad(i)); } double SinFromInt(int i) { return Math.Sin(GetRad(i)); } double GetRad(double d) { return d * Math.PI / 180; } } |
↑キーが押されたら向いている方向に移動、↓キーが押されたらバック、←キーと→キーで方向転換を行ないます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public partial class Form1 : Form { protected override bool ProcessDialogKey(Keys keyData) { // Leftキーが押されたら[方向転換]がチェックされていたら左に方向転換 if((keyData & Keys.KeyCode) == Keys.Left) { if(checkBoxTurn.Checked) OnLeft(); else OnDown(); return true; } else if((keyData & Keys.KeyCode) == Keys.Right) { if(checkBoxTurn.Checked) OnRight(); else OnUp(); return true; } else if((keyData & Keys.KeyCode) == Keys.Up) { if(checkBoxTurn.Checked) OnGo(); else OnUp(); return true; } else if((keyData & Keys.KeyCode) == Keys.Down) { if(checkBoxTurn.Checked) OnBack(); else OnDown(); return true; } return base.ProcessDialogKey(keyData); } } |
前進、バック、方向転換のときはEyeX、EyeZ、EyeAngleプロパティを変更してラベルに表示されている文字列を変更するとともにglControlを再描画します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
public partial class Form1 : Form { void OnLeft() { EyeAngle += 10; ShowLabel(); glControl.Refresh(); } void OnRight() { EyeAngle -= 10; ShowLabel(); glControl.Refresh(); } void OnGo() { EyeZ += (float)(-0.1f * SinFromInt((int)EyeAngle)); EyeX += (float)( 0.1f * CosFromInt((int)EyeAngle)); ShowLabel(); glControl.Refresh(); } void OnBack() { EyeZ -= (float)(-0.1f * SinFromInt((int)EyeAngle)); EyeX -= (float)(0.1f * CosFromInt((int)EyeAngle)); ShowLabel(); glControl.Refresh(); } void ShowLabel() { labelTranslateX.Text = X.ToString(); labelTranslateY.Text = Y.ToString(); labelTranslateZ.Text = Z.ToString(); labelEyeX.Text = "X座標 = " + EyeX.ToString(); labelEyeZ.Text = "Z座標 = " + EyeZ.ToString(); // 北向きを0度とする int angle = (int)EyeAngle - 90; if(angle < 0) angle += 360; labelEyeAngle.Text = "向き = "+ angle.ToString(); } } |
glControl.Refresh()が実行されたら各プロパティの値を調べて適切な描画処理をおこないます。このとき視点や視線の変更にあわせて視界の設定が必要です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
public partial class Form1 : Form { private void glControlEx1_Paint(object sender, PaintEventArgs e) { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); // 視点を変更する SetSight(EyeX, EyeY, EyeZ); DrawMaze(); Lighting(); glControl.SwapBuffers(); } void SetSight(float x, float y, float z) { // 視界の設定 Vector3 eye = new Vector3(x, y, z); Vector3 target = new Vector3(EyeX + (float)CosFromInt((int)EyeAngle), 0, EyeZ - (float)SinFromInt((int)EyeAngle)); Vector3 up = new Vector3(0, 1, 0); Matrix4 look = Matrix4.LookAt(eye, target, up); GL.LoadMatrix(ref look); } void SetInitSight() { EyeX = 0f; EyeY = 0f; EyeZ = 2f; ShowLabel(); } } |