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

2007年12月20日木曜日

3D画像ビューア サンプル

Flashなどでありそうな3Dの画像ビューア。のサンプル。
マウスのホイールに連動して回転します。
#include "d3m.hsp"
#include "hspmath.as"
#const TILE_NUM 16
#const SHIFT_TURN 5
#const BUFFER_WIDTH 200
#const BUFFER_HEIGHT 200

    // タイルを表示するための配列変数
    tile_x = 100300300100
    tile_y =   0,   0,   0,   0
    tile_z = 200200,   0,   0

    // タイル用のバッファを作成
    repeat TILE_NUM1
        buffer cntBUFFER_WIDTHBUFFER_HEIGHT
        color , 255 : boxf
        color 255255255
        font msgothic32font_bold
        mes cnt
    loop

    // メイン画面の初期化・カメラ設定
    screen 0240200screen_normal
    title "ホイールで回転"
    d3setcam -250,-300,100250,0,100

    // 各種初期化作業
    gmode GMODE_ALPHA, , , 128
    theta_shift = 0.0                   // タイルの回転量
    theta_add = 0.0                     // タイルの角速度
    moving = 0                          // 回転中フラグ
    need_to_draw = 1                    // 描画フラグ

// ★メインループ
*main
    gosub *move
    gosub *draw
    wait 5
    goto *main

// 移動計算
*move
    mw = mousew
    if moving > 0 {
        // 移動中
        moving--
        theta_shift += theta_add
    } else : if mw != 0 {
        // 静止中かつホイールが動いたとき
        moving = SHIFT_TURN             // SHIFT_TURN回動く
        need_to_draw = 1                // 描画フラグON

        // 角速度を決定
        theta_add = M_PI * 2.0 * sgn(mw) / SHIFT_TURN / TILE_NUM
    }
    return

// 描画処理
*draw
    if need_to_draw == 0 : return
    if moving == 0 : need_to_draw = 0

    redraw 0
    color : boxf

    vect_x = cos(theta_shift)
    vect_y = sin(theta_shift)
    // 2.0 * M_PI / TILE_NUMだけ角度(位置)を変えながら、タイルをTILE_NUM個描画
    repeat TILE_NUM
        d3rotate vect_x, vect_y, vect_x, vect_y, 2.0 * M_PI / TILE_NUM
        d3setlocal 0,0,0, vect_x,vect_y,0, -vect_y,vect_x,00,0,1
        d3texture tile_x, tile_y, tile_z, cnt + 100BUFFER_WIDTHBUFFER_HEIGHT1
    loop

    redraw 1
    return

2007年10月2日火曜日

俯瞰型RPGマップの描画 2

前回のスクリプトにカメラの移動とキャラクタの移動・ジャンプを追加しました。マップチップの明るさは距離に応じた変化に限定しています。
ここまでくるとRPGというよりはアクションに近いものがありますね。

カメラの位置に合わせてカーソルキーで移動する方向を変化させる処理や、ビルボードのようにキャラクタを常に手前を向ける処理など、三角関数を多用しているので、今回のスクリプトはさすがに読みづらいと思います。d3setlocal命令によって多少は読みやすくなっていますが……。興味のある方はぜひ式を解いてみてください。
// カーソルキーでキャラクタの移動
// Ctrl同時押しでカメラの移動(回転)
// Spaceでジャンプ
#include "d3m.hsp"
#include "hspmath.as"
#const CELL_SIZE 100    // d3m上のマップチップサイズ
#const IMG_SIZE   64    // bmpのマップチップサイズ
#const CAM_DIST  500
#const CHAR_SPEED 15
#const MAP_WIDTH  18
#const MAP_HEIGHT 15
    // マップチップのロード
    buffer 1 : picload dir_exe + "/sample/game/testchr.bmp"

    // 影用の画像を用意する
    color : boxf IMG_SIZE * 30IMG_SIZE * 4IMG_SIZE
    color 255,255,255 : circle IMG_SIZE * 30IMG_SIZE * 4IMG_SIZE1

    gsel 0

    // 変数の初期化
    cam_theta = M_PI * 2 / 5    // カメラの角度
    cam_phi = -M_PI / 2
    char_x  = 0.0               // キャラクタの位置
    char_y  = 0.0
    char_z  = 0.0
    char_vz = 0.0               // キャラクタの速度

    // マップ作成
    dim map, MAP_WIDTHMAP_HEIGHT
    repeat length2( map )
        y = cnt
        repeat length( map )
            map( cnt, y ) = rnd3 )
        loop
    loop
    gosub *draw

*main
    stick keys, 15 + 64
    gosub *move
    if ( is_jumping ) | ( ( keys & 15 ) > 0 ) : gosub *draw
    wait 3
    goto *main

*move
    if ( keys >> 4 & 1 ) & ( is_jumping == 0 ) {
        char_vz = 20.0  // ジャンプ開始
        is_jumping = 1  // ジャンプ中フラグをON
    }
    char_vz -= 2.0      // 重力による加速
    char_z += char_vz   // Z方向の移動
    if char_z < 0.0 {
        // 着地時の演算
        char_z = 0.0
        char_vz = 0.0
        is_jumping = 0
    }
    if keys & 64 {
        // ctrl同時押し…カメラの移動
        cam_theta = limitf( cam_theta + 0.05 * ((keys >> 3 & 1) - (keys >> 1 & 1)), 0.01, M_PI / 2 - 0.01 )
        cam_phi += 0.05 * ((keys >> 2 & 1) - (keys & 1))
    } else {
        // ctrlを押さない…キャラクタの移動
        keys_x = (keys >> 2 & 1) - (keys & 1)
        keys_y = (keys >> 3 & 1) - (keys >> 1 & 1)
        char_x = limitf( char_x - ( sin( cam_phi ) * keys_x - cos( cam_phi ) * keys_y ) * CHAR_SPEED, -MAP_WIDTH/2*CELL_SIZE, (MAP_WIDTH-1)/2*CELL_SIZE)
        char_y = limitf( char_y + ( cos( cam_phi ) * keys_x + sin( cam_phi ) * keys_y ) * CHAR_SPEED, -MAP_HEIGHT/2*CELL_SIZE, (MAP_HEIGHT-1)/2*CELL_SIZE)
    }
    return

*draw
    // カメラの設定
    d3setcam cos(cam_phi)*sin(cam_theta)*CAM_DIST + char_x, sin(cam_phi)*sin(cam_theta)*CAM_DIST + char_y, cos(cam_theta)*CAM_DIST, char_x, char_y, 0

    redraw 0
    color : boxf
    gz = 0000 // d3texture用の配列
    repeat MAP_WIDTH, -MAP_WIDTH/2 : x = cnt
        repeat MAP_HEIGHT, -MAP_HEIGHT/2 : y = cnt
            dx = ( x * CELL_SIZE - char_x ) / CELL_SIZE
            dy = ( y * CELL_SIZE - char_y ) / CELL_SIZE
            gmode GMODE_ALPHA, , , 255.0 / pow( dx * dx + dy * dy + 10.25 )
            // d3texture用の配列を準備
            gx( 0 ) = x * CELL_SIZE - CELL_SIZE / 2, x * CELL_SIZE + CELL_SIZE / 2
            gx( 2 ) = gx( 1 ), gx( 0 )
            gy( 0 ) = y * CELL_SIZE + CELL_SIZE / 2 : gy( 1 ) = gy( 0 )
            gy( 2 ) = y * CELL_SIZE - CELL_SIZE / 2 : gy( 3 ) = gy( 2 )
            d3texture gx, gy, gz, 1IMG_SIZE * map( x + MAP_WIDTH/2, y + MAP_HEIGHT/2 ), 0IMG_SIZEIMG_SIZE
        loop
    loop

    d3setlocal char_x, char_y, 0sin(cam_phi),cos(cam_phi),0,-cos(cam_phi),sin(cam_phi),0,0,0,1
    // 影を描画
    gmode GMODE_SUB,,,32
    gx( 0 ) = -CELL_SIZE / 2CELL_SIZE / 2
    gx( 2 ) = gx( 1 ), gx( 0 )
    gy( 0 ) =  CELL_SIZE / 2 : gy( 1 ) = gy( 0 )
    gy( 2 ) = -CELL_SIZE / 2 : gy( 3 ) = gy( 2 )
    gz( 0 ) = 0000
    d3texture gx, gy, gz, 1IMG_SIZE * 30IMG_SIZEIMG_SIZE
    // キャラクターを描画
    gmode GMODE_RGB0
    gy( 0 ) = 0000
    gz( 0 ) = CELL_SIZE + char_z : gz( 1 ) = gz( 0 )
    gz( 2 ) = int( char_z ) : gz( 3 ) = gz( 2 )
    d3texture gx, gy, gz, 1IMG_SIZE * 1IMG_SIZEIMG_SIZEIMG_SIZE
    redraw 1
    return

俯瞰型RPGマップの描画

RPG関連のスクリプトを書こうと思い立ち、作成。普通のマップじゃつまらないので3Dにしてみました。
drawmodeが1の時の明るさの計算は、CodeZine: 3Dモデルを表示するJavaアプレットの作成を参考としています。
障害物のあるマップで視野を求めるで使った視野計算を合わせれば、よりそれっぽくなりそうです。

少々単純すぎるうえに用途が限られるので、開発wikiには投げずにここへ置いときます。
#include "d3m.hsp"
#const CELL_SIZE 100    // d3m上のマップチップサイズ
#const IMG_SIZE   64    // bmpのマップチップサイズ
#const CAM_X       0    // カメラ位置(45°手前から俯瞰)
#const CAM_Y    -400
#const CAM_Z     400
#define ctype pow(%1,%2expf(logf(%1)*(%2))    // x^y

    // マップチップのロード
    buffer 1 : picload dir_exe + "/sample/game/testchr.bmp"

    // カメラの設定
    gsel 0
    d3setcam CAM_XCAM_YCAM_Z000

    // 変数の初期化
    gz = 0000     // d3texture用の配列
    ch = CAM_Z          // CH = カメラの高さ

    // マップ作成
    dim map, 2020
    repeat length2( map )
        y = cnt
        repeat length( map )
            map( cnt, y ) = rnd3 )
        loop
    loop
    title "クリックで描画モード変更"
    onclick gosub *change_drawmode
    gosub *draw
    stop

*draw
    redraw 0
    color : boxf
    for x, -1010
        for y, -1010
            // マップチップの明るさを計算
            switch drawmode
                case 0  // 均等描画(どのマップチップも同じ明るさで描画)
                    c = 255
                    swbreak
                case 1  // 光の反射を考慮(45°手前から俯瞰するとして最適化済み)
                    // カメラの位置を点C、カメラの位置からXY平面におろした垂線の足を点H、
                    // マップチップの中心を点B、マップチップの中心からY軸におろした垂線の足を点A、
                    // XY平面上の原点を点Oとする。
                    // このとき、マップの中心が最も明るいという条件(すなわち光が45°奥から入射するという条件)を課すと、
                    // マップの明るさは cos( ∠COH - ∠CAH ) * cos( ∠BCA ) で決定される。
                    ah = y * CELL_SIZE - CAM_Y                  // AH
                    ab = x * CELL_SIZE                          // AB
                    cb = sqrt( ab * ab + ah * ah + ch * ch )    // CB
                    c = intdouble( ah + ch ) / ( sqrt2.0 ) * cb ) * 255 )   // = cos( ∠COH - ∠CAH ) * cos( ∠BCA ) * 255
                    swbreak
                case 2  // 距離に応じて変化(遠ければ遠いほど暗くなる)
                    c = int255.0 / pow( x * x + y * y + 10.2 ) )            // 距離に反比例させると暗すぎたので0.2乗で調整
                    swbreak
            swend
            gmode GMODE_ALPHA, , , c

            // d3texture用の配列を準備
            gx( 0 ) = x * CELL_SIZE - CELL_SIZE / 2, x * CELL_SIZE + CELL_SIZE / 2
            gx( 2 ) = gx( 1 ), gx( 0 )
            gy( 0 ) = y * CELL_SIZE + CELL_SIZE / 2 : gy( 1 ) = gy( 0 )
            gy( 2 ) = y * CELL_SIZE - CELL_SIZE / 2 : gy( 3 ) = gy( 2 )
            d3texture gx, gy, gz, 1IMG_SIZE * map( x + 10, y + 10 ), 0IMG_SIZEIMG_SIZE
        next
    next
    redraw 1
    return

*change_drawmode
    drawmode = ( drawmode + 1 ) \ 3
    switch drawmode
        case 0 : title "均等描画"         : swbreak
        case 1 : title "光の反射を考慮"   : swbreak
        case 2 : title "距離に応じて変化" : swbreak
    swend
    gosub *draw
    return

2007年7月27日金曜日

円柱のHSV色空間


最近文字列ばかりだったので、一息入れる意味で3D。

奥行きの計算は厳密なものではなく近似したものです。

#include "d3m.hsp"
#include "hspda.as"

#const H_MAX      12
#const S_MAX      4
#const V_MAX      7
#const PANEL_MAX  H_MAX * S_MAX * V_MAX
#const PANEL_SIZE 150                   // パネル一辺の長さ
#const PANEL_DIST 200                   // 同じ角度上のパネル同士の距離(左下の座標の間)
#const OBSERVER_R 2000                  // 観測者の中心からの距離

#const double PI        3.1415
#const double ANG_SPEED 0.04            // 観測者の角速度

    ct = 0.0
    d3setcam OBSERVER_R, 0, V_MAX * 100,  00, V_MAX * 100

*init
    dim  panel_h,   PANEL_MAX : dim  panel_s,   PANEL_MAX : dim  panel_v,   PANEL_MAX  // 色(HSV指定)
    dim  panel_r,   PANEL_MAX : ddim panel_t,   PANEL_MAX : dim  panel_z,   PANEL_MAX  // パネルの位置 円柱座標系(パネルの左下の座標)
    ddim panel_far, PANEL_MAX   // 奥行き

    repeat H_MAX : cnt1 = cnt
        h = 191 * cnt / H_MAX                       // HSV の H
        t = PI * 2.0 * cnt / H_MAX                  // 角度
        repeat S_MAX : cnt2 = cnt
            s = 256 * cnt / S_MAX                   // HSV の S
            r = cnt * PANEL_DIST + PANEL_DIST / 2   // 中心からの距離
            repeat V_MAX
                v = 256 * cnt / V_MAX               // HSV の V

                i = cnt1 * S_MAX * V_MAX + cnt2 * V_MAX + cnt
                panel_h( i ) = h : panel_s( i ) = s : panel_v( i ) = v
                panel_r( i ) = r : panel_t( i ) = t : panel_z( i ) = cnt * PANEL_DIST
            loop
        loop
    loop
    observer_t = 0.0

*main
    gosub *calc
    gosub *draw
    wait 2
    goto *main

*calc
    // 観測者の回転
    observer_t += ANG_SPEED
    if ( PI * 2.0 < observer_t ) : observer_t -= PI * 2.0
    d3setlocal 0,0,0cos( observer_t ),sin( observer_t ),0, -sin( observer_t ),cos( observer_t ),00,0,1

    // 奥行きを計算。遠くにあるものほどpanel_farは小さくなる
    repeat PANEL_MAX
        panel_far( cnt ) = cos( panel_t( cnt ) - observer_t ) * panel_r( cnt )
    loop
    // 小さい順(昇順)に並べ替え
    sortval panel_far, 0
    return

*draw
    redraw 0
    hsvcolor 00, ( sin( observer_t )  + 1.0 ) * 128 : boxf
    repeat PANEL_MAX
        sortget i, cnt // 奥から数えてcnt番目のパネルのインデックス
        x = cos( panel_t( i ) ) * panel_r( i ), cos( panel_t( i ) ) * ( panel_r( i ) + PANEL_SIZE ), cos( panel_t( i ) ) * ( panel_r( i ) + PANEL_SIZE ), cos( panel_t( i ) ) * panel_r( i )
        y = sin( panel_t( i ) ) * panel_r( i ), sin( panel_t( i ) ) * ( panel_r( i ) + PANEL_SIZE ), sin( panel_t( i ) ) * ( panel_r( i ) + PANEL_SIZE ), sin( panel_t( i ) ) * panel_r( i )
        z = panel_z( i ) + PANEL_SIZE, panel_z( i ) + PANEL_SIZE, panel_z( i ), panel_z( i )
        hsvcolor panel_h( i ), panel_s( i ), panel_v( i )
        d3square x, y, z
    loop
    redraw 1
    return

2007年7月16日月曜日

波紋のエフェクト(3次元ver.)

d3moduleを利用した波紋のエフェクトの3次元版。波の高さによって明るさが変わるようにもなっています。
#include "d3m.hsp"
#define  ctype round(%1) double(strf("%%0.0f"%1)) // 四捨五入(hspmathより抜粋)
#const   CONST_A     0.1 // 定数その1。波長を決定付ける。
#const   CONST_B     7   // 定数その2。屈折の程度を決定付ける。

    buffer 1
    onclick goto *start

*start
    onclick 0
    dialog "jpg;*.gif;*.bmp"16
    if stat == 0 : end
    gsel 1 : picload refstr
    sizeX = ginfo_winx
    sizeY = ginfo_winy
    centerX = ginfo_winx / 2
    centerY = ginfo_winy / 2

    screen 0, sizeX, sizeY, SCREEN_NORMAL
    gmode GMODE_GDI, sizeX, sizeY
    gcopy 100
    d3setcam sizeX/2, -300, sizeY/4, sizeX/20, sizeY/2
    redraw 0
    boxf
    gmode GMODE_MEM11
    repeat sizeY : targetY = cnt
        title "変換中…" + str(100 * cnt / sizeY) + "%"
        repeat sizeX : targetX = cnt
            // 中心から見た座標を算出。
            d3getpos vx, vy, targetX, 0, targetY
            dx = vx - centerX
            dy = centerY -vy

            // 中心から見た角度と距離を算出。
            theta = atan(dy, dx)
            dist  = sqrt(dx * dx + dy * dy)

            // ここがポイント。sinで屈折する距離を決めています。
            // ラスタースクロールのスクリプトのほうが分かりやすいかもしれません。
            copyX = centerX + round(cos(theta) * (dist + sin(dist * CONST_A) * CONST_B))
            copyY = centerY + round(sin(theta) * (dist + sin(dist * CONST_A) * CONST_B))

            pos targetX, targetY
            gmode GMODE_ALPHA11absf(sin(dist * CONST_A)) * 64 + 192
            gcopy 1, copyX, copyY
        loop
        await 10
    loop
    redraw 1
    title "変換終了 - クリックで再開"
    onclick 1
    stop

2007年6月8日金曜日

ワープゾーン

d3moduleによるワープゾーンっぽい表現。

これは半年近く前に書いたスクリプトなのですが、今思うとlength()関数を使うべき箇所や悪い変数名が散見されます。
#include "d3m.hsp"
    randomize
    repeat 50
        t(cnt) = 0.02*rnd(314)
        y(cnt) = rnd(500)
    loop
    screen 0480320
    d3setcam 00007000

*main
    redraw 0
    color : boxf
    color 191,191
    repeat 50
        d3initlineto
        d3ribbonto cos(t(cnt))*100, y(cnt), sin(t(cnt))*100cos(t(cnt) + 0.05)*100, y(cnt), sin(t(cnt) + 0.05)*100
        d3ribbonto cos(t(cnt))*100, y(cnt) + 200sin(t(cnt))*100cos(t(cnt) + 0.05)*100, y(cnt) + 200sin(t(cnt) + 0.05)*100
        y(cnt) -= 20
        if y(cnt) < 0 {
            t(cnt) = 0.02*rnd(314)
            y(cnt) = rnd(700) + 200
        }
    loop
    redraw
    wait 2
    goto *main

2007年5月22日火曜日

シェルピンスキーのギャスケット

シェルピンスキーのギャスケットを再帰を利用して描画。
そのままではつまらないので3D表示に。

#include "d3m.hsp"
#module Gasket
#deffunc drawGasket double x1, double y1, double x2, double y2, double x3, double y3, int count
    // X-Y平面上にシェルピンスキーのギャスケットを描く
    if count {
        drawGasket x1, y1, (x1 + x2)/2, (y1 + y2)/2, (x1 + x3)/2, (y1 + y3)/2, count - 1
        drawGasket x2, y2, (x1 + x2)/2, (y1 + y2)/2, (x2 + x3)/2, (y2 + y3)/2, count - 1
        drawGasket x3, y3, (x1 + x3)/2, (y1 + y3)/2, (x2 + x3)/2, (y2 + y3)/2, count - 1
    } else {
        d3initlineto
        d3lineto x1, y1, 0
        d3lineto x2, y2, 0
        d3lineto x3, y3, 0
        d3lineto x1, y1, 0
    }
return
#global
    redraw 0
    d3setcam -30, -409050430
    color : boxf
    color 0128
    drawGasket 001000cos(3.14/3) * 100sin(3.14/3) * 1004
    redraw 1
    stop

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