/**************************************************************
*  ボールの衝突(完全弾性衝突)シミュレーション(2)         *
*   BALL01  ver 1.1 (1999/04/03)                              *
*  Copyright(c) 1999 Yamada,K    (1999.02.20)                 *
*                                                             *
*  エネルギーも表示する                                       *
*  個々のボールのエネルギー変化は、対応する色で表示し         *
*  全エネルギーは黄色で表示する                               *
*  スペースキーで描画を終了する                               *
***************************************************************
*/

#include <iostream.h>
#include <time.h>
#include "glibw32.h"// グラフィックス・クラスライブラリ GLIBW32

const int size_x = 640, size_y = 200, size_y2 = 200;
const double ZERO = 1.E-20;      // ゼロと見なせる
const double PI = atan(1.0)*4.0; // 円周率
const int time_max = 1000;

// ボール・クラス
class Ball{
    public:
        Ball();                // デフォルト・コンストラクタ
        ~Ball() {--count;}     // デストラクタ
        void draw(GRAPH *);    // ボールの描画
        void non_coll();       // 衝突しないボールの次の位置を計算
        void collision_w();    // 壁との衝突を調べ次の位置を計算
        void collision(Ball &); // ボール同士の衝突を調べ次の位置を計算
        int getcolor() const { return color;}
        double getenergy() const { return m * v * v /2.0;}// エネルギーを計算
    private:
        static int count;  // ボールの個数
        int x_size;        // ウィンドウサイズ(x方向)
        int y_size;        // ウィンドウサイズ(y方向)
        int coll;          // 次の1秒間に他のボールと衝突するかどうか
        int color;         // ボールの色
        double x;          // ボールのx座標
        double y;          // ボールのy座標
        double v;          // ボールの速度
        double angle;      // ボールの運動方向(度)
        double r;          // ボールの半径
        double m;          // ボールの質量
        double x_next;     // ボールの次のx座標
        double y_next;     // ボールの次のy座標
        double dt;         // 時間間隔(次のステップまでの時間)
        int inside(double, double) const; // 領域の判定(0:外にある、1:内)
};

int Ball::count = 0; // 静的データメンバーの初期化

// デフォルト・コンストラクタ
Ball::Ball(): x_size(size_x), y_size(size_y), coll(0), dt(1.0)
{
    ++count;
    if(count == 1){
        time_t t;
        srand((unsigned) time(&t));	//乱数の初期化
    }

    x =( rand() % (x_size/2) ) + x_size/4;// x座標の初期値
    y =( rand() % (y_size/2) ) + y_size/4;// y座標の初期値
    angle = rand() % 360;                 // 角度の初期値
    v = rand() % 11 + 10;                 // 速度の初期値(10〜20)[m/s]
    r = rand() % 6 + 5;                   // 半径(5〜10)[m]
    m = r * r * r * 4./3.* PI * 0.0001;   // 質量 [Kg]
    color = count % 15 + 1;
    non_coll();
}

// ボールの描画
void Ball::draw(GRAPH *g)
{
    // 更新
    coll = 0;
    dt = 1.0;
    x = x_next;
    y = y_next;
    g->fcircle(x, y, r, color);// ボールを描く
}


// 衝突しない場合の次の位置を計算する
void Ball::non_coll()
{
    x_next = x + v*cos(PI*angle/180.0)*dt;
    y_next = y + v*sin(PI*angle/180.0)*dt;
}

// 壁との衝突
void Ball::collision_w()
{
    double x_min = r, x_max = x_size - r;
    double y_min = r, y_max = y_size - r;

    // 2回衝突(壁の角で2回衝突する場合)
    if( ( x_next <= x_min )  && ( y_next <= y_min ) ){
        double tx = ( x - x_min) / (x - x_next);
        double ty = ( y - y_min) / (y - y_next);
        x_next = x_min - dt*(1.0-tx)*v*cos(PI*angle/180.0);
        y_next = y_min - dt*(1.0-ty)*v*sin(PI*angle/180.0);
        angle = 180.0 + angle;
    }
    if( ( x_next >= x_max )  && ( y_next <= y_min ) ){
        double tx = ( x_max - x) / (x_next - x);
        double ty = ( y - y_min) / (y - y_next);
        x_next = x_max - dt*(1.0-tx)*v*cos(PI*angle/180.0);
        y_next = y_min - dt*(1.0-ty)*v*sin(PI*angle/180.0);
        angle = 180.0 + angle;
    }
    if( ( x_next <= x_min )  && ( y_next >= y_max ) ){
        double tx = ( x - x_min) / (x - x_next);
        double ty = ( y_max - y) / (y_next - y);
        x_next = x_min - dt*(1.0-tx)*v*cos(PI*angle/180.0);
        y_next = y_max - dt*(1.0-ty)*v*sin(PI*angle/180.0);
        angle = 180.0 + angle;
    }
    if( ( x_next >= x_max )  && ( y_next >= y_max ) ){
        double tx = ( x_max - x) / (x_next - x);
        double ty = ( y_max - y) / (y_next - y);
        x_next = x_max - dt*(1.0-tx)*v*cos(PI*angle/180.0);
        y_next = y_max - dt*(1.0-ty)*v*sin(PI*angle/180.0);
        angle = 180.0 + angle;
    }
    // 1回衝突
    if( ( x_next <= x_min )  && ( x >= x_min ) ){
        double t = ( x - x_min) / (x - x_next);
        angle = 180.0 - angle;
        x_next = x_min + dt*(1.0-t)*v*cos(PI*angle/180.0);
    }
    if( ( x_next >= x_max ) && ( x <= x_max ) ){
        double t = ( x_max -x ) / (x_next - x);
        angle = 180.0 - angle;
        x_next = x_max + dt*(1.0-t)*v*cos(PI*angle/180.0);
    }
    if( ( y_next <= y_min ) && ( y >= y_min ) ){
        double t = ( y - y_min ) / (y - y_next);
        angle =  - angle;
        y_next = y_min + dt*(1.0-t)*v*sin(PI*angle/180.0);
    }
    if( ( y_next >= y_max ) && ( y <= y_max ) ){
        double t = (  y_max - y) / (y_next - y);
        angle =  - angle;
        y_next = y_max + dt*(1.0-t)*v*sin(PI*angle/180.0);
    }
}

// ボール同士の衝突
void Ball::collision(Ball &B)
{
    if( this == &B)  return;     // 相手は自分自身
    if( coll == 1 ) return;      // 他のボールと衝突

    // 重心と重心速度
    double X1 = ( m * x + B.m * B.x ) / ( m + B.m);
    double Y1 = ( m * y + B.m * B.y ) / ( m + B.m);
    double X2 = ( m * x_next + B.m * B.x_next ) / ( m + B.m);
    double Y2 = ( m * y_next + B.m * B.y_next ) / ( m + B.m);
    double V_x = ( m * v * cos(PI*angle/180.0) +
                   B.m * B.v * cos(PI*B.angle/180.0) ) / ( m + B.m);
    double V_y = ( m * v * sin(PI*angle/180.0) +
                   B.m * B.v * sin(PI*B.angle/180.0) ) / ( m + B.m);

    if( (V_x * V_x + V_y * V_y) < ZERO ) return;

    // 重心座標系
    double v_p = (v*cos(PI*angle/180.0) - V_x)*(v*cos(PI*angle/180.0) - V_x)
                 + (v*sin(PI*angle/180.0) - V_y)*(v*sin(PI*angle/180.0) - V_y);
    v_p = sqrt(v_p); // 速さ
    double x1 = x -X1, y1 = y - Y1, x2 = x_next - X2, y2 = y_next - Y2;
    // 軌道直線の係数
    double a = y2- y1, b = x2 - x1, c = x2*y1 - x1*y2;

    double d = fabs(c)/sqrt(a*a + b*b);       // 衝突径数
    double R = B.m / ( m + B.m ) * (r + B.r) ;// 衝突距離

    // 衝突条件(1)
    if( d >= R) return;// 衝突しない

    double theta, phi, psi;

    if(fabs(b) < ZERO) theta = ( a*b > 0.0) ? 0.5*PI : -0.5*PI;
    else theta = atan2(a,b);

    phi = acos(d/R);

    double x_c, y_c, x_d, y_d;// 衝突位置
    if(fabs(b) < ZERO && x1 < 0 && y1 > 0 ) psi = PI - phi;
    else if(fabs(b) < ZERO && x1 > 0 && y1 > 0 ) psi = phi;
    else if(fabs(b) < ZERO && x1 < 0 && y1 < 0 ) psi = PI + phi;
    else if(fabs(b) < ZERO && x1 > 0 && y1 < 0 ) psi = - phi;
    else if(x1 < x2 && b*c > 0 ) psi = theta + phi + 0.5 * PI;
    else if(x1 > x2 && b*c > 0 ) psi = theta - phi - 0.5 * PI;
    else if(x1 < x2 && b*c < 0 ) psi = theta - phi - 0.5 * PI;
    else if(x1 > x2 && b*c < 0 ) psi = theta + phi + 0.5 * PI;
    else return;

    psi = fmod(psi,360.0);
    // 衝突位置
    x_c =  r*cos(psi);
    y_c =  r*sin(psi);
    // 衝突時刻
    double t = (fabs(b) < ZERO) ? ( y_c - y1 )/ a : ( x_c - x1 )/ b;

    // 衝突条件(2)衝突は次のステップ(1秒後)の前に起こる
    if(t<0.0 || t>1.0) return;

    // 衝突位置(実験室系)
    double xc = x + t*v*cos(PI*angle/180.0);
    double yc = y + t*v*sin(PI*angle/180.0);
    double xd = B.x + t*v*cos(PI*B.angle/180.0);
    double yd = B.y + t*v*sin(PI*B.angle/180.0);

    // 衝突条件(3) 衝突は領域内で起こる
    if( inside(xc,yc) == 0 ||  B.inside(xd,yd) == 0 ) return;

    // 衝突後の速度の計算
    double v_x, v_y;
    v_x = v_p * cos(2.0 * psi - theta + PI) + V_x;
    v_y = v_p * sin(2.0 * psi - theta + PI) + V_y;
    v = sqrt(v_x * v_x + v_y * v_y);
    if(fabs(v_x) < ZERO) angle = (v_x * v_y > 0.0) ?  90.0 : -90.0;
    else angle = (180.0 /PI ) * atan2(v_y , v_x);

    // 現在位置を衝突位置に更新
    x=xc;
    y=yc;
    dt = 1.0-t;
    non_coll();

    v_x = -( m / B.m ) * v_p * cos(2.0 * psi - theta + PI) + V_x;
    v_y = -( m / B.m ) * v_p * sin(2.0 * psi - theta + PI) + V_y;
    B.v = sqrt(v_x * v_x + v_y * v_y);
    if(fabs(v_x) < ZERO) angle = (v_x * v_y > 0.0) ?  90.0 : -90.0;
    else B.angle = (180.0 /PI ) * atan2(v_y , v_x);

    // 現在位置を衝突位置に更新
    B.x=xd;
    B.y=yd;
    B.dt = 1.0-t;
    B.non_coll();

    coll = 1;
    B.coll = 1;

    return;
}

// 領域の判定
int Ball::inside(double a, double b) const
{
    double x_min = r, x_max = x_size - r;
    double y_min = r, y_max = y_size - r;
    if(a <= x_min || a >= x_max || b <= y_min || b >= y_max ) return 0;
    else return 1;
}

main()
{
    int no;
    cout << "ボールの衝突運動のシミュレーションです。\n"
    << "スペースキーで描画を終了します。\n\n"
    << "ボールの個数を入力してください(2、3個がお薦め):";
    cin >> no;

    Ball *ptr = new Ball[no]; // ボールの生成
    double *e = new double[no];

    double energy = 0.0;
    for(int i = 0; i < no; ++i)
        energy += (ptr+i)->getenergy();
    cout << "開始時の全エネルギー: " << energy << " [N・m]" << endl;

    // グラフィックスの初期化
    ginit(size_x, size_y + size_y2);
    GRAPH *g = new GRAPH[2];

    // g[0]:ボールの描画、g[1]:エネルギーのグラフ
    double axis_x = time_max / 10.0;
    double axis_y = energy /10.0;
    g[0].window(0, 0, size_x, size_y);
    g[0].view(0, 0, size_x -1, size_y -1);
    g[1].window(0, - 0.1 * energy , time_max, energy * 1.1);
    g[1].view(0, size_y, size_x -1, size_y + size_y2 -1);
    g[1].axis(axis_x, axis_y);

    int count = 0; // 時刻のカウンタ

    while( vkey()  != VK_SPACE ){// スペースキーが押されるまで

        g[0].cls();   // 画面消去
        int i,j;
        // まずは衝突しないとして全ボールの次の位置を計算
        for(i = 0; i < no; ++i)
            (ptr+i)->non_coll();

        for(i = 0; i < no; ++i){
            // ボール同士の衝突を調べて、次の位置を修正
            for(j = i+1; j < no; ++j)
                (ptr+i)->collision(*(ptr+j));

            // 壁との衝突を調べて、次の位置を修正
            (ptr+i)->collision_w();
        }
        // ボールの描画
        for(i = 0; i < no; ++i)
            (ptr + i) -> draw(&g[0]);

        Sleep(20); // 実行を中断する時間をミリ秒単位で指定するWIN32 API関数

        // エネルギーをプロットする
        double energy_old = energy;
        energy = 0.0;
        for(i = 0; i < no; ++i){
            double e_old = e[i];
            g[1].moveto(count, e_old);
            e[i] = (ptr+i)->getenergy();
            int c = (ptr+i)->getcolor();
            g[1].lineto(count, e[i], c);
            energy += e[i];
        }
        g[1].moveto(count-1, energy_old);
        g[1].lineto(count, energy, YELLOW);// 全エネルギー
        ++count;
        count %= time_max;
        if(count == 0){
            g[1].cls();
            g[1].axis(axis_x, axis_y);
        }
    }

    cout << "終了時の全エネルギー: " << energy << " [N・m]" << endl;


    delete [] g;
    gend(); // グラフィックスの終了

    delete [] e;
    delete [] ptr; // ボールの削除

    return 0;
}


| 戻る |

Copyright(c) Yamada,K