前回予告した通り、今後はGameWindow版スケルトンを使ってGLUTでやってきたプリミティブ等を移植してみようかと思います。
で、その主旨は、
OpenTKにすぐ使える基本図形(プリミティブ)のライブラリーが無かったので、OpenTKの学習と動作確認用に始めたものです。現在、6つのプリミティブを作ってみました。(コードは巻末参照)これらを同じような使い方ができるよう「緩い仕様」として、(x, y, zの-1.0~1.0内に収まる既定サイズにすることは勿論)
共通仕様:
(1) 第1引数がソリッド・ワイアーのフラグ(bool)
(2) 以降の引数に既定値を与える
だけ決めました。これで出力ターゲットをDLLにしてMSCompAssでコンパイルすると、
using OpenTK_Primitives; //OpenTK自作図形ライブラリー
とするだけで6つの基本図形を描画できるようになります。
現在利用できるプリミティブは以下の6つ(注)でサンプルプログラムを作る程度であれば耐えられるのではないでしょうか?また、(3)(5)(6)は分割数を変えると正4面体などになるので実験用には楽しいです。
(1)立(直)方体(Cube)
(2)(正)三角錐(Triangular)
(3)球(Sphere)
(4)円環(Torus)
(5)円柱(Pipe)
(6)円錐(Cone)
注:(1)、(2)はGLUTシリーズで使ったやつの移植です。後はGLUTは関数化しているので、webに出ていたプログラムを眺め、比較し、いいとこどりして纏めてみました。
なお、使用するスケルトンはGameWindow用ですが、メンバーフィールドとして、
bool SorW = true; //Solidか、Wireかのフラグ
bool drawStop = false; //描画停止
およびマウスイベント
//Mouse割込み
this.MouseDown += gW_MouseDown;
と
//Mouse割込み
private void gW_MouseDown(object sender, MouseButtonEventArgs e)
{
if(e.Button == MouseButton.Left)
SorW = !SorW;
else if(e.Button == MouseButton.Right)
drawStop = !drawStop;
}
を追加し、ウィンドウ描画が更新された場合に実行されるOnRenderFrame(FrameEventArgs e)メソッドを
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
if(drawStop)
return; //描画停止
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.MatrixMode(MatrixMode.Modelview); //モデルビューモードを選択
//視点を設定して原点(0, 0, 0)を見る
Vector3 eye = new Vector3(0.0f, 0.0f, 5.0f);
Matrix4 modelview = Matrix4.LookAt(eye, Vector3.Zero, Vector3.UnitY); //gluLookAt(0, 0, 5, 0, 0, 0, 0, 1, 0);
GL.LoadMatrix(ref modelview); //現在の変換マトリックスに設定
//以下に描画処理を行う
GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
GL.PushMatrix();
{
GL.Rotate(RotateX, 1, 0, 0);
GL.Rotate(RotateY, 0, 1, 0);
GL.Rotate(RotateZ, 0, 0, 1);
//ここで描画メソッドを呼び出します。
}
GL.PopMatrix();
RotateX += 1.5f;
RotateY += 5.0f;
RotateZ += 1.0f;
this.SwapBuffers();
}
としてクルクルと回転させ、左ボタンでソリッドとワイアーの切り替え、右ボタンで描画停止を行うことにしました。
追伸:因みに追加説明すると、メソッドはGLUTの関数と非常に名称が似ているので「大体何をするのか」についてアタリが付くと思います。逆に結構戸惑ったのは、GLUTではデータはfloatかdoubleだけだったのですが、OpenTKでは行列変数(Vector3等-注1)というものがあり、これを使って処理することが多いことです。しかし、慣れるとかえってこっちの方が便利で楽になりました。又「OpenGLの関数」はGLクラスの静的メソッドになっているので、「オブジェクト」というよりも「関数」という感じで使用します。これはOpenGLの使用を崩したくなかったのでしょう。そんなわけで私も「プリミティブ毎にクラス化する考え」(注2)もあったのですが、あえてC#の「プリミティブクラス」として6つのプリミティブをメソッドで呼ぶようにしました。
注1:Vector3.UnitZ, Vector3.UnitX, Vector3.UnitZ、Vector.Zero等を始めてみた時は???となりました。
注2:例えばSphereクラスを作って、オブジェクトを生成し、Showメソッドで表示、Tranlatesde移動、Rotateで回転、Color、Materialで色や質感を出す、等のオブジェクト化が図れますが、それは既にOpenGLではないですよね。
次回はこれらのサンプルのお披露目を行います。
【OpenTK_Primitives.cs】
/////////////////////////////////////////////////
// OpenTK_Primitives.cs
// OpenTKの基本図形集
// Copyright (c) 2024 by Ysama
//共通仕様:
//(1) 第1引数がソリッド・ワイアーのフラグ(bool)
//(2) 以降の引数に既定値を与える
/////////////////////////////////////////////////
using System;
using OpenTK; //OpenTKを使用する際に必須
using OpenTK.Graphics; //OpenTKのグラフィックを使用する際に必須
using OpenTK.Graphics.OpenGL; //OpenGLを使用する際に必要
namespace OpenTK_Primitives
{
public class OTKPrims
{
//立(直)方体(引数:ソリッドまたはワイアー、横、縦、高さ)
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();
}
//(正)三角錐(引数:ソリッドまたはワイアー、辺長さ、縦辺(ver - 既定1.0)の倍数)
public void Triangular(bool SorW = true, double len = 1.225, double ratio = 1.0)
{
double hor = len / 2;
double dep = len * Math.Sin(Math.PI / 3); //Sin(60)
double ver = len * ratio * Math.Sqrt(2.0 / 3.0); //sqrt(power(len, 2.0) + power(dep * 2.0 / 3.0, 2.0))
//(正)三角錐データ
double[][] Tvertex = new double[4][]; //三角錐の4頂点
Tvertex[0] = new double[3] {0.0, ver * 2 / 3, 0.0}; //頂天
Tvertex[1] = new double[3] {hor, -ver / 3, -dep / 3}; //右奥
Tvertex[2] = new double[3] {-hor, -ver / 3, -dep / 3}; //左奥
Tvertex[3] = new double[3] {0.0, -ver / 3, dep * 2 / 3}; //手前下
int[][] Tedge = new int[6][]; //頂点を結ぶ5線分
Tedge[0] = new int[2] {0, 1};
Tedge[1] = new int[2] {1, 2};
Tedge[2] = new int[2] {2, 0};
Tedge[3] = new int[2] {0, 3};
Tedge[4] = new int[2] {3, 2};
Tedge[5] = new int[2] {1, 3};
int[][] Tface = new int[4][]; //三角錐の4面
Tface[0] = new int[3] {0, 1, 2};
Tface[1] = new int[3] {0, 2, 3};
Tface[2] = new int[3] {0, 3, 1};
Tface[3] = new int[3] {1, 2, 3};
if(SorW) {
GL.Begin(PrimitiveType.Triangles);
for(int j = 0; j < 4; ++j)
{
Vector3 v1 = new Vector3((float)Tvertex[Tface[j][0]][0], (float)Tvertex[Tface[j][0]][1], (float)Tvertex[Tface[j][0]][2]);
Vector3 v2 = new Vector3((float)Tvertex[Tface[j][1]][0], (float)Tvertex[Tface[j][1]][1], (float)Tvertex[Tface[j][1]][2]);
Vector3 v3 = new Vector3((float)Tvertex[Tface[j][2]][0], (float)Tvertex[Tface[j][2]][1], (float)Tvertex[Tface[j][2]][2]);
GL.Normal3(Vector3.Normalize(Vector3.Cross(v2 - v1, v3 - v1)));
for(int i = 0; i < 3; ++i)
GL.Vertex3(Tvertex[Tface[j][i]][0], Tvertex[Tface[j][i]][1], Tvertex[Tface[j][i]][2]);
}
GL.End();
}
else {
GL.Begin(PrimitiveType.Lines);
for(int i = 0; i < 6; i++)
{
GL.Vertex3(Tvertex[Tedge[i][0]]);
GL.Vertex3(Tvertex[Tedge[i][1]]);
}
GL.End();
}
}
//球(引数:ソリッドまたはワイアー、半径、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(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(vec2);
GL.Vertex3(vec2);
}
}
}
GL.End();
}
//円環(引数:ソリッドまたはワイアー、半径、円環の太さ、面の肌理)
public void Torus(bool SorW = true, double r1 = 1.0, double r2 = 0.2, int count = 128)
{
if(!SorW) count = 32; //ワイアーの場合、肌理を粗くする
GL.Material(MaterialFace.Front, MaterialParameter.Ambient, Color4.Cyan);
double x = r1 * Math.Sqrt(2 * (1 - Math.Cos((Math.PI / 180.0) * (360f / count))));
for(int k = 0; k < count; k++)
{
GL.PushMatrix();
GL.Rotate(360f / count * k, 0, 1, 0); //Y軸を中心として一周する
GL.Translate(0, 0, r1 - r2); //Z軸方向へ半径 - 円環太さ移動
for(int i = 0; i <= count; i++)
{
GL.Rotate(360f / count, 1, 0, 0); //X軸を中心に指定角回転
if(SorW)
GL.Begin(PrimitiveType.Quads); //四角面を描画(Solid) count = 128;
else
GL.Begin(PrimitiveType.LineStrip); //四角面を描画(Wire) count = 32;
{
Vector3 p1 = new Vector3((float)(-x / 2), (float)r2, (float)(x / 2));
Vector3 p2 = new Vector3((float)(x / 2), (float)r2, (float)(x / 2));
Vector3 p3 = new Vector3((float)(x / 2), (float)r2, (float)(-x / 2));
GL.Normal3(Vector3.Normalize(Vector3.Cross(p2 - p1, p3 - p1)));
GL.Vertex3(-x / 2, r2, x / 2);
GL.Vertex3(x / 2, r2, x / 2);
GL.Vertex3(x / 2, r2, -x / 2);
GL.Vertex3(-x / 2, r2, -x / 2);
}
GL.End();
}
GL.PopMatrix();
}
}
//円柱(引数:ソリッドまたはワイアー、半径、縦の長さ)
public void Pipe(bool SorW = true, double radius = 0.5, double length = 2.0)
{
double step = 0.1;
for(double rad = -Math.PI; rad <= Math.PI; rad += step)
{
if(SorW)
GL.Begin(PrimitiveType.Quads);
else
GL.Begin(PrimitiveType.LineStrip);
//側面法線
Vector3 v0 = new Vector3((float)(radius * Math.Cos(rad)), (float)(length / 2), (float)(radius * Math.Sin(rad)));
Vector3 v1 = new Vector3((float)(radius * Math.Cos(rad)), (float)-(length / 2), (float)(radius * Math.Sin(rad)));
Vector3 v2 = new Vector3((float)(radius * Math.Cos(rad + step)), (float)-(length / 2), (float)(radius * Math.Sin(rad + step)));
GL.Normal3(Vector3.Normalize(Vector3.Cross(v1 - v0, v2 - v0)));
//側面
GL.Vertex3(radius * Math.Cos(rad + step), length / 2, radius * Math.Sin(rad + step));
GL.Vertex3(radius * Math.Cos(rad), length / 2, radius * Math.Sin(rad));
GL.Vertex3(radius * Math.Cos(rad), -length / 2, radius * Math.Sin(rad));
GL.Vertex3(radius * Math.Cos(rad + step), -length / 2, radius * Math.Sin(rad + step));
GL.End();
if(SorW)
GL.Begin(PrimitiveType.Triangles);
else
GL.Begin(PrimitiveType.LineStrip);
//天面法線
GL.Normal3(Vector3.UnitY);
//天面
GL.Vertex3(0.0, length / 2, 0.0);
GL.Vertex3(radius * Math.Cos(rad), length / 2, radius * Math.Sin(rad));
GL.Vertex3(radius * Math.Cos(rad + step), length / 2, radius * Math.Sin(rad + step));
GL.End();
if(SorW)
GL.Begin(PrimitiveType.Triangles);
else
GL.Begin(PrimitiveType.LineStrip);
//底面法線
GL.Normal3(-Vector3.UnitY);
//底面
GL.Vertex3(0.0, -length / 2, 0.0);
GL.Vertex3(radius * Math.Cos(rad), -length / 2, radius * Math.Sin(rad));
GL.Vertex3(radius * Math.Cos(rad + step), -length / 2, radius * Math.Sin(rad + step));
GL.End();
}
}
//円錐(引数:ソリッドまたはワイアー、半径、縦の高さ)
public void Cone(bool SorW = true, double radius = 0.5, double height = 2.0)
{
double step = 0.1;
for(double rad = -Math.PI; rad <= Math.PI; rad += step)
{
if(SorW)
GL.Begin(PrimitiveType.Triangles);
else
GL.Begin(PrimitiveType.LineStrip);
//側面法線
Vector3 v1 = new Vector3((float)(radius * Math.Cos(rad)), (float)-(height / 2), (float)(radius * Math.Sin(rad)));
Vector3 v2 = new Vector3((float)(radius * Math.Cos(rad + step)), (float)-(height / 2), (float)(radius * Math.Sin(rad + step)));
GL.Normal3(Vector3.Normalize(Vector3.Cross(v1, v2)));
//側面
GL.Vertex3(0.0, height / 2, 0.0);
GL.Vertex3(radius * Math.Cos(rad), -height / 2, radius * Math.Sin(rad));
GL.Vertex3(radius * Math.Cos(rad + step), -height / 2, radius * Math.Sin(rad + step));
//底面法線
GL.Normal3(-Vector3.UnitY);
//底面
GL.Vertex3(0.0, -height / 2, 0.0);
GL.Vertex3(radius * Math.Cos(rad), -height / 2, radius * Math.Sin(rad));
GL.Vertex3(radius * Math.Cos(rad + step), -height / 2, radius * Math.Sin(rad + step));
GL.End();
}
}
}
}