C#3D立方体ワイヤーフレーム (第1回) for VS2013 Express
下記はマウスでグリグリ、動かしているところ。
参考: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
クラスファイルの追加は
ソリューションエクスプローラー内で右クリックして、クラスを追加する。
全てをForm1.csのファイルにまとめていいのだが、
さすがに煩雑になるので、クラス毎にファイルを分けることにした。
C#の機能で「public partial」で囲えば、一つのファイルとして扱ってくれる。みたいだ。
public partial class Form1 : Form
新しくクラスを追加したらこの赤い部分を追加し、クラスを編集する。
}
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 Listbond = 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
ロール(φ)ピッチ(θ)ヨー(ψ)で回転する場合
しかし、このプログラムでは、視点を動かしている。
ので上記式が直接あてはまらないと思う。
TransferVertex()にて、PolarToVertex()で極座標を
直行座標に返還後、X軸回転、Y軸回転を行っている。
(ア)PolarToVertex関数、Z軸固定して極座標を直行座標に変換
(イ)軸(X軸、Y軸)固定の回転 (matrix)行列
マウスで、グリグリと動かす際、縦方向と横方向の
どちらかをX軸固定、Y軸固定と見立ている仕組みですね。
つまり、2つの情報しかインプットできないので、Z軸固定回転は使用していない。
次に、この2つがどういうことしているか(イ)の処理から見てみる。
GPU並列図形処理入門 ~CUDA・OpenGLの導入と活用 (Software Design plus)
- 作者: 乾正知
- 出版社/メーカー: 技術評論社
- 発売日: 2014/02/18
- メディア: 大型本
- この商品を含むブログ (1件) を見る
(イ)軸(X軸、Y軸)固定の回転 (matrix)行列
まずは、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)の行列で実行してみた場合(以下)、
ちゃんと投影されるわけだがなんだか、違和感のある変な動きとなる。
なので(B)の行列を利用している。
ああ・・そうか。この立方体の座標をアフィン変換しているわけでなく
視点の方を動かしているので、 (A)の行列ではおかしくなりそうだ。
大体、原因は分かったところで、頭が痛くなってきたし、まあ、
動くからこれでよしとするかにゃ~(*´ω`*)ピャー。
この(B)の行列をつかうことで、X軸、Y軸それぞれで固定した座標変換を行った後に
次のPolarToVertex()関数で直行座標に変換している。
(ア)PolarToVertex関数、Z軸固定して極座標を直行座標に変換
この関数では、極座標から、直行座標への変換を行っている。
以上が、座標を2次元に投影する仕組みである。
ざっくりと理解できたところで、
どこに座標を保持しているかというと、一旦MyPoint3Dクラスに保持している。
List<int>型は、C++のVector<int>に相当する。
ある座標の点が、どの座標の点と接続しているか分からなければ
線を引けないので、情報をもたせることにした。
格納の仕方は bond.Add()で格納する。
取り出し方は、bond.Countで要素数が分かるので
for(int i=0; bond.Count>i; i++) boud[i] ~
のようにする。
いわゆる、XYZ座標で表現するのをデカルト座標という。
MyPoint3Dでワンクッション置いて、Vertexに入れている。
次回は、CBoundクラスで立方体の座標を決定する。
終わり。
- 作者: OpenGL策定委員会,松田晃一
- 出版社/メーカー: ピアソンエデュケーション
- 発売日: 2006/12/19
- メディア: 大型本
- 購入: 2人 クリック: 71回
- この商品を含むブログ (45件) を見る