今回は、空気抵抗があるときの物体の軌跡を描いてみました。下のプログラムでは、実際にはアニメーションさせてます。
※追記 2023/4/30 動画を投稿しました
空気抵抗がある中でモノを投げるシミュレーションhttps://t.co/zd6SCX8qE1 pic.twitter.com/IM2HbKomzc
— きしだൠ(K1S) (@kis) 2023年4月30日
2階以上の微分方程式は、
を、
と置くことで
という1階連立微分方程式に分解して、ルンゲクッタ法を使います。
このとき物体の位置x,yの微分方程式は、空気抵抗の係数をT、重力加速度をgとすると
となります。
ここで
と置くことで
のような1階微分方程式に変形して計算を行います。
プログラム中では、funcw・funcvメソッドが変形後の式にあたります。
こうやって2階微分方程式が解けるようになると、いろいろな物理現象がシミュレートできるようになって楽しいです。
import java.awt.*; import java.awt.image.BufferedImage; import javax.swing.*; public class RungeKutta2 { static double gt = 9.8;//重力加速度 static double tt = 0.008;//空気抵抗の係数 private static double funcv(double t, double v, double w, double x, double y) { return -gt - tt * v * Math.sqrt(w * w + v * v); } private static double funcw(double t, double v, double w, double x, double y) { return -tt * w * Math.sqrt(w * w + v * v); } private static double funcx(double t, double v, double w, double x, double y) { return w; } private static double funcy(double t, double v, double w, double x, double y) { return v; } public static void main(String[] args) { //表示準備 BufferedImage bi = new BufferedImage(400, 300, BufferedImage.TYPE_INT_RGB); final Graphics g = bi.getGraphics(); //画面に表示 JFrame f = new JFrame("ルンゲクッタ2階連立微分方程式"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setSize(400, 300); final JLabel label = new JLabel(new ImageIcon(bi)); f.add(label); f.setVisible(true); new Thread() { @Override public void run() { for(;;){ try { Thread.sleep(300); } catch (InterruptedException e) { } //初期条件 double h = .1;//刻み幅 double x = 0;//初期位置 double y = 0; double sp = Math.random() * 100 + 20;//初速 double th = (Math.random() * 3 / 4 + 1. / 8) * Math.PI;//角度 double v = sp * Math.sin(th); double w = sp * Math.cos(th); for (double t = 0; t < 20; t += h) { g.setColor(Color.WHITE); g.fillRect(0, 0, 400, 300); g.setColor(Color.RED); g.fillOval((int)(10 + x * 1.5), (int) (250 - y * 1.5), 7, 7); double vk1 = h * funcv(t, v, w, x, y); double wk1 = h * funcw(t, v, w, x, y); double xk1 = h * funcx(t, v, w, x, y); double yk1 = h * funcy(t, v, w, x, y); double vk2 = h * funcv(t + h/2, v + vk1/2, w + wk1/2, x + xk1/2, y + yk1/2); double wk2 = h * funcw(t + h/2, v + vk1/2, w + wk1/2, x + xk1/2, y + yk1/2); double xk2 = h * funcx(t + h/2, v + vk1/2, w + wk1/2, x + xk1/2, y + yk1/2); double yk2 = h * funcy(t + h/2, v + vk1/2, w + wk1/2, x + xk1/2, y + yk1/2); double vk3 = h * funcv(t + h/2, v + vk2/2, w + wk2/2, x + xk2/2, y + yk2/2); double wk3 = h * funcw(t + h/2, v + vk2/2, w + wk2/2, x + xk2/2, y + yk2/2); double xk3 = h * funcx(t + h/2, v + vk2/2, w + wk2/2, x + xk2/2, y + yk2/2); double yk3 = h * funcy(t + h/2, v + vk2/2, w + wk2/2, x + xk2/2, y + yk2/2); double vk4 = h * funcv(t + h, v + vk3, w + wk3, x + xk3, y + yk3); double wk4 = h * funcw(t + h, v + vk3, w + wk3, x + xk3, y + yk3); double xk4 = h * funcx(t + h, v + vk3, w + wk3, x + xk3, y + yk3); double yk4 = h * funcy(t + h, v + vk3, w + wk3, x + xk3, y + yk3); double vk = (vk1 + 2 * vk2 + 2 * vk3 + vk4) / 6; double wk = (wk1 + 2 * wk2 + 2 * wk3 + wk4) / 6; double xk = (xk1 + 2 * xk2 + 2 * xk3 + xk4) / 6; double yk = (yk1 + 2 * yk2 + 2 * yk3 + yk4) / 6; v += vk; w += wk; x += xk; y += yk; if (y < 0) v = -v; //反射させてみる label.repaint(); try { Thread.sleep(20); } catch (InterruptedException e) { } } } } }.start(); } }