弦の動きのアニメーション

昨日のはほぼ本の通りのプログラムだったんで、それはそれであれなので、ちゃんと自力で組んでみました。
ということで、アニメーションさせてみました。すごく、なまめかしい動きをします。なかなか面白い動きなので、ぜひ自分でも動かしてみてください。このソースだけで完結してるので、コピペしてコンパイルすれば動かせます。

本に載ってたのは、時間の範囲を決めてその範囲の値を全部まとめて求めるという形で、アニメーションができなかったので、ちょっと前のデータから次の時間の値を求めるという形に書き直しました。
VIEWSTEPの値を大きくすると動きが速くなり、小さくすると動きが遅くなります。

※追記 2023/4/30 動画ツイートを置いておきます

//WaveAnim.java
import java.awt.*;
import java.awt.geom.Path2D;
import java.awt.image.BufferedImage;
import javax.swing.*;

public class WaveAnim {
    public static void main(String[] args){
        final int TDIV = 1000; //時間の分割数
        final int XDIV = 40; //座標の分割数
        final int VIEWSTEP = 20;//何コマごとに描画するか
        
        final Image img = new BufferedImage(400, 300, BufferedImage.TYPE_INT_RGB);
        //画面表示
        JFrame f = new JFrame("波動方程式のアニメ");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(400, 300);
        final JLabel label = new JLabel(new ImageIcon(img));
        f.add(label);
        f.setVisible(true);

        new Thread(){
            @Override
            public void run(){
                double[][] data = new double[3][XDIV];
                double a = (double)XDIV * XDIV / TDIV / TDIV;

                //初期値
                //最初の形
                int m = XDIV / 4;
                for(int x = 0; x < m; ++x){
                    data[0][x] = (double)x / m;
                }
                for(int x = m; x < XDIV; ++x){
                    data[0][x] = (double)(XDIV - x - 1) / (XDIV - m);
                }
                //初速の計算
                for (int x = 1; x < XDIV - 1; ++x) {
                    data[1][x] = data[0][x] + a * (data[0][x + 1] - 
                            2 * data[0][x] + data[0][x - 1]) / 2;
                }
                //動きの計算
                boolean first = true;//最初はちょっと停止しておくため
                int count = 0;//数回に一回描画するためのカウンター
                for(;;){
                    for (int i = 0; i < 100; ++i) {
                        double[] pre = new double[XDIV];
                        double p = 0;
                        double sum = 0;
                        for (int x = 1; x < XDIV - 1; ++x) {
                            //値の更新
                            pre[x] = data[2][x];
                            data[2][x] = 2 * data[1][x] - data[0][x] + 
                                    a * (data[2][x + 1] - 2 * data[2][x] + data[2][x - 1]);
                            //変位の計算
                            p += Math.abs(pre[x] - data[2][x]);
                            sum += Math.abs(data[2][x]);
                        }
                        //変位が少なければ収束したことにする
                        if (p < sum * 0.1) break;
                    }
                    ++count;
                    if(count > VIEWSTEP || first){//描画するのは数回に一回
                        count = 0;
                        //グラフの描画
                        Graphics2D g = (Graphics2D) img.getGraphics();
                        g.setColor(Color.WHITE);
                        g.fillRect(0, 0, 400, 300);
                        g.setStroke(new BasicStroke(2));
                        g.setColor(Color.BLACK);
                        Path2D p = new Path2D.Double();
                        p.moveTo(30, 150 - data[0][0]);
                        for (int x = 1; x < XDIV; ++x) {
                            p.lineTo(x * 300 / XDIV + 30, 150 - data[0][x] * 100);
                        }
                        g.draw(p);
                        g.dispose();
                        label.repaint();
                        try{
                            Thread.sleep(first ? 1000 : 20);
                            first = false;
                        } catch (InterruptedException ex) {
                        }
                    }
                    //データを進める
                    data[0] = data[1];
                    data[1] = data[2];
                    data[2] = new double[XDIV];
                }
            }
        }.start();
    }
}