プログラムの概要
入力画面で、マウスを用いて、側面より見た平 面図を描きます。マウスの左ボタンをクリック する事で連続線を描き、右ボタンをクリックす ると新しい線を描く事が出来る。 側面図が完成すると、回転の基本角度を設定し て、確定ボタンをクリックすると、平面図を立 体図に座標変換する。 各軸の回転角度を設定して、表示ボタンをクリ ックすると、立体図が表示される。各軸の回転 角度を変更して、表示ボタンをクリックすると 色々な角度から立体図を見る事が出来る。3D回転体プログラム
C# 2005 ④ □ 単純変数の宣言(private) □ 標準コントロールの利用(Picture、GroupBox、RadioButton、Label、Text、Button) □ プロパティの値の取得と設定(Image、Button、X、Y、Enabled) □ イベントの利用(Load、Click、MouseDown) □ メソッドの利用(FromImage、BringToFront、Dispose) □ メソッドの利用(Clear、DrawLine、Refresh) □ ステートメントの利用(return) □ 静的クラスの利用(静的クラスのメソッド) □ 制御構造構文(条件分岐、ループ処理) □ サブルーチン(新規プロシージャ、引数) 今回の課題項目 □ マウスイベントに依るマウス座標の取得(MouseDown) □ 描画メソッドに依るグラフィックスの描画(Clear、DrawLine、Refresh) 今回の重点項目 □ 視点と物体、物体と表示面の距離を自由に設定出来る様にする。 □ 作成した立体画像をファイルに記録出来る様にする。 今回の応用項目■ オブジェクト・プロパティ一覧 ■ コントロールの種類 プロパティ プロパティの設定値 フォーム Name kaitentai Text 3D回転体 ピクチャーボックス1 Name picInput BackColor White Size 640, 400 ピクチャーボックス2 Name picDisp BackColor White Size 640, 400 グループボックス1 Name grpBaseDegree Text、Font 回転の基本角度(MS明朝,標準,9) グループボックス2 Name grpAxisDegree Text、Font 各軸の回転角度(MS明朝,標準,9) ラジオボタン1 Name radBaseDegree10 Text 10 ラジオボタン2 Name radBaseDegree20 Text 20 ラジオボタン3 Name radBaseDegree30 Text 30 Checked True ラベル1 ボタン1 ラジオボタン1~8 ピクチャーボックス1 ピクチャーボックス2 ピクチャーボックスの 1と2は同じ場所に重 ねて貼り付ける。 グループ1 テキスト1 ボタン2 ボタン3 ボタン4 ラベル2 テキスト2 ラベル3 テキスト3 グループ2
コントロールの種類 プロパティ プロパティの設定値 ラジオボタン4 Name radBaseDegree40 Text 40 ラジオボタン5 Name radBaseDegree45 Text 45 ラジオボタン6 Name radBaseDegree60 Text 60 ラジオボタン7 Name radBaseDegree90 Text 90 ラジオボタン8 Name radBaseDegree120 Text 120 ラベル1 Name lblAxisDegreeX Caption X軸 ラベル2 Name lblAxisDegY Caption Y軸 ラベル3 Name lblAxisDegZ Caption Z軸 テキストボックス1 Name txtAxisDegX TextAlign Right Text 30 テキストボックス2 Name txtAxisDegY TextAlign Right Text 0 テキストボックス3 Name txtAxisDegZ TextAlign Right Text 0 ボタン1 Name btnInput Text、Font 入力(MS明朝,太字,11) ボタン2 Name btnFix Text、Font 確定(MS明朝,太字,11) ボタン3 Name btnDisp Text、Font 表示(MS明朝,太字,11) ボタン4 Name btnFinish Text、Font 終了(MS明朝,太字,11)
■ 既存の標準モジュールの追加 ■ プロジェクトに既存の標準モジュールを追加するには、ソリューションエクスプローラでプロジェクト を右クリックして表示されるポップアップメニューで、『追加』をクリックし、更に、サブメニューか ら『既存項目の追加』を選択する。猶、因みに、新規に標準モジュールを作成する場合は、『新しい項 目』を選択する。 次に、既存項目を指定する為のダイアログが表示されるので、既存の標準モジュールを選択し、『開く』 ボタンをクリックする。以上で、プロジェクトに、既存の標準モジュールが組み込まれる。
■ プログラムリスト(フォームモジュール部分) ■ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using kaitentai_mod; namespace kaitentai {
public partial class kaitentai : Form {
private Graphics g; private bool flag;
private int xa, xb, ya, yb; public kaitentai( ) { InitializeComponent( ); } // フォームが読み込まれた時の処理
private void kaitentai_Load( object sender, EventArgs e ) { // 視点~物体~表示面の距離の設定 kaitenti_mod.d2d = 500; kaitenti_mod.d2e = 500; // 使用可能なボタンの制限 btnFix.Enabled = false; btnDisp.Enabled = false; // Graphics オブジェクトの生成
picInput.Image = new Bitmap( picInput.Width, picInput.Height ); g = Graphics.FromImage( picInput.Image ); } 標準モジュール(コードモジュー ル)の追加 ネームスペース(名前空間)の使 用を宣言して居る。 此の部分は、エディタが、自動的 に記述して下れる。 此処で宣言した変数は同じフォー ム内の総てのサブプロシージャで 値の参照と設定を行う事が出来 る。 此の値を変更する事に依り拡大率 等を変更する事が出来る。 ピクチャボックスの Imege プロ パティに、描画を行うキャンバス の役目をするBitmap オブジェク トを生成して居る。 実際に描画を行う Graphics オブ ジェクトをピクチャボックスの Image から生成して居る。
// ボタン(終了)がクリックされた時の処理
private void btnFinish_Click( object sender, EventArgs e ) { this.Dispose( ); Application.Exit( ); } // ボタン(入力)がクリックされた時の処理
private void btnInput_Click( object sender, EventArgs e ) { // 入力画面の表示と初期化 picInput.BringToFront( ); g.Clear( Color.White ); g.DrawLine( Pens.Blue, 0, 200, 639, 200 ); g.DrawLine( Pens.Blue, 320, 0, 320, 399 ); picInput.Refresh( ); flag = false; kaitenti_mod.cnt = 0; // 使用可能なボタンの制限 btnFix.Enabled = false; btnDisp.Enabled = false; } // ボタン(確定)がクリックされた時の処理
private void btnFix_Click( object sender, EventArgs e ) { // 計算ジェネラルプロシージャの呼出 kaitenti_mod.Calculation( ); // 使用可能なボタンの制限 btnFix.Enabled = false; btnDisp.Enabled = true; } // ボタン(表示)がクリックされた時の処理
private void btnDisp_Click( object sender, EventArgs e ) { // 表示画面の表示 picDisp.BringToFront( ); // 表示ジェネラルプロシージャの呼出 kaitenti_mod.Display( ); } アプリケーションを終了する場 合、Dispose メソッドに依り、正 しくプログラムをメモリから消去 して終了する事が望ましい。 同じ位置に2枚重ねたピクチャー ボックスをBringToFront メソッ ドに依り、表示するピクチャーボ ックスを切り替えて居る。 Enabled プロパティを False に設 定する事に依り、使用出来るボタ ンを制限して居る。 入力画面のクリアとX軸・Y軸の 描画を行う。 其の時点で使用出来るボタンを Enabled プロパティに依り、使用 可と使用不可に切り替える事で誤 操作を防ぐ事が出来る。 コードモジュールで定義されたメ ソッドを呼び出す。 同じ位置に2枚重ねたピクチャー ボックスをBringToFront メソッ ドに依り、表示するピクチャーボ ックスを切り替えて居る。 コードモジュールで定義されたメ ソッドを呼び出す。
// 入力画面でマウスボタンが押し下げられた時の処理
private void picInput_MouseDown( object sender, MouseEventArgs e ) { if ( e.Button == MouseButtons.Left ) { // 左ボタンが押し下げられた場合 if ( !flag ) { xa = e.X; ya = e.Y;
g.DrawLine( Pens.Red, xa, ya, xa, ya ); flag = true;
} else {
xb = e.X; yb = e.Y;
g.DrawLine( Pens.Red, xa, ya, xb, yb );
kaitenti_mod.x1[ kaitenti_mod.cnt ] = xa - 320; kaitenti_mod.y1[ kaitenti_mod.cnt ] = 200 - ya; kaitenti_mod.z1[ kaitenti_mod.cnt ] = 0; kaitenti_mod.x2[ kaitenti_mod.cnt ] = xb - 320; kaitenti_mod.y2[ kaitenti_mod.cnt ] = 200 - yb; kaitenti_mod.z2[ kaitenti_mod.cnt ] = 0; kaitenti_mod.cnt++;
xa = xb; ya = yb;
if ( !btnFix.Enabled ) btnFix.Enabled = true; } picInput.Refresh( ); } else { // 右ボタンが押し下げられた場合 flag = false; } } } } 此処では、マウスイベントに依るマウス座標の取得と描画メソッドに依るグラフィックスの描画を 主題として居る為、平面座標を立体座標に変換する等の部分は、標準モジュール kaitentai_mod.vb に既に入力された物を組み込んで使用する。標準モジュールは、通常、アプリケーションの他のモ ジュールで共通に使う為の宣言やプロシージャを記述する為に使用する。 引数のe には、マウスイベントに 関する情報(マウスボタンの種類 やマウスポインタの位置)が格納 されて居る。 赤いペンで点を描画して居る。始 点と終点を同じ位置に設定すると DrawLine は点を描画する。 描画後は、Refresh メソッドで再 描画しないと、描画結果が反映さ れない。 マウスの右ボタンがクリックされ た場合は、描画フラグをFalse に 設定し、非描画モードにする。 始点と終点の平面座標を配列変数 に格納する。猶、Z 座標は、3D 座 標に変換する為に必要で有る。 赤いペンで直線を描画して居る。 此処では、始点と終点が異なるの でDrawLine は直線を描画する。
■ プログラムリスト(標準モジュール部分) ■ using System; using System.Drawing; using System.Windows.Forms; namespace kaitentai_mod {
public class kaitenti_mod {
// グローバルな定数の宣言
public const int MAX = 1000;
public const float DEG = ( float )( 3.14159 / 180 );
// グローバルな変数の宣言
public static float[ ] x1 = new float[ MAX ]; public static float[ ] x2 = new float[ MAX ]; public static float[ ] y1 = new float[ MAX ]; public static float[ ] y2 = new float[ MAX ]; public static float[ ] z1 = new float[ MAX ]; public static float[ ] z2 = new float[ MAX ]; public static int cnt, d2d, d2e;
// ローカルな変数の宣言
private static float cosX, cosY, cosZ; private static float sinX, sinY, sinZ; private static int xn, yn;
// 平面座標を立体座標に変換するジェネラルプロシージャ public static void Calculation( )
{
kaitentai.kaitentai f = new kaitentai.kaitentai( ); RadioButton rb; int k = 0, t, d, i, j, n = 0; float s, c; // アクティブなフォームの設定 f = ( kaitentai.kaitentai ) kaitentai.kaitentai.ActiveForm; // 基本回転角度の取得
foreach ( Control r in f.grpBaseDegree.Controls ) {
if ( r.GetType( ).ToString( ) == "System.Windows.Forms.RadioButton" ) { rb = ( RadioButton ) r; if ( rb.Checked ) { k = System.Convert.ToInt32( rb.Text ); break; } } } if ( k == 0 ) return; ネームスペース(名前空間)の使 用を宣言して居る。 此の部分は、エディタが、自動的 に記述して下れる。 此処でPublic 宣言した定数は、総 てのクラスの総てのプロシージャ で参照する事が出来る。 此処で Public 宣言した変数は総 てのクラスの総てのプロシージャ で値の参照と設定を行う事が出来 る。 配列の要素数は、将来の変更に備 えて、定数や変数で指定する事が 望ましい。此の場合定数MAX の 値を変更するだけで、簡単に総て の要素数を変更する事が出来る。 此処で宣言した変数は宣言したプ ロシージャ内でしか値の参照と設 定を行う事が出来ない。 フォームやコントロールもデータ 型と仕て指定する事が出来る。 標準モジュールからフォームモジ ュールを操作する為の変数を設定 して居る。 此処では,何のラジオボタンが選 択されて居るかを判定して居る が、判定が付けば、残りの判定は 不要なのでbreak でループから強 制脱出して居る。
// 平面座標を立体座標に変換 t = cnt;
for ( i = 1; i <= ( 360 / k – 1 ); i++ ) {
d = k * i;
s = ( float )Math.Sin(( double )d * DEG ); c = ( float )Math.Cos(( double )d * DEG ); for ( j = 0; j <= t - 1; j++ ) { n = t * i + j; if ( n > MAX ) { MessageBox.Show("データが大き過ぎます!¥n 回転の基本角度を大きく仕て下さい。"); return; } x1[ n ] = z1[ j ] * s + x1[ j ] * c; y1[ n ] = y1[ j ]; z1[ n ] = z1[ j ] * c - x1[ j ] * s; x2[ n ] = z2[ j ] * s + x2[ j ] * c; y2[ n ] = y2[ j ]; z2[ n ] = z2[ j ] * c - x2[ j ] * s; } } cnt = n + 1; for ( i = 0; i <= ( 360 / k - 1 ); i++ ) { for ( j = 0; j <= t; j++ ) { n = ( t + 1 ) * i + j + cnt; if ( j == t ) { x1[ n ] = x2[ t * i + j - 1 ]; y1[ n ] = y2[ t * i + j - 1 ]; z1[ n ] = z2[ t * i + j - 1 ]; if ( i == 360 / k – 1 ) { x2[ n ] = x2[ j - 1 ]; y2[ n ] = y2[ j - 1 ]; z2[ n ] = z2[ j - 1 ]; } else { x2[ n ] = x2[ t * ( i + 1 ) + j - 1 ]; y2[ n ] = y2[ t * ( i + 1 ) + j - 1 ]; z2[ n ] = z2[ t * ( i + 1 ) + j - 1 ]; } } else { x1[ n ] = x1[ t * i + j ]; y1[ n ] = y1[ t * i + j ]; z1[ n ] = z1[ t * i + j ]; x2[ n ] = x1[ t * ( i + 1 ) + j ]; y2[ n ] = y1[ t * ( i + 1 ) + j ]; z2[ n ] = z1[ t * ( i + 1 ) + j ]; } } } cnt = n; }
Sin や Cos 等の算術関数は、Math クラスで定義されて居る。 配列の要素番号(添字)には変数 だけでなく、変数を含む計算式も 指定出来る。 此の部分では、Y 軸を中心に回転 の基本角度で指定した角度毎の立 体座標を算出して居る。 此等は、プログラミングに必須の 知識ではないが、コンピュータグ ラフィックの基本と成る物で有る ので、興味が有れば、順を追って コードを確認し、何の様に仕てコ ンピュータグラフィックが成り立 って居るのかを理解して欲しい。
// 3D回転体を表示するジェネラルプロシージャ public static void Display( )
{
kaitentai.kaitentai f = new kaitentai.kaitentai( ); Graphics g;
int i, xa, xb, ya, yb;
// アクティブなフォームの設定
f = ( kaitentai.kaitentai ) kaitentai.kaitentai.ActiveForm;
// Graphics オブジェクトの生成
f.picDisp.Image = new Bitmap( f.picDisp.Width, f.picDisp.Height ); g = Graphics.FromImage( f.picDisp.Image ); // 表示画面のクリア g.Clear( Color.White ); // 各軸の回転角度の基本値の算出
cosX = ( float )Math.Cos( System.Convert.ToDouble( f.txtAxisDegX.Text ) * DEG ); cosY = ( float )Math.Cos( System.Convert.ToDouble( f.txtAxisDegY.Text ) * DEG ); cosZ = ( float )Math.Cos( System.Convert.ToDouble( f.txtAxisDegZ.Text ) * DEG ); sinX = ( float )Math.Sin( System.Convert.ToDouble( f.txtAxisDegX.Text ) * DEG ); sinY = ( float )Math.Sin( System.Convert.ToDouble( f.txtAxisDegY.Text ) * DEG ); sinZ = ( float )Math.Sin(S ystem.Convert.ToDouble( f.txtAxisDegZ.Text ) * DEG );
// 各座標を変換して表示 for ( i = 0; i <= cnt; i++ ) {
kaitenti_mod.Convert( x1[ i ], y1[ i ], z1[ i ] ); xa = xn; ya = yn; kaitenti_mod.Convert( x2[ i ], y2[ i ], z2[ i ] ); xb = xn; yb = yn; g.DrawLine( Pens.Blue, new Point( xa, ya ), new Point( xb, yb )); }
}
// 座標を変換するジェネラルプロシージャ
public static void Convert( float x, float y, float z ) { float x3, y3, z3; float x4, y4, z4; float tt; // 各座標の変換
x3 = ( y * sinX + z * cosX ) * sinY + x * cosY; y3 = ( y * cosX - z * sinX );
z3 = ( y * sinX + z * cosX ) * cosY - x * sinY; x4 = x3 * cosZ - y3 * sinZ; y4 = x3 * sinZ + y3 * cosZ; z4 = z3; tt = ( d2d - z4 ) / ( z4 - d2d - d2e ); xn = 320 + ( int )(( tt + 1 ) * x4 + 0.5 ); yn = 200 - ( int )(( tt + 1 ) * y4 + 0.5 ); } } } 回転体を描画する為のピクチャボ ックスを基に Graphics オブジェ クトを生成して居る。 何度も使用される値は、其の都度 計算するのではなく、予め計算し た結果を変数に格納して置くと実 行速度を上げる事が出来る。 特に、ループ内で複雑な計算を行 うと実行速度が下がるので、注意 を要する。 此処では2種類の算術演算子が使 用されて居る。 +:加算(足す) *:乗算(掛ける) 優先順位は、数学と同じく乗算が 加算よりも高く、先に計算される。 標準モジュールからフォームモジ ュールを操作する為の変数を設定 して居る。 標準モジュールからフォームモジ ュールを操作する為の変数を設定 して居る。 点 P(x,y,z)をX軸の周りに角度α だけ回転させた時の点 P’(x’,y’,z’) は三角関数を用いて下記の様に表 す事が出来る。 x’ = x
y’ = ycosα - zsinα z’ = ysinα + zcosα
イベントハンドラのsender 引数と e 引数 各イベントハンドラのメソッド宣言(旧称:イベントプロシージャ)には、共通してsender と e の2 個の同じ引数が渡される。 イベントハンドラのメソッドの第1引数 sender には、イベントの発生源で有るコントロールのインス タンス(イベント発生源のオブジェクト)への参照が格納されて居る。 イベントハンドラのメソッドの第2引数は、System.EventArgs クラス、又は、其れを継承したクラス が指定されて居り、イベントに関連する補足情報が格納されて居る。 引数e には、特に付加情報の無い Click イベント等は、基本と成る System.EventArgs クラスが引き渡 されるが、付加情報が有る場合は、付加情報を含むクラスをSystem.EventArgs クラスを継承して作成
した物が引き渡される。例えば、MouseDown イベントや MouseMove イベントや MouseUp イベント の場合、System.Windows.Forms.MouseEventArgs クラスが其れに該当する。 此のクラスは、System.EventArgs クラスを継承して居て、マウスイベントに固有の情報を表現する能 力を有して居り、マウスポインタの座標値を表すX や Y、マウスボタンの状態を表す Button 等が定義 されて居る。 例えば、入力画面でマウスボタンが押し下げられた時の処理を記述したイベントハンドラのメソッド宣 言では、下記の様に定義されて居る。
private void picInput_MouseDown( object sender, MouseEventArgs e )
上記の sender は、イベント発生源のオブジェクト、即ち、picInput を表し、亦、e は、MouseDown
イベントに関連する補足情報(マウスポインタの座標値を表す X や Y、マウスボタンの状態を表す
Button 等)をプロパティと仕て持つオブジェクトを表す。
従って、MouseDown イベントが発生した時のマウスポインタの座標は、e.X と e.Y で知る事が出来、 亦、マウスボタンの状態は、e.Button で知る事が出来る。 静的クラスと静的クラスメンバ (参考) 静的クラスと静的クラスメンバは、クラスのインスタンスを作成せずにアクセス出来るデータや関数を 作成する際に使用する。静的クラスメンバを使用すると、オブジェクト ID に依存しないデータと動作 を分離する事が出来る。分離されたデータと関数は、オブジェクトに何の様な処理が行われたかに拘ら ず、変更されない。静的クラスは、オブジェクトID に依存するデータや動作がクラスに存在しない時 に使用する事が出来る。 静的クラスは、静的メンバ丈を含むクラスと仕て宣言する。new キーワードを使用して静的クラスのイ ンスタンスを作成する事は出来ない。静的クラスは、クラスを含むプログラムや名前空間が読み込まれ ると、.NET Framework 共通言語ランタイム(CLR)に依り自動的に読み込まれる。 特定のオブジェクトに関連付けられないメソッドを利用するには、静的クラスを使用する。例えば、イ ンスタンスデータで機能せず、コード内の特定のオブジェクトに関連付けられて居ないメソッドのセッ トを作成する事が良く有るが、静的クラスは、此の様なメソッドを定義する為に使用出来る。
static 修飾子 (参考) static 修飾子は、静的メンバの宣言に使用する。静的メンバは、特定のオブジェクトでは無く、型自体 に属するメンバで有る。static 修飾子は、クラス、フィールド、メソッド、プロパティ、演算子、イベ ント、及び、コンストラクタと組み合わせて使用出来るが、インデクサ、デストラクタ、又は、クラス 以外の型で使用する事は出来ない。例えば、下記のクラスは、static と仕て宣言され、static メソッド 而巳が含まれる事に成る。
static class CompanyEmployee {
public static string GetCompanyName(string name) { ... } public static string GetCompanyAddress(string address) { ... } }
・定数宣言や型宣言は、暗黙に静的メンバで有る。
・静的メンバは、インスタンスを使用して参照する事は出来ない。代わりに、型の名前を使用して参照 する。例えば、下記のクラスに付いて考えて観る事にする。
public class MyBaseC {
public struct MyStruct {
public static int x = 100; } } 静的メンバx を参照するには、同じスコープで静的メンバにアクセス可能で無い限り、下記の様に完 全名を使用する。 MyBaseC.MyStruct.x ・クラスのインスタンスには同クラスの総てのインスタンスフィールドのコピーが別に含まれるが、各 静的フィールドのコピーは1 個丈で有る。 ・静的メソッドや静的プロパティアクセサへの参照には、this は使用する事が出来ない。 ・static キーワードをクラスに適用する場合、其のクラスの総てのメンバが静的で有る事が必要で有る。 ・静的クラスを含め、クラスには静的コンストラクタを含める事が出来る。静的コンストラクタは、プ ログラム開始時点からクラスがインスタンス化される時点迄の間に呼び出される。 静的メンバを例示する為に、或る企業の従業員を表すクラスを考えて観る。此のクラスには、従業員の 数を数えるメソッドと、従業員の数を格納するフィールドが有ると仮定する。メソッドとフィールドは 共に、従業員の何のインスタンスにも属して居ない。代わりに、其等は、企業のクラスに属して居る。 此の為、クラスの静的なメンバと仕て宣言する必要が有る。