ローレンツアトラクタをOpenTKで描いてみた、ただし球ではなく立方体で!
OpenGLを使うにしてもC#でコーディングしたいのでOpenTKを使うことにして、学習には、ほげさんの「ほげほげ草」ブログを参考にしたというか、ほぼコードを剽窃させていただいた。「学習」といっても十分に読み込んで、コーディングして理解したわけではなく、まだ何とかプログラムは動いているようだという水準である。
立体感ではさすがにUnityには及ばないが、デスクトッププログラムとしてコーディングするとどんな感じなのかを体験してみたかったので良しとしたい。
残念ながら以下の2点は諦めることにした。
- アトラクタ―の点を球面で表現すること。代わりに立方体とした。
- シミュレーションの機能。時間の経過とともに点を描画することは断念した。
2次元の描画で単にドットを上書きしていくだけならよいが、3次元の描画になると上書きはできなく、徐々に描画する点の数を増やしていく強引なやり方は点の数が少ないうちは、なんとなくうまくいっているように見えるが、点の数が増えてくると画面上は変化はなく、ただ無駄に計算しているという感じになる。更に、async/awaitの非同期処理で描画させようとするとうまくいかず、古いやり方でないと動いてくれなかったためである。
最初、Vector3構造体は float なので数値計算の中で使うのには少々不安があったので、doubleのベクトルを扱う独自のクラスを定義しようかと思った。
public class Vec3 { public double X; public double Y; public double Z; public Vec3( double x, double y, double z ) { this.X = x; this.Y = y; this.Z = z; } public static Vec3 operator +(Vec3 a, Vec3 b) { return new Vec3(a.X + b.X, a.Y + b.Y, a.Z + b.Z); } public static Vec3 operator -(Vec3 a, Vec3 b) { return new Vec3(a.X - b.X, a.Y - b.Y, a.Z - b.Z); } public static double operator *(Vec3 a, Vec3 b) { return (a.X * b.X + a.Y * b.Y + a.Z * b.Z); } }
素直にdoubleでインスタンス化したとき
a = new Vec3( 1.5, 2.7, 3.2 );
b = new Vec3( 4.1, 5.5, 6.3 );
a + b を計算すると以下の結果
x:5.6 y:8.2 z:9.5
一方、bのXをfloatにした場合は
a = new Vec3( 1.5, 2.7, 3.2 );
b = new Vec3( 4.1f, 5.5, 6.3 );
a + b を計算すると以下の結果のように異なる結果となった。
x:5.59999990463257 y:8.2 z:9.5
そもそも b の値は
x:4.09999990463257 y:5.5 z:6.3
となっていた。
まあ、「こんな感じかぁー!」と見て楽しむ程度なので多少の誤差があっても問題ないのだが、自作クラスは定義せずに普通に List
3次元に限定したかったが、配列のサイズは指定できないので以下のようにジャギ配列(リスト)も可能であるが、使う側で注意すればよいので今回の目的からして問題ないだろう。
using System; using System.Collections.Generic; namespace ListTest { class Program { [STAThread] static void Main() { List<double[]> pnts = new List<double[]>(); pnts.Add( new double[] { 1.5, 2.7, 3.2 } ); pnts.Add( new double[] { 4.1, 5.5 } ); pnts.Add( new double[] { 3.1, 4.5, 5.3, 6.4 } ); foreach ( var pnt in pnts ) { foreach ( var e in pnt ){ Console.Write(" {0}", e ); } Console.WriteLine(); } } } }
結果としては以下のようなイメージが描かれた。
アトラクタ―は重心に近いと想定される点の回りにコントロール画面から回転させることができ、回転角はオイラー角で指定するが、上の画像はθを20としたときのもの。
ソースコードは以下の通り。
// // Lorenz Attractor 3D with OpenTK // // using OpenTK.dll and OpenTK.GLControl.dll // // /reference:OpenTK.dll // /reference:OpenTK.GLControl.dll // using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Drawing.Drawing2D; using System.Threading; using System.Drawing.Imaging; using OpenTK; using OpenTK.Graphics; using OpenTK.Graphics.OpenGL; namespace LorenzAtrc3D { public class Form2 : Form { const int dimension = 3; const int num_angle = 3; int width = 2600, height = 1200; int iteration; internal double[] angle = new double[num_angle]; double[, ,] rot = new double[num_angle, dimension, dimension]; List<double[]> attr_pnts = new List<double[]>(); //Thread mythread; //bool running; double param_s = 10.0, param_r = 50.0, param_b = 8.0 / 3.0; // Lorenz Attractor Param double[] vec_a = new double[3]; double[] vec_b = new double[3]; private System.ComponentModel.IContainer components = null; protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows フォーム デザイナーで生成されたコード private void InitializeComponent() { this.glControl = new OpenTK.GLControl(); this.SuspendLayout(); // // glControl // this.glControl.BackColor = System.Drawing.Color.SkyBlue; this.glControl.Location = new System.Drawing.Point(10, 10); this.glControl.Margin = new System.Windows.Forms.Padding(7, 6, 7, 6); this.glControl.Name = "glControl"; this.glControl.Size = new System.Drawing.Size(width, height); Console.WriteLine(" width = {0} height = {1}",width,height); this.glControl.TabIndex = 0; this.glControl.VSync = false; // // Form2 // this.AutoScaleDimensions = new System.Drawing.SizeF(13F, 24F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(width, height); this.Controls.Add(this.glControl); this.Name = "Form2"; this.Text = "Lorenz Attractor 3D"; this.ResumeLayout(false); Console.WriteLine(" GL width = {0} height = {1}",this.glControl.Width,this.glControl.Height); // // saveFileDialog1 // this.saveFileDialog1 = new System.Windows.Forms.SaveFileDialog(); // image File this.saveFileDialog1.Filter = "jpgファイル|*.jpg|bmpファイル|*.bmp"; } #endregion private System.Windows.Forms.SaveFileDialog saveFileDialog1; private OpenTK.GLControl glControl; private void glControl_Load(object sender, EventArgs e) { // 背景色の設定 GL.ClearColor(glControl.BackColor); // ビューポートの設定 GL.Viewport(0, 0, glControl.Width, glControl.Height); // 視体積の設定 GL.MatrixMode(MatrixMode.Projection); Matrix4 proj = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver6, glControl.AspectRatio,0.1f,400.0f); GL.LoadMatrix(ref proj); // 視界の設定 GL.MatrixMode(MatrixMode.Modelview); Matrix4 look = Matrix4.LookAt(new Vector3(0, 0, -80.0f), Vector3.Zero, Vector3.UnitY); GL.LoadMatrix(ref look); // デプスバッファの使用 GL.Enable(EnableCap.DepthTest); // 光源の使用 GL.Enable(EnableCap.Lighting); float[] position = new float[] { 100.0f, 200.0f, -50.0f, 0.0f }; GL.Light(LightName.Light0, LightParameter.Position, position); GL.Enable(EnableCap.Light0); } private void glControl_Resize(object sender, EventArgs e) { // ビューポートの設定 GL.Viewport(0, 0, glControl.Width, glControl.Height); } public void glControl_Paint(object sender, System.Windows.Forms.PaintEventArgs e) { draw_attractor(); } public void draw_attractor() { GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); GL.Material(MaterialFace.FrontAndBack, MaterialParameter.Diffuse, Color4.Red); my_draw_attractor(); glControl.SwapBuffers(); } public void calc_attractor() { double x, y, z, xx, yy, zz, s, r, b, dt; int interval = 10; dt = 0.001; x = 5.0; y = 8.0; z = 10.0; s = param_s; r = param_r; b = param_b; double[] offset = new double[]{ 0.19700702, 0.1629469, 45.41953137 }; for (int i = 0; i < iteration; i++) { xx = x + s * (-x + y) * dt; yy = y + (-x * z + r * x - y) * dt; zz = z + (x * y - b * z) * dt; if ( i % interval == 0 ){ attr_pnts.Add( new double[] { x - offset[0], y - offset[1], z - offset[2] } ); } x = xx; y = yy; z = zz; } Console.WriteLine("calculation : attr_pnts.Count = {0}",attr_pnts.Count()); } public void my_draw_attractor() { double size = 0.5; SetMatrix(); foreach ( var pnt in attr_pnts ) { vec_b = new double[] { pnt[0], pnt[1], pnt[2] }; vec_a = RotateVec(vec_b); cube( vec_a[0], vec_a[1], vec_a[2], size); } } void cube(double x, double y, double z, double size) { double w, h, d; double half_size = size / 2.0; w = half_size; h = half_size; d = half_size; GL.PushMatrix(); GL.Begin(PrimitiveType.TriangleStrip); // right { GL.Normal3( Vector3.UnitX); GL.Vertex3( x+w, y+h, z-d); GL.Vertex3( x+w, y+h, z+d); GL.Vertex3( x+w, y-h, z-d); GL.Vertex3( x+w, y-h, z+d); } GL.End(); GL.Begin(PrimitiveType.TriangleStrip); // left { GL.Normal3(-Vector3.UnitX); GL.Vertex3( x-w, y-h, z-d); GL.Vertex3( x-w, y-h, z+d); GL.Vertex3( x-w, y+h, z-d); GL.Vertex3( x-w, y+h, z+d); } GL.End(); GL.Begin(PrimitiveType.TriangleStrip); // up { GL.Normal3( Vector3.UnitY); GL.Vertex3( x+w, y+h, z-d); GL.Vertex3( x-w, y+h, z-d); GL.Vertex3( x+w, y+h, z+d); GL.Vertex3( x-w, y+h, z+d); } GL.End(); GL.Begin(PrimitiveType.TriangleStrip); // down { GL.Normal3(-Vector3.UnitY); GL.Vertex3( x-w, y-h, z+d); GL.Vertex3( x-w, y-h, z-d); GL.Vertex3( x+w, y-h, z+d); GL.Vertex3( x+w, y-h, z-d); } GL.End(); GL.Begin(PrimitiveType.TriangleStrip); // front { GL.Normal3( Vector3.UnitZ); GL.Vertex3( x+w, y+h, z+d); GL.Vertex3( x-w, y+h, z+d); GL.Vertex3( x+w, y-h, z+d); GL.Vertex3( x-w, y-h, z+d); } GL.End(); GL.Begin(PrimitiveType.TriangleStrip); // back { GL.Normal3(-Vector3.UnitZ); GL.Vertex3( x+w, y-h, z-d); GL.Vertex3( x-w, y-h, z-d); GL.Vertex3( x+w, y+h, z-d); GL.Vertex3( x-w, y+h, z-d); } GL.End(); GL.PopMatrix(); } public void SetMatrix() { // Rotation Matrix 0 rot[0, 0, 0] = Math.Cos(angle[0]); rot[0, 0, 1] = -Math.Sin(angle[0]); rot[0, 0, 2] = 0.0; rot[0, 1, 0] = Math.Sin(angle[0]); rot[0, 1, 1] = Math.Cos(angle[0]); rot[0, 1, 2] = 0.0; rot[0, 2, 0] = 0.0; rot[0, 2, 1] = 0.0; rot[0, 2, 2] = 1.0; // Rotation Matrix 1 rot[1, 0, 0] = Math.Cos(angle[1]); rot[1, 0, 1] = 0.0; rot[1, 0, 2] = Math.Sin(angle[1]); rot[1, 1, 0] = 0.0; rot[1, 1, 1] = 1.0; rot[1, 1, 2] = 0.0; rot[1, 2, 0] = -Math.Sin(angle[1]); rot[1, 2, 1] = 0.0; rot[1, 2, 2] = Math.Cos(angle[1]); // Rotation Matrix 2 rot[2, 0, 0] = Math.Cos(angle[2]); rot[2, 0, 1] = -Math.Sin(angle[2]); rot[2, 0, 2] = 0.0; rot[2, 1, 0] = Math.Sin(angle[2]); rot[2, 1, 1] = Math.Cos(angle[2]); rot[2, 1, 2] = 0.0; rot[2, 2, 0] = 0.0; rot[2, 2, 1] = 0.0; rot[2, 2, 2] = 1.0; } public double[] RotateVec(double[] vec) { int i, j, k; double[] tmp1 = new double[3]; double[] tmp2 = new double[3]; double sum; for (i = 0; i < 3; i++) { tmp1[i] = vec[i]; } for (k = 0; k < 3; k++) { for (i = 0; i < 3; i++) { sum = 0.0; for (j = 0; j < 3; j++) { sum += rot[k, i, j] * tmp1[j]; } tmp2[i] = sum; } for (i = 0; i < 3; i++) { tmp1[i] = tmp2[i]; } } return tmp2; } public Form2(int width, int height, int iteration, double theta) { this.width = width; this.height = height; this.iteration = iteration; this.angle[1] = theta; InitializeComponent(); calc_attractor(); //イベントの追加 glControl.Load += glControl_Load; glControl.Paint += glControl_Paint; glControl.Resize += glControl_Resize; } internal void save_image() { if ( saveFileDialog1.ShowDialog() == DialogResult.OK ) { GLrefresh(); Image img = TakeScreenshot(); if ( saveFileDialog1.FilterIndex == 1 ){ img.Save(saveFileDialog1.FileName,ImageFormat.Jpeg); } else if ( saveFileDialog1.FilterIndex == 2 ){ img.Save(saveFileDialog1.FileName,ImageFormat.Bmp); } } } public Bitmap TakeScreenshot() { if (GraphicsContext.CurrentContext == null) throw new GraphicsContextMissingException(); int w = glControl.ClientSize.Width; int h = glControl.ClientSize.Height; Bitmap bmp = new Bitmap(w, h); System.Drawing.Imaging.BitmapData data = bmp.LockBits(glControl.ClientRectangle, System.Drawing.Imaging.ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb); GL.ReadPixels(0, 0, w, h, OpenTK.Graphics.OpenGL.PixelFormat.Bgr, PixelType.UnsignedByte, data.Scan0); bmp.UnlockBits(data); bmp.RotateFlip(RotateFlipType.RotateNoneFlipY); return bmp; } public void GLrefresh() { glControl.Refresh(); } }/* end Form2 */ public partial class Form1 : Form { Form2 fm2; // // 引数の解析 // private void AnalyzeArgs(ref int width, ref int height, ref int iteration, ref double theta) { int pos = -1, len; string opt, val; string [] args = Environment.GetCommandLineArgs(); if ( args.Length > 1 ){ for ( int i = 0; i < args.Length; i++ ){ pos = args[i].IndexOf(":"); if ( pos > 0 ) { len = args[i].Length; opt = args[i].Substring(0,pos); val = args[i].Substring(pos+1,len - pos -1); switch ( opt ) { case "/w": width = int.Parse(val); break; case "/h": height = int.Parse(val); break; case "/iteration": iteration = int.Parse(val); break; case "/theta": theta = double.Parse(val); break; default: break; } } } } } private System.ComponentModel.IContainer components = null; protected override void Dispose(bool disposing) { if (disposing && (components != null)) { components.Dispose(); } base.Dispose(disposing); } #region Windows Form Designer generated code private void InitializeComponent() { this.trackBar1 = new System.Windows.Forms.TrackBar(); this.trackBar2 = new System.Windows.Forms.TrackBar(); this.trackBar3 = new System.Windows.Forms.TrackBar(); this.textBox1 = new System.Windows.Forms.TextBox(); this.textBox2 = new System.Windows.Forms.TextBox(); this.textBox3 = new System.Windows.Forms.TextBox(); this.button1 = new System.Windows.Forms.Button(); this.button2 = new System.Windows.Forms.Button(); this.button3 = new System.Windows.Forms.Button(); this.label1 = new System.Windows.Forms.Label(); this.label2 = new System.Windows.Forms.Label(); this.label3 = new System.Windows.Forms.Label(); this.label4 = new System.Windows.Forms.Label(); this.label5 = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize)(this.trackBar1)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBar2)).BeginInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBar3)).BeginInit(); this.SuspendLayout(); // // trackBar1 // this.trackBar1.Location = new System.Drawing.Point(152, 65); this.trackBar1.Maximum = 50; this.trackBar1.Minimum = -50; this.trackBar1.Name = "trackBar1"; this.trackBar1.Size = new System.Drawing.Size(395, 90); this.trackBar1.TabIndex = 0; this.trackBar1.Scroll += new System.EventHandler(this.trackBar1_Scroll); // // trackBar2 // this.trackBar2.Location = new System.Drawing.Point(152, 128); this.trackBar2.Maximum = 50; this.trackBar2.Minimum = -50; this.trackBar2.Name = "trackBar2"; this.trackBar2.Size = new System.Drawing.Size(395, 90); this.trackBar2.TabIndex = 1; this.trackBar2.Scroll += new System.EventHandler(this.trackBar2_Scroll); // // trackBar3 // this.trackBar3.Location = new System.Drawing.Point(152, 193); this.trackBar3.Maximum = 50; this.trackBar3.Minimum = -50; this.trackBar3.Name = "trackBar3"; this.trackBar3.Size = new System.Drawing.Size(395, 90); this.trackBar3.TabIndex = 2; this.trackBar3.Scroll += new System.EventHandler(this.trackBar3_Scroll); // // textBox1 // this.textBox1.Location = new System.Drawing.Point(554, 65); this.textBox1.Name = "textBox1"; this.textBox1.Size = new System.Drawing.Size(100, 31); this.textBox1.TabIndex = 3; this.textBox1.Text = "0"; this.textBox1.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // // textBox2 // this.textBox2.Location = new System.Drawing.Point(554, 128); this.textBox2.Name = "textBox2"; this.textBox2.Size = new System.Drawing.Size(100, 31); this.textBox2.TabIndex = 4; this.textBox2.Text = "0"; this.textBox2.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // // textBox3 // this.textBox3.Location = new System.Drawing.Point(554, 193); this.textBox3.Name = "textBox3"; this.textBox3.Size = new System.Drawing.Size(100, 31); this.textBox3.TabIndex = 5; this.textBox3.Text = "0"; this.textBox3.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; // // button1 // this.button1.Location = new System.Drawing.Point(152, 310); this.button1.Name = "button1"; this.button1.Size = new System.Drawing.Size(200, 40); this.button1.TabIndex = 6; this.button1.Text = "start"; this.button1.UseVisualStyleBackColor = true; this.button1.Enabled = false; // // button2 // //this.button2.Location = new System.Drawing.Point(152, 583); this.button2.Location = new System.Drawing.Point(152, 383); this.button2.Name = "button2"; this.button2.Size = new System.Drawing.Size(200, 40); this.button2.TabIndex = 7; this.button2.Text = "stop"; this.button2.UseVisualStyleBackColor = true; this.button2.Enabled = false; // // button3 // this.button3.Location = new System.Drawing.Point(152, 453); this.button3.Name = "button3"; this.button3.Size = new System.Drawing.Size(200, 40); this.button3.TabIndex = 8; this.button3.Text = "save image"; this.button3.UseVisualStyleBackColor = true; this.button3.Click += new System.EventHandler(this.button3_Click); // // label1 // this.label1.AutoSize = true; this.label1.Font = new System.Drawing.Font("MS UI Gothic", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); this.label1.Location = new System.Drawing.Point(28, 12); this.label1.Name = "label1"; this.label1.Size = new System.Drawing.Size(316, 27); this.label1.TabIndex = 9; this.label1.Text = "Rotation (Eulerian Angular)"; // // label2 // this.label2.AutoSize = true; this.label2.Location = new System.Drawing.Point(104, 65); this.label2.Name = "label2"; this.label2.Size = new System.Drawing.Size(34, 24); this.label2.TabIndex = 10; this.label2.Text = "ψ"; // // label3 // this.label3.AutoSize = true; this.label3.Location = new System.Drawing.Point(101, 128); this.label3.Name = "label3"; this.label3.Size = new System.Drawing.Size(34, 24); this.label3.TabIndex = 11; this.label3.Text = "θ"; // // label4 // this.label4.AutoSize = true; this.label4.Location = new System.Drawing.Point(101, 193); this.label4.Name = "label4"; this.label4.Size = new System.Drawing.Size(34, 24); this.label4.TabIndex = 12; this.label4.Text = "φ"; // // label5 // this.label5.AutoSize = true; this.label5.Font = new System.Drawing.Font("MS UI Gothic", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(128))); this.label5.Location = new System.Drawing.Point(27, 264); this.label5.Name = "label5"; this.label5.Size = new System.Drawing.Size(213, 27); this.label5.TabIndex = 13; this.label5.Text = "Attractor Drawing"; // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(13F, 24F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(710, 790); this.Controls.Add(this.label5); this.Controls.Add(this.label4); this.Controls.Add(this.label3); this.Controls.Add(this.label2); this.Controls.Add(this.label1); this.Controls.Add(this.button3); this.Controls.Add(this.button2); this.Controls.Add(this.button1); this.Controls.Add(this.textBox3); this.Controls.Add(this.textBox2); this.Controls.Add(this.textBox1); this.Controls.Add(this.trackBar3); this.Controls.Add(this.trackBar2); this.Controls.Add(this.trackBar1); this.Name = "Form1"; this.Text = "Control Form"; //this.TopMost = true; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing); ((System.ComponentModel.ISupportInitialize)(this.trackBar1)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBar2)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.trackBar3)).EndInit(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.TrackBar trackBar1; private System.Windows.Forms.TrackBar trackBar2; private System.Windows.Forms.TrackBar trackBar3; private System.Windows.Forms.TextBox textBox1; private System.Windows.Forms.TextBox textBox2; private System.Windows.Forms.TextBox textBox3; private System.Windows.Forms.Button button1; private System.Windows.Forms.Button button2; private System.Windows.Forms.Button button3; private System.Windows.Forms.Label label1; private System.Windows.Forms.Label label2; private System.Windows.Forms.Label label3; private System.Windows.Forms.Label label4; private System.Windows.Forms.Label label5; public Form1() { int width =2600; int height =1200; int iteration = 48000; double theta = 0.0; InitializeComponent(); AnalyzeArgs(ref width, ref height, ref iteration, ref theta); fm2 = new Form2( width, height, iteration, theta); fm2.Show(); } private void trackBar1_Scroll(object sender, EventArgs e) { textBox1.Text = (trackBar1.Value).ToString(); ((Form2)fm2).angle[0] = ((double)trackBar1.Value) * Math.PI / 100.0; ((Form2)fm2).draw_attractor(); } private void trackBar2_Scroll(object sender, EventArgs e) { textBox2.Text = (trackBar2.Value).ToString(); ((Form2)fm2).angle[1] = ((double)trackBar2.Value) * Math.PI / 100.0; ((Form2)fm2).draw_attractor(); } private void trackBar3_Scroll(object sender, EventArgs e) { textBox3.Text = (trackBar3.Value).ToString(); ((Form2)fm2).angle[2] = ((double)trackBar3.Value) * Math.PI / 100.0; ((Form2)fm2).draw_attractor(); } private void SetEnabled(bool val) { trackBar1.Enabled = val; trackBar2.Enabled = val; trackBar3.Enabled = val; button1.Enabled = val; button3.Enabled = val; } private void button1_Click(object sender, EventArgs e) { SetEnabled(false); } private void button2_Click(object sender, EventArgs e) { SetEnabled(true); } private void button3_Click(object sender, EventArgs e) { ((Form2)fm2).save_image(); } private void Form1_FormClosing(object sender, FormClosingEventArgs e) { //((Form2)fm2).stop_drawing(); } }/* end Form1 */ static class Program { [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } } }
以上