// 渦糸系の力学系のシミュレーション // (c)kou // 2003.02.11完成 // // modified by mk (2003/3/19) Runge-Kutta 法を使うように変更 import java.applet.*; import java.awt.*; import java.awt.event.*; final public class vortex extends Applet implements Runnable, ActionListener { private Image off; // オフスクリーン private Graphics grf; // オフスクリーンのグラフィックス private Thread th = null; // スレッド private Dimension d; // アプレットのサイズ private Dimension imgD; // オフスクリーンのサイズ private double dt = 0.2; // 時刻の増分 (刻み幅) private int N = 5; // 渦糸の個数 private int MaxN = 12; private double scale = 20;// 拡大率 private int sleepTime=10; // スレッドの待ち時間 private double[] Gamma; // 渦の強さ (ガンマ) private double[] Z; // 渦糸の位置 (k番目の渦の位置は (Z[2*k],Z[2*k+1])) private double [] Z2, f, k1, k2, k3, k4; // Runge-Kutta 法の計算用変数 private String[] btString = {"Start", "End"}; // ボタンの名前 private Button[] bt; // ボタン private String[] laString1={"dt","N","scale","sleepTime"}; // ラベルの名前 private String[] laString2={"k","Γ","X","Y"}; // ラベルの名前 private Label[] la1, la2, la3; // ラベル private TextField[] tf1; // ラベルに対するテキストフィールド private TextField[] tfGamma; // Γの値のテキストフィールド private TextField[] tfX; // 渦糸の初期位置のx座標のテキストフィールド private TextField[] tfY; // 渦糸の初期位置のy座標のテキストフィールド private Color[] color1; // 色合いの薄い色 (未使用!) private Color[] color2; // 色合いが中くらいの色 public void init() { int i; // カウンタ setBackground(Color.white);//背景色の設定 d=getSize();//アプレットのサイズの取得 imgD=new Dimension(d.width-200,d.height);//オフスクリーンの色の設定 // 色の作成 color1 = new Color[MaxN]; color2 = new Color[MaxN]; for (i = 0; i < MaxN;i++) { float a = i / (float)MaxN; color1[i]=Color.getHSBColor(a, 0.2f, 1.0f); color2[i]=Color.getHSBColor(a, 0.5f, 1.0f); } // オフスクリーンの準備 off = createImage(imgD.width,imgD.height); // オフスクリーンの作成 grf = off.getGraphics(); // グラフィックスの取得 // オフスクリーンのクリア grf.setColor(Color.black); // 背景色の設定 grf.fillRect(0,0,imgD.width,imgD.height); // 塗りつぶす // オフスクリーンに座標軸を描く grf.setColor(Color.gray); // 軸の色の設定 grf.drawLine(0,imgD.height/2,imgD.width,imgD.height/2); // x軸 grf.drawLine(imgD.width/2,0,imgD.width/2,imgD.height); // y軸 // レイアウトマネージャを使わない (自分で設定する)) setLayout(null); // Start, End ボタンの準備 bt= new Button[btString.length]; // ボタンの配列の生成 for (i = 0; i < bt.length; i++) { bt[i] = new Button(btString[i]); // ボタンの生成 bt[i].addActionListener(this); // リスナーに関連付け bt[i].setBounds(imgD.width,20*i,d.width-imgD.width,20);//ボタンの位置決め add(bt[i]); //ボタンの登録 } // dt, N, scale, sleepTime のラベル、テキスト・フィールドの準備 la1 = new Label[laString1.length]; // ラベルの宣言 tf1 = new TextField[laString1.length]; // テキストフィールドの宣言 for (i = 0; i < la1.length; i++) { la1[i] = new Label(laString1[i]); // ラベルの生成 la1[i].setBounds(imgD.width, 20*(i+bt.length), (d.width-imgD.width)/2, 20); // ラベルの位置決め add(la1[i]); // ラベルの登録 tf1[i]=new TextField(); // テキストフィールドの生成 tf1[i].setBounds(imgD.width+(d.width-imgD.width)/2, 20*(i+bt.length), (d.width-imgD.width)/2, 20); // テキストフィールドの位置決め add(tf1[i]); // テキストフィールドの登録 } // k, Γ, X, Y というラベルの準備 la2 = new Label[laString2.length]; // ラベルの配列の生成 for (i = 0; i < laString2.length; i++) { la2[i] = new Label(laString2[i]); // ラベルの生成 la2[i].setBounds(imgD.width+(d.width-imgD.width)*i/4, 20*(bt.length+la1.length), (d.width-imgD.width)/4, 20); // ラベルの位置決め add(la2[i]); // ラベルの登録 } // 各渦糸のデータ (強さ、初期位置) のラベル、テキストフィールド la3 = new Label[MaxN]; // ラベルの配列の生成 tfGamma = new TextField[MaxN]; // テキストフィールドの生成 tfX = new TextField[MaxN]; // テキストフィールドの生成 tfY = new TextField[MaxN]; // テキストフィールドの生成 for (i = 0; i < la3.length; i++) { la3[i] = new Label("" + (i + 1)); // ラベルの生成 la3[i].setBounds(imgD.width+(d.width-imgD.width)*0/4, 20*(bt.length+la1.length+1+i), (d.width-imgD.width)/4, 20); // ラベルの位置決め la3[i].setBackground(color2[i]); // 背景の設定 add(la3[i]); // ラベルの登録 tfGamma[i] = new TextField(); // テキストフィールドの生成 tfGamma[i].setBounds(imgD.width+(d.width-imgD.width)*1/4, 20*(bt.length+la1.length+1+i), (d.width-imgD.width)/4, 20); // ラベルの位置決め add(tfGamma[i]); // テキストフィールドの登録 tfX[i]=new TextField(); // テキストフィールドの生成 tfX[i].setBounds(imgD.width+(d.width-imgD.width)*2/4, 20*(bt.length+la1.length+1+i), (d.width-imgD.width)/4, 20); // ラベルの位置決め add(tfX[i]); // テキストフィールドの登録 tfY[i]=new TextField(); // テキストフィールドの生成 tfY[i].setBounds(imgD.width+(d.width-imgD.width)*3/4, 20*(bt.length+la1.length+1+i), (d.width-imgD.width)/4, 20); // ラベルの位置決め add(tfY[i]); // テキストフィールドの登録 } // 計算用の配列を確保する Gamma = new double[MaxN]; Z = new double[2*MaxN]; Z2 = new double[2*MaxN]; f = new double[2*MaxN]; k1 = new double[2*MaxN]; k2 = new double[2*MaxN]; k3 = new double[2*MaxN]; k4 = new double[2*MaxN]; // 渦の強さ, 初期位置の決定 for (i = 0; i < MaxN; i++) { Gamma[i]=1; Z[2*i] = Math.pow(-1.0,i)*i; Z[2*i+1] = 0; } // 渦の強さ, 初期位置をテキストフィールドに表示 tf1[0].setText("" + dt); tf1[1].setText("" + N); tf1[2].setText("" + scale); tf1[3].setText("" + sleepTime); for (i = 0; i < tfGamma.length; i++) { tfGamma[i].setText("" + Gamma[i]); tfX[i].setText("" + Z[2*i]); tfY[i].setText("" + Z[2*i+1]); } // スタートボタンを無効化 bt[0].setEnabled(false); } public void paint(Graphics g) { update(g); } public void update(Graphics g) { g.drawImage(off, 0, 0, this); // オフスクリーンの表示 } // スレッドを生成してスタート public void start() { if (th == null) { th = new Thread(this); th.start(); } } // スレッドを止める public void stop() { if (th != null) { th=null; } } // 座標変換 (x座標) private int scx(double x) { return (int)(scale * x) + imgD.width / 2; } // 座標変換 (y座標) private int scy(double y) { return - (int)(scale * y) + imgD.height / 2; } // ユーザーの座標系で線分を描く private void MydrawLine(double x1, double y1, double x2, double y2) { grf.drawLine(scx(x1), scy(y1), scx(x2), scy(y2)); } // 渦糸系のベクトル場 f(z) の計算 public void aux(double []z, double []f, int N) { double doublePI = 2.0 * Math.PI; double sumx, sumy; for (int i = 0; i< N; i++) { sumx= 0; sumy= 0; for (int j = 0; j < N; j++) if (j != i) { double seki = Gamma[j] / (sqr(z[2*i]-z[2*j])+sqr(z[2*i+1]-z[2*j+1])); sumx -= seki * (z[2*i+1]-z[2*j+1]); sumy += seki * (z[2*i]-z[2*j]); } f[2*i+1] = sumy / doublePI; f[2*i] = sumx / doublePI; } } // 計算スレッド public void run() { int i; int n = 2 * N; // 力学系の次元 // オリジナルではここで配列の宣言をしていたが、それでは遅すぎる // double [] Z2 = new double[n]; // double [] f = new double[n]; // double [] k1 = new double[n]; // double [] k2 = new double[n]; // double [] k3 = new double[n]; // double [] k4 = new double[n]; while (th != null) { // Runge-Kutta: next Z = Z + (k1 + 2 k2 + 2 k3 + k4) / 6 // k1 aux(Z, f, N); for (i = 0; i < n; i++) k1[i] = dt * f[i]; // k2 for (i = 0; i < n; i++) Z2[i] = Z[i] + 0.5 * k1[i]; aux(Z2, f, N); for (i = 0; i < n; i++) k2[i] = dt * f[i]; // k3 for (i = 0; i < n; i++) Z2[i] = Z[i] + 0.5 * k2[i]; aux(Z2, f, N); for (i = 0; i < n; i++) k3[i] = dt * f[i]; // k4 for (i = 0; i < n; i++) Z2[i] = Z[i] + k3[i]; aux(Z2, f, N); for (i = 0; i < n; i++) k4[i] = dt * f[i]; // 次のステップの値 (Runge-Kutta) for (i = 0; i < n; i++) Z2[i] = Z[i] + (k1[i] + 2 * (k2[i] + k3[i]) + k4[i]) / 6; // 軌跡を描く for (int k = 0; k < N; k++) { grf.setColor(color2[k]); MydrawLine(Z[2*k], Z[2*k+1], Z2[2*k], Z2[2*k+1]); } // 値の更新 for (i = 0; i < n; i++) Z[i] = Z2[i]; // オフスクリーンを描画する repaint(); // スレッドの処理 (田代君の真似) if (sleepTime > 0) { try { th.sleep(sleepTime); //スレッドを一時停止 } catch(InterruptedException e) {}; } th.yield(); //スレッドを一時譲る } } // イベントの処理 public void actionPerformed(ActionEvent e) { if (e.getSource() == bt[0]) { // Startボタンが押されたら //スタートボタンを無効化, エンドボタンを有効化してフォーカスを移す bt[0].setEnabled(false); bt[1].setEnabled(true); bt[1].requestFocus(); // テキストフィールドの値を変数に代入 dt = Double.valueOf(tf1[0].getText()).doubleValue(); N = Integer.parseInt(tf1[1].getText()); scale = Double.valueOf(tf1[2].getText()).doubleValue(); sleepTime = Integer.parseInt(tf1[3].getText()); for (int i = 0; i < MaxN; i++) { Gamma[i] = Double.valueOf(tfGamma[i].getText()).doubleValue(); Z[2*i] = Double.valueOf(tfX[i].getText()).doubleValue(); Z[2*i+1] = Double.valueOf(tfY[i].getText()).doubleValue(); } // N の範囲をチェックする if (N > MaxN) { // N が最大値を超えていたら MaxN にする N = MaxN; tf1[1].setText("" + N); //数値をテキストフィールドに表示 } if (N < 1) { // N が最大値を超えていたら MaxN にする N = 1; tf1[1].setText("" + N); //数値をテキストフィールドに表示 } // オフスクリーンのクリア grf.setColor(Color.black); // 背景色の設定 grf.fillRect(0,0,imgD.width,imgD.height); // 塗りつぶす (クリア) grf.setColor(Color.gray); // 軸の色の設定 grf.drawLine(0,imgD.height/2,imgD.width,imgD.height/2); // x軸 grf.drawLine(imgD.width/2,0,imgD.width/2,imgD.height); // y軸 // スレッドの開始 start(); } else if (e.getSource() == bt[1]) { // Endボタンが押されたら bt[0].setEnabled(true); // スタートボタンを有効化 bt[0].requestFocus(); // スタートボタンにフォーカスを移す bt[1].setEnabled(false); // エンドボタンを無効化 // スレッドをとめる stop(); } } // 二乗を返す private double sqr(double a) { return a * a; } }