前回はUnityをつかって反発係数が異なるボールを地面に落としてその運動について調べました。Unityでは反発係数を1.0にするとだんだんボールの跳ね返りが強くなるという不思議な現象がおきました。
今回はいつもつかっているWindowsFormsアプリケーションをつかって検証をしてみることにします。
まず物体の自由落下の公式ですが、
落下速度 = 重力加速度 × 時間
落下する距離 = 重力加速度 × 時間の二乗 × 0.5 です。
物体が地面に当たったときは上方向の力がかかります。速度は下向きから上向きにかわります。そのときの上向きの速度ですが、これは反発係数によります。反発係数が0であれば速度は0、地面に落ちた物体はまったく動かないし、反発係数が1.0であるなら地面に衝突する直前の速度が逆向きになるだけで速さはかわりません。反発係数が1.0であるなら物体を落とした高さまで跳ね上がり、永久に跳ね続けることになります。
地面と衝突して上向きに移動方向を変えた場合、これは地面からの投げ上げと同じです。このときの物体の高さは公式より、
時間 t 経過後の物体の高さ = 初速度 × 時間 - 重力加速度 × 時間の二乗 × 0.5
上向きを+として考えるため、下向きにかかる重力加速度はマイナスになります。
ではこれをつかってプログラミングしてみましょう。
最初はこんなコードを書いてみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
public partial class Form1 : Form { Timer Timer = new Timer(); int BallDiameter = 20; // ボールの直径 double v0 = 0; // ボールの初速度 int y0 = 0; // ボールの初期Y座標 double t = 0; // 経過時間 int h = 100; // 物体を落とす高さ // ボールがみえない位置にくるかもしれないので左と上に余白をおく int margin = 30; public Form1() { InitializeComponent(); Timer.Interval = 100; Timer.Tick += Timer_Tick; Timer.Start(); DoubleBuffered = true; BackColor = Color.White; // 背景を白にする(とくに意味はない) } private void Timer_Tick(object sender, EventArgs e) { t += 0.2; // 早く動かすために時間の経過速度を倍にする this.Invalidate(); } private void Form1_Paint(object sender, PaintEventArgs e) { int y = (int)(v0 * t + 9.8 / 2 * t * t); // ボールのY座標が(高さ - ボールの直径)より大きくなったら地面に接触したことになる if (y + y0 > h - BallDiameter) { // このときのボールの落下速度を取得 // ボールは上方に移動するので投げ上げの処理 v0 = -(v0 + 9.8 * t); // 投げ上げにおけるボールの初速度はボールの落下速度の逆 // ボールの位置をリセット。 // ボールのY座標とY座標の初期値を地面に接触している状態にする y0 = h - BallDiameter; y = h - y0 - BallDiameter; // ここからは投げ上げの処理になるので 経過時間は0にリセット t = 0; } int ballX = margin; int ballY = y + y0 + margin; e.Graphics.FillEllipse(new SolidBrush(Color.Blue), new Rectangle(ballX, ballY, BallDiameter, BallDiameter)); // 地面に相当する部分に直線を描画する e.Graphics.DrawLine(new Pen(Color.Black), new Point(0, h + margin), new Point(500, h + margin)); } } |
ここでは反発係数は1.0であるとしています。そして実際に実行してみるとUnityで実験したときと同じようにボールが跳ね上がる高さがだんだん高くなっていきます。
なぜだ???
ボールのY座標が(高さ – ボールの直径)より大きくなったら地面に接触したことになるので、ここでボールの初速度を設定しているのですが、実際にはボールはそれよりも低い位置まですすんでいるかもしれません。そのときの速度を新たなボールの初速度にするとボールは実際に地面に当たったときよりも速い速度で跳ね返ることになります。おそらくこれが原因であると思われます。
ではどうすればいいかというとボールが地面にあたる直前の速度を使えばよいということになります。Form1_Paintをちょっとだけ変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
public partial class Form1 : Form { // 跳ね返る直前の速度がわかるように保存しておく double lastV = 0; private void Form1_Paint(object sender, PaintEventArgs e) { int y = (int)(v0 * t + 9.8 / 2 * t * t); if (y + y0 > h - BallDiameter) { // 跳ね返るときの初速度は(v0 + 9.8 * t)を使わず保存しておいたlastVを使う v0 = -lastV; t = 0; y0 = h - BallDiameter; y = h - y0 - BallDiameter; } else { lastV = (v0 + 9.8 * t); } // if文を抜けたあとの処理は同じ } } |
これだと反発係数を1.0にすると同じ高さで跳ね続けます。
では反発係数を0.0~1.0にして複数のボールをつかって検証してみましょう。各ボールの初速度、経過時間、各ボールのYの初期座標、各ボールが地面に衝突する直前の速度は配列にしました。
最後のGetValueメソッドで反発係数を0.0から1.0まで0.1刻みで設定していますが、ここを変えると0.8から1.0まで0.02刻みなど他の条件で動作を検証することができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
public partial class Form1 : Form { Timer Timer = new Timer(); int h = 100; // 物体を落とす高さ int BallDiameter = 20; // ボールの直径 int margin = 30; // 余白 const int BALL_COUNT = 11; // ボールの個数 double[] initialVelocitys = new double[BALL_COUNT]; // 各ボールの初速度 double[] elapsedTimes = new double[BALL_COUNT]; // 経過時間 int[] initialYPositions = new int[BALL_COUNT]; // 各ボールのYの初期座標 double[] lastVelocitys = new double[BALL_COUNT]; // 各ボールが地面に衝突する直前の速度 // コンストラクタは上記と同じ private void Timer_Tick(object sender, EventArgs e) { for (int i = 0; i < BALL_COUNT; i++) elapsedTimes[i] += 0.2; this.Invalidate(); } private void Form1_Paint(object sender, PaintEventArgs e) { for (int i = 0; i < BALL_COUNT; i++) { double t = elapsedTimes[i]; int y = (int)(initialVelocitys[i] * t + 9.8 / 2 * t * t); if (y + initialYPositions[i] > h - BallDiameter) { initialVelocitys[i] = -lastVelocitys[i] * GetValue(i); elapsedTimes[i] = 0; initialYPositions[i] = h - BallDiameter; y = h - initialYPositions[i] - BallDiameter; } else { lastVelocitys[i] = (initialVelocitys[i] + 9.8 * t); } int ballX = i * 40 + margin; int ballY = (y + initialYPositions[i]) + margin; e.Graphics.FillEllipse(new SolidBrush(Color.Blue), new Rectangle(ballX, ballY, BallDiameter, BallDiameter)); } e.Graphics.DrawLine(new Pen(Color.Black), new Point(0, h + margin), new Point(600, h + margin)); } double GetValue(int i) { return 0.1 * i; } } |
0.0から1.0まで0.1刻みで設定
0.8から1.0まで0.02刻み