barus's diary

とても真面目なblogですにゃ

C#3D立方体ワイヤーフレーム (第1回) for VS2013 Express

 下記はマウスでグリグリ、動かしているところ。

 

f:id:hatakeka:20160912174949g:plain

 

参考:Visual C++ を使ってルービックキューブを作ってみよう

 

参考URLを元にC#用に3D立方体を作成した。

 C#グラフィック コッホ曲線 for VS2013 Expressで作成したのを流用します。

ファイル数と同じ、4回に分けることにする。

 

 

CThreeD.cs  C#3D立方体ワイヤーフレーム (第1回) for VS2013 Express
CBond.cs   C#3D立方体ワイヤーフレーム (第2回) for VS2013 Express
CRubic.cs     C#3D立方体ワイヤーフレーム (第3回) for VS2013 Express
Form1.cs    C#3D立方体ワイヤーフレーム (第4回) for VS2013 Express

 

 

クラスファイルの追加は

ソリューションエクスプローラー内で右クリックして、クラスを追加する。

f:id:hatakeka:20160912170611p:plain

 

 

 

全てをForm1.csのファイルにまとめていいのだが、
さすがに煩雑になるので、クラス毎にファイルを分けることにした。
C#の機能で「public partial」で囲えば、一つのファイルとして扱ってくれる。みたいだ。

 

public partial class Form1 : Form

 

新しくクラスを追加したらこの赤い部分を追加し、クラスを編集する。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
 
using System.Windows.Forms; //Formで用いる
using System.Drawing;           //描画で用いる
 
namespace WindowsFormsApplication1
{
 
    public partial class Form1 : Form
    {
     class Class1
     {
      ~
     }
 }
}

 

 

 

 

 

 

CThreeDクラス

(CThreeD.csファイル)

 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using System.Windows.Forms;
using System.Drawing;

namespace WindowsFormsApplication1
{

    public partial class Form1 : Form
    {

        //絶対座標の中心
        static public int RUBIC_X = 300;
        static public int RUBIC_Y = 300;


        public class MyPoint3D
        {
            public int x;
            public int y;
            public int z;
            public List bond = new List();//接続先
            public int num;
            public MyPoint3D() { }
            public MyPoint3D(int _x, int _y, int _z)
            {
                x = _x;
                y = _y;
                z = _z;
            }
        }

        //デカルト座標
        public class Vertex
        {
            public int group;     //グループ
            public List bond = new List();//接続先
            public int x, y, z;
            public Point point;
            public Vertex() { }
        }

        //極座標
        public class Polar
        {
            public int r;
            public double p, q;
        }

        //線の構造体
        public class Bond
        {
            public Vertex vertex;
            public Vertex center = new Vertex(); //相対座標
        }

        //面の構造体
        public class Face
        {
            public Vertex[] vertex = new Vertex[4];
            public Vertex center = new Vertex();
        }

        class CThreeD
        {

            /*=============================================================================
             機能  2次元座標系に変換する
             引数  ViewPolar : 視点の極座標
                   vertex    : 変換したい座標
            =============================================================================*/
            public void
                TransferScreen(Polar ViewPolar, ref Vertex pVertex)
            {
                double a;
                double[,] matrix = new double[3, 3];

                Vertex vertex = new Vertex();

                a = ViewPolar.r * Math.Cos(ViewPolar.q);

                /****************************************************************
                      回転行列を作る
                ****************************************************************/

                matrix[0, 0] = -1 * Math.Sin(ViewPolar.p);
                matrix[0, 1] = Math.Cos(ViewPolar.p);
                matrix[0, 2] = 0;
                matrix[1, 0] = -1 * Math.Sin(ViewPolar.q) * Math.Cos(ViewPolar.p);
                matrix[1, 1] = -1 * Math.Sin(ViewPolar.q) * Math.Sin(ViewPolar.p);
                matrix[1, 2] = Math.Cos(ViewPolar.q);
                matrix[2, 0] = -1 * Math.Cos(ViewPolar.q) * Math.Cos(ViewPolar.p);
                matrix[2, 1] = -1 * Math.Cos(ViewPolar.q) * Math.Sin(ViewPolar.p);
                matrix[2, 2] = -1 * Math.Sin(ViewPolar.q);

                /*
                 X軸とY軸
                     -sin.p         cos.p         0
                     -sin.q*cps.p  -sin.q*sin.p   cos.q
                     -cos.q*cos.p  -cos.q*sin.p  -sin.q
                  
                */
                TransferVertex(matrix, ViewPolar, ref pVertex);
            }

            /*=============================================================================
             機能  2次元座標系に変換する
             引数  matrix[3] : 回転行列
                   ViewVertex  : 視点の座標
            =============================================================================*/
            public void TransferVertex(double[,] matrix, Polar ViewPolar, ref Vertex pVertex)
            {
                double x, y, z;
                Vertex vertex = new Vertex();

                PolarToVertex(ViewPolar, ref vertex);

                x = matrix[0, 0] * (pVertex.x - vertex.x)
                  + matrix[0, 1] * (pVertex.y - vertex.y)
                  + matrix[0, 2] * (pVertex.z - vertex.z);

                y = matrix[1, 0] * (pVertex.x - vertex.x)
                  + matrix[1, 1] * (pVertex.y - vertex.y)
                  + matrix[1, 2] * (pVertex.z - vertex.z);

                z = matrix[2, 0] * (pVertex.x - vertex.x)
                  + matrix[2, 1] * (pVertex.y - vertex.y)
                  + matrix[2, 2] * (pVertex.z - vertex.z);
                z = z / 300;

                pVertex.point.X = (int)(RUBIC_X + x / z);
                pVertex.point.Y = (int)(RUBIC_Y + y / z);
            }

            /*=============================================================================
             機能  極座標を直行座標に変換する
             引数  polar    : 極座標の座標値
                   pVertex  : 直行座標へのポインタ
            =============================================================================*/
            public void PolarToVertex(Polar polar, ref Vertex pVertex)
            {
                pVertex = new Vertex();
                pVertex.x = (int)(polar.r * Math.Cos(polar.q) * Math.Cos(polar.p));
                pVertex.y = (int)(polar.r * Math.Cos(polar.q) * Math.Sin(polar.p));
                pVertex.z = (int)(polar.r * Math.Sin(polar.q));
            }

            /*=============================================================================
             機能  極座標を回転移動する
             引数  new_polar : 回転後の座標
                   old_polar : 回転させたい極座標の座標値
                   rotateP   : Z軸を中心とした回転量
                   rotateQ   : 原点を中心としてXY平面と垂直な回転量
            =============================================================================*/
            public void TurnPolar(ref Polar new_polar, Polar old_polar, double rotateP, double rotateQ)
            {
                new_polar.p = old_polar.p += rotateP;
                new_polar.q = old_polar.q += rotateQ;
            }

        }

    }


}

 

 

CThreeD クラスの説明
このクラスは、デカルト座標情報をVertexに入れて
TransferScreen()関数で、2次元座標に投影している。

ここさえ押さえれば、後は様々な立体の3次元情報を
このクラスに与えればいいだけにゃ~♪

なので、ここが肝なので、ちょっと深入りしてみる。


3次元空間の座標移動を、3次元アフィン変換っていうらしいにゃ。

 

参考URL
ロール(φ)ピッチ(θ)ヨー(ψ)で回転する場合

 

f:id:hatakeka:20160912171151p:plain

 

しかし、このプログラムでは、視点を動かしている。

ので上記式が直接あてはまらないと思う。

TransferVertex()にて、PolarToVertex()で極座標

直行座標に返還後、X軸回転、Y軸回転を行っている。

 

(ア)PolarToVertex関数、Z軸固定して極座標を直行座標に変換

(イ)軸(X軸、Y軸)固定の回転 (matrix)行列

 

 

マウスで、グリグリと動かす際、縦方向と横方向の
どちらかをX軸固定、Y軸固定と見立ている仕組みですね。
つまり、2つの情報しかインプットできないので、Z軸固定回転は使用していない。

 

次に、この2つがどういうことしているか(イ)の処理から見てみる。

 

 

 

 

 

 

 

(イ)軸(X軸、Y軸)固定の回転 (matrix)行列 

 

まずは、2次元の場合の座標移動について考えてみる。


下記図の、座標P(x ,y)からQ(x', y')の2次元アフィン変換は

x' = x *cosθ - y*sinθ
y' = x *sinθ + y*cosθ

となる。この式は、加法定理から求まる。

 

f:id:hatakeka:20160912172712p:plain

 

加法定理

三角形OPQを考える、余弦定理より 
PQ^2 = 2 - 2cos(αーβ) ・・・(1)
線分PQを座標で表した長さは
PQ^2=( cosβ ー cosα)^2  + ( sinβ ー sinα)^2  ・・・(2)
(1)(2)より
cos(αーβ)=cosα*cosβ +sinα* sinβ ・・・(3)
となる。(3)を用いて、
cos(α+β)=cos(αー(ーβ))
    =cosα*cos(ーβ) +sinα* sin(ーβ)
    =cosα*cosβ ー sinα* sinβ ・・・(4)
が求まる。sin(α+β)の場合は(4)を用いて
sin(α+β)=cos(90-(α+β))
    =cos((90-α)ーβ)
    =cos(90-α)*cosβ +sin(90-α)* sinβ
    =sinα*cosβ + cosα* sinβ ・・・(5)

 

 

f:id:hatakeka:20160912172751p:plain

 

いま、求めた 座標P(x ,y)からQ(x', y')の2次元アフィン変換は
Z軸を固定して回転した場合である。

x' = x *cosθ - y*sinθ
y' = x *sinθ + y*cosθ

 

X軸、Y軸を固定した場合のそれぞれの2次元アフィン変換については以下。

 

f:id:hatakeka:20160912172831p:plain

 

X軸固定とY軸固定の行列を掛けたのが以下(A)の行列となる。

cos.p      0        sin.p
sin.q*sin.p      cos.q       -cos.p*sin.q
-cos.q*sin.p    sin.q        cos.q*cos.p

 

で、実際のプログラムは以下の(B)の行列

-sin.p      cos.p       0
-sin.q*cos.p   -sin.q*sin.p      cos.q
-cos.q*cos.p   -cos.q*sin.p    -sin.q


(A)と(B)が異なるのでなぜなんだろうと悩んだ。
列を入れ替えても同じだから それはよしとしても、符号が異なる。
試しに(A)の行列で実行してみた場合(以下)、

 

          //(A)の行列でテスト
     matrix[0, 0] = 1 * Math.Sin(ViewPolar.p);
                matrix[0, 1] = Math.Cos(ViewPolar.p);
                matrix[0, 2] = 0;
 
                matrix[1, 0] = -1 * Math.Sin(ViewPolar.q) * Math.Cos(ViewPolar.p);
                matrix[1, 1] = 1 * Math.Sin(ViewPolar.q) * Math.Sin(ViewPolar.p);
                matrix[1, 2] = Math.Cos(ViewPolar.q);
                
                matrix[2, 0] =  1 * Math.Cos(ViewPolar.q) * Math.Cos(ViewPolar.p);
                matrix[2, 1] = -1 * Math.Cos(ViewPolar.q) * Math.Sin(ViewPolar.p);
                matrix[2, 2] =  1 * Math.Sin(ViewPolar.q);

 

ちゃんと投影されるわけだがなんだか、違和感のある変な動きとなる。
なので(B)の行列を利用している。

 

ああ・・そうか。この立方体の座標をアフィン変換しているわけでなく

視点の方を動かしているので、 (A)の行列ではおかしくなりそうだ。

 

大体、原因は分かったところで、頭が痛くなってきたし、まあ、

動くからこれでよしとするかにゃ~(*´ω`*)ピャー。

 

 


この(B)の行列をつかうことで、X軸、Y軸それぞれで固定した座標変換を行った後に
次のPolarToVertex()関数で直行座標に変換している。

 

 

 (ア)PolarToVertex関数、Z軸固定して極座標を直行座標に変換

 

 
   public void PolarToVertex(Polar polar, ref Vertex pVertex)
            {
                pVertex = new Vertex();
                pVertex.x = (int)(polar.r * Math.Cos(polar.q) * Math.Cos(polar.p));
                pVertex.y = (int)(polar.r * Math.Cos(polar.q) * Math.Sin(polar.p));
                pVertex.z = (int)(polar.r * Math.Sin(polar.q));
            }
 

 

この関数では、極座標から、直行座標への変換を行っている。

 

f:id:hatakeka:20160912173734p:plain

 

以上が、座標を2次元に投影する仕組みである。
ざっくりと理解できたところで、

 

どこに座標を保持しているかというと、一旦MyPoint3Dクラスに保持している。

 

 

 

 
        public class MyPoint3D
        {
            public int x;
            public int y;
            public int z;
            public List<int> bond = new List<int>();//接続先
            public int num;
            public MyPoint3D() { }
            public MyPoint3D(int _x, int _y, int _z)
            {
                x = _x;
                y = _y;
                z = _z;
            }
        }
 

 

List<int>型は、C++Vector<int>に相当する。
ある座標の点が、どの座標の点と接続しているか分からなければ
線を引けないので、情報をもたせることにした。


格納の仕方は bond.Add()で格納する。
取り出し方は、bond.Countで要素数が分かるので
for(int i=0; bond.Count>i; i++) boud[i] ~
のようにする。

 



 
        //デカルト座標
        public class Vertex
        {
            public int group;     //グループ
            public List<int> bond = new List<int>();//接続先
            public int x, y, z;
            public Point point;
            public Vertex() { }
        }
 

 

いわゆる、XYZ座標で表現するのをデカルト座標という。
MyPoint3Dでワンクッション置いて、Vertexに入れている。

 

 


次回は、CBoundクラスで立方体の座標を決定する。


終わり。

 

 

OpenGLプログラミングガイド 原著第5版

OpenGLプログラミングガイド 原著第5版