プログラムと政治とオカルトと戯れ言
C#3D立方体 for VS2013 Express
参考:Visual C++ を使ってルービックキューブを作ってみよう 参考URLを元にC#用に3D立方体を作成した。 投稿できる量に制限があるので4回に分けることにする。 CThreeD.cs → C#3D立方体ワイヤーフレーム 序(第1回) for VS2013 Express
CRubic.cs → C#3D立方体ワイヤーフレーム マウスの座標渡し(第3回) for VS2013 Express
Form1.cs → C#3D立方体ワイヤーフレーム 終(第4回) for VS2013 Express ソリューションエクスプローラー内で右クリックして、クラスを追加する。 全てを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;
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<int> bond = new List<int>();//接続先
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<int> bond = new List<int>();//接続先
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 ロール(φ)ピッチ(θ)ヨー(ψ)で回転する場合 しかし、このプログラムでは、 TransferVertex()にて、PolarToVertex()で極座標を 直行座標に返還後、X軸回転、Y軸回転を行っている。 マウスで、グリグリと動かす際、縦方向と横方向の どちらかをX軸固定、Y軸固定と見立ている仕組みですね。 つまり、2つの情報しかインプットできないので、Z軸固定回転は使用していない。 まずは、2次元の場合の座標移動について考えてみる。 下記図の、座標P(x ,y)からQ(x', y')の2次元アフィン変換は x' = x *cosθ - y*sinθ y' = x *sinθ + y*cosθ となる。この式は、加法定理から求まる。 加法定理 三角形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) いま、求めた 座標P(x ,y)からQ(x', y')の2次元アフィン変換は Z軸を固定して回転した場合である。 x' = x *cosθ - y*sinθ y' = x *sinθ + y*cosθ X軸、Y軸を固定した場合の2次元アフィン変換については以下。 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)の行列で実行してみた場合(以下)、 //テストしてみた行列 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)の行列を利用している。 ちゃんと理由があるはずなんだろうが、 頭が痛くなってきたので、これでよしとするかにゃ~(*´ω`*)。 次のPolarToVertex()関数で直行座標に変換している。 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));
}
この関数では、極座標から、直行座標への変換を行っている。 以上が、座標を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クラスで立方体の座標を決定する。 終わり。 PR |