ラベル Physics の投稿を表示しています。 すべての投稿を表示
ラベル Physics の投稿を表示しています。 すべての投稿を表示

2007年12月2日日曜日

相互作用が働く粒子の運動2

前回のスクリプトの改良版。
速度に上限をつけるのではなく、抵抗力による減速を導入することでより物理らしくなっています。ガタガタと動かずにきれいに収束するのも特徴です。

また、粒子の追加および初期化の機能をつけました。

Java移植版
#module member id, x, y, vx, vy
#include "hspmath.as"
// id     ... 個別認識用ID(int)
// x, y   ... 位置(double)
// vx, vy ... 速度(double)
#const  PARTY_DIST  120     // 仲間と保とうとする距離
#const  RADIUS      5       // 描画時の半径

#modinit double _x, double _y
    x = _x : y = _y : vx = 0.0 : vy = 0.0
    id = next_id : next_id++
    return

#deffunc make_member double new_x, double new_y
    newmod members, member, new_x, new_y
    return

// -----------------------------
// ここから各メンバーごとに用いる命令

// IDを返す
#modfunc get_id
    return id

// 位置を返す
#modfunc get_position array v
    v = x, y
    return

// 自分自身の速度の計算
#modfunc calc_speed
    ax = 0.0 : ay = 0.0
    // 仲間との相互作用(一定の距離を保とうとする)
    foreach members
        // 自分自身とは判定を行わない
        get_id members(cnt)
        if id == stat : continue

        // 仲間との距離を計算
        this_member_pos = x, y
        get_position members(cnt), target_member_pos
        dist = distance2(this_member_pos, target_member_pos)    // 対象の仲間との距離(double)

        // 仲間との角度を計算
        theta = atan(y - target_member_pos(1), target_member_pos(0) - x)

        // 距離に応じて加速
        s = absf(dist - PARTY_DIST) * (dist - PARTY_DIST) / 5000
        ax += cos(theta) * s
        ay += sin(theta) * s
    loop

    // 速度の変更(加速・減速)
    vx = 0.9 * (vx + ax)
    vy = 0.9 * (vy - ay)
    return

// 自分自身の移動
#modfunc move
    x = limitf(x + vx, 0ginfo_winx)
    y = limitf(y + vy, 0ginfo_winy)
    return

// 自分自身を描画
#modfunc draw
    circle x - RADIUS, y - RADIUS, x + RADIUS, y + RADIUS
    return

// -----------------------------
// ここから外部から呼び出す群れ全体を扱う命令

// 全員の速度を計算
#deffunc calc_all_speed
    foreach members
        calc_speed members(cnt)
    loop
    return

// 全員の移動
#deffunc move_all
    foreach members
        move members(cnt)
    loop
    return

// 全員の描画
#deffunc draw_all
    foreach members
        draw members(cnt)
    loop
    return

// 全員消去
#deffunc kill_all
    if vartype(members) != 5 : return
    foreach members
        delmod members(cnt)
    loop
    return
#global
    next_id@member = 0  // IDの初期化

// -----------------------------
// モジュールここまで

#const NUM_MEMBERS  12  // 群れを構成するメンバーの数(初期値)
    randomize
    onclick goto *flag_onclick
    title "左クリック:粒子の追加 / 右クリック:やりなおし"

*restart
    // すべてのメンバーを削除
    kill_all
    // 群れのメンバーをランダムな位置に発生
    repeat NUM_MEMBERS
        make_member rnd(ginfo_winx), rnd(ginfo_winy)
    loop
    // 色を決定
    color_h = rnd(192)

*main
    calc_all_speed  // 全員の速度を計算
    move_all        // 全員を移動
    redraw 0
        color : boxf : hsvcolor color_h, 255255
        draw_all        // 全員を描画
    redraw 1
    wait 3
    goto *main

*flag_onclick
    if iparam == 0 {
        // 左クリックなら、粒子を追加
        make_member mousexmousey
        goto *main
    } else {
        // それ以外は初期化
        goto *restart
    }

2007年12月1日土曜日

相互作用が働く粒子の運動

HSPコンテスト2007にて株式会社秀和システム章を受賞しました。
おにたまさんから「ある意味ではモジュールの達人」と言っていただけまして、とても嬉しいです。モジュール(変数)は1時期とっても勉強した部分なので……。
モジュール変数の扱いをもっと勉強していきたいですね。

というわけで受賞記念に、得意(?)のモジュールを利用したスクリプトを。
「遠い時はくっつきたがるけど、近い時は離れたがる」という特殊な粒子の運動のシミュレートです。現実では原子がこれにあたります。3次元ですが。

ひとつのモジュール変数でひとつの粒子を表しています。決まった距離を保つために、距離の2乗に従って速度を算出しています。力学のポテンシャルの考え方に近いでしょうか。

粒子数が同じなら、最初の条件が違っても大抵は同じ陣形に収束するのが面白いです。
#module member id, x, y, vx, vy
#include "hspmath.as"
// id     ... 個別認識用ID(int)
// x, y   ... 位置(double)
// vx, vy ... 速度(double)
#const  PARTY_DIST  120     // 仲間と保とうとする距離
#const  MAX_SPEED   4       // 最大スピード
#const  RADIUS      5       // 描画時の半径

#modinit int _x, int _y
    x = _x : y = _y
    id = next_id : next_id++
    return

#deffunc make_member double new_x, double new_y
    newmod members, member, new_x, new_y
    return

// -----------------------------
// ここから各メンバーごとに用いる命令

// IDを返す
#modfunc get_id
    return id

// 位置を返す
#modfunc get_position array v
    v = x, y
    return

// 自分自身の速度の計算
#modfunc calc_speed
    ax = 0.0 : ay = 0.0
    // 仲間との相互作用(一定の距離を保とうとする)
    foreach members
        // 自分自身とは判定を行わない
        get_id members(cnt)
        if id == stat : continue

        // 仲間との距離を計算
        this_member_pos = x, y
        get_position members(cnt), target_member_pos
        dist = distance2(this_member_pos, target_member_pos)    // 対象の仲間との距離(double)

        // 仲間との角度を計算
        theta = atan(y - target_member_pos(1), target_member_pos(0) - x)

        // 距離に応じて加速
        s = (dist - PARTY_DIST) * (dist - PARTY_DIST) * sgn(dist - PARTY_DIST) / 150
        ax += cos(theta) * s
        ay += sin(theta) * s
    loop

    // 速度の変更
    vx += ax : vy -= ay
    // 速度が大きすぎる場合は調節する
    velo = sqrt(vx * vx + vy * vy)
    if absf(velo) > MAX_SPEED {
        velo = limitf(velo, -MAX_SPEEDMAX_SPEED)
        theta = atan(vy, vx)
        vx = cos(theta) * velo
        vy = sin(theta) * velo
    }
    return

// 自分自身の移動
#modfunc move
    x = limitf(x + vx, 0ginfo_winx)
    y = limitf(y + vy, 0ginfo_winy)
    return

// 自分自身を描画
#modfunc draw
    circle x - RADIUS, y - RADIUS, x + RADIUS, y + RADIUS
    return

// -----------------------------
// ここから外部から呼び出す群れ全体を扱う命令

// 全員の速度を計算
#deffunc calc_all_speed
    foreach members
        calc_speed members(cnt)
    loop
    return

// 全員の移動
#deffunc move_all
    foreach members
        move members(cnt)
    loop
    return

// 全員の描画
#deffunc draw_all
    foreach members
        draw members(cnt)
    loop
    return

#global
    next_id@member = 0  // IDの初期化

// -----------------------------
// モジュールここまで

#const NUM_MEMBERS  12  // 群れを構成するメンバーの数
    // 群れのメンバーをランダムな位置に発生
    randomize
    repeat NUM_MEMBERS
        make_member rnd(ginfo_winx), rnd(ginfo_winy)
    loop

*main
    calc_all_speed  // 全員の速度を計算
    move_all        // 全員を移動
    redraw 0
        color : boxf : color ,,255
        draw_all        // 全員を描画
    redraw 1
    wait 3
    goto *main

2007年5月4日金曜日

砲弾の射出(d3module)

目標地点を指定すると、そのために必要な射出方向を計算する。
射出速度は一定なので、遠方を狙うほど早く着弾する。
// ang ... 角度, pos ... 位置(座標), velo... 速さ
// distance ... 距離, b ... 放物線の第2係数
#include "d3m.hsp"
#module powModule
#defcfunc pow double d1, int i2, local st
    st = 1.0
    repeat i2
        st *= d1
    loop
    return st
#global

#const GRAVITY  1.0                                 // 重力加速度
#const VELOCITY 30.1                                // 射出速度
#const DISTANCE_MAX VELOCITY * VELOCITY / GRAVITY   // 最大飛距離

#const GRID_MAX         8   // グリッドの本数
#const GRID_DISTANCE    100 // グリッドの間隔
#const TARGET_RADIUS    10  // 照準の半径
#const BULLET_RADIUS    8   // 砲弾の半径

    screen 0480480

    // 射出点の座標
    posCannonX = double(GRID_MAX) * GRID_DISTANCE / 2
    posCannonY = 0.0
    posCannonZ = 0.0

    // 照準の座標
    posTargetX = double(GRID_MAX) * GRID_DISTANCE / 2
    posTargetY = double(GRID_MAX) * GRID_DISTANCE / 2
    posTargetZ = 0.0

    d3setcam GRID_MAX * GRID_DISTANCE / 2, -300200GRID_MAX * GRID_DISTANCE / 2GRID_MAX * GRID_DISTANCE / 20

*main
    gosub *move
    gosub *draw
    wait 4
    goto *main

*move
    stick key, 15
    if key & 16 : gosub *shot

    // 照準の移動
    posTargetX += 10 * (((key >> 2) & 1) - (key & 1))
    posTargetY += 10 * (((key >> 1) & 1) - ((key >> 3) & 1))

    // 砲弾の移動
    if posBulletZ >= 0 {
        posBulletX += veloBulletX
        posBulletY += veloBulletY
        posBulletZ += veloBulletZ
        veloBulletZ -= GRAVITY
    }
    return

*shot
    // 射出 ここらへんのスクリプトの理解は力学と微分の知識が要ります
    posBulletX = posCannonX
    posBulletY = posCannonY
    posBulletZ = posCannonZ
    angBullet = atan(posTargetY - posCannonY, posTargetX - posCannonX)
    distance = limitf(sqrt(pow(posTargetX - posCannonX, 2) + pow(posTargetY - posCannonY, 2)), 0.0DISTANCE_MAX)
    b = DISTANCE_MAX / distance + sqrt(pow(DISTANCE_MAX / distance, 2) - 1)

    veloBulletR = sqrt(GRAVITY * distance / b / 2)
    veloBulletZ = b * veloBulletR
    veloBulletX = veloBulletR * cos(angBullet)
    veloBulletY = veloBulletR * sin(angBullet)
    return

*draw
    redraw 0
    color : boxf
    color 0255                // グリッド
    repeat GRID_MAX + 1
        d3line cnt * GRID_DISTANCE00cnt * GRID_DISTANCEGRID_MAX * GRID_DISTANCE0
        d3line 0cnt * GRID_DISTANCE0GRID_MAX * GRID_DISTANCEcnt * GRID_DISTANCE0
    loop

    color 255                   // 照準
    d3circle posTargetX, posTargetY, posTargetZ, TARGET_RADIUS0

    if posBulletZ >= 0 {
        color 00255         // 砲弾
        d3circle posBulletX, posBulletY, posBulletZ, BULLET_RADIUS
        color 191191191     // 影
        d3circle posBulletX, posBulletY, 0BULLET_RADIUS
    }
    redraw 1
    return

3次元空間を反射する光線

モジュール変数によるキャラクター管理法。
d3moduleを利用しています。

d3box命令を使って修正。(2007/10/02)
#include "d3m.hsp"
#const global BOX_X    200
#const global BOX_Y    300
#const global BOX_Z    150
#const CAMERA_DISTANCE 500
#const BALL_MAX         30
#module physics3d x, y, z, vx, vy, vz
#modinit double d1, double d2, double d3, double d4, double d5, double d6
    x = d1, d1, d1, d1
    y = d2, d2, d2, d2
    z = d3, d3, d3, d3
    vx = d4 : vy = d5 : vz = d6
    return

#modfunc moveBall
    x(1) = x, x(1), x(2)
    y(1) = y, y(1), y(2)
    z(1) = z, z(1), z(2)
    x += vx : y += vy : z += vz
    if ( x < 0 ) | ( BOX_X < x ) : vx *= -1
    if ( y < 0 ) | ( BOX_Y < y ) : vy *= -1
    if ( z < 0 ) | ( BOX_Z < z ) : vz *= -1
    return

#modfunc drawBall
    d3initlineto
    repeat 4
        d3lineto x(cnt), y(cnt), z(cnt)
    loop
    return
#global

    screen 0400370
    title "3次元を反射する光線"

    theta = 0.0          // カメラの角度 (極座標)
    phi = 3.14 / 2       // カメラの角度2(極座標)
    gosub *setcam
    repeat BALL_MAX
        newmod ball, physics3d, rnd(200), rnd(300), rnd(150), (rnd(9)-4)*3, (rnd(9)-4)*4, (rnd(9)-4)*3
    loop

*main
    gosub *moveCam
    gosub *draw
    wait 4
    goto *main

*moveCam
    stick key, 15
    if key & 15 {
        theta += 0.1*((key & 1) - (key >> 2 & 1))
        phi    = limitf(phi + 0.1*((key >> 1 & 1) - (key >> 3 & 1)), 0.13.13)
            // ↑ カメラのXY座標がターゲットと同じにならないための対策

        gosub *setcam
    }
    return

*draw
    redraw 0
    color : boxf
    hsvcolor 30255100
    d3box 000BOX_XBOX_YBOX_Z
    hsvcolor 30255255
    foreach ball
        moveBall ball(cnt)
        drawBall ball(cnt)
    loop
    redraw
    return

*setcam
    d3setcam sin(phi)*cos(theta)*CAMERA_DISTANCE + 100sin(phi)*sin(theta)*CAMERA_DISTANCE + 150cos(phi)*CAMERA_DISTANCE + 7510015075
    return