Luaレッスン23「(3.2)物理エンジンへようこそ」和訳

出典:http://compasstech.com.au/TNS_Authoring/Scripting/script_tut23.html

レッスン23「(3.2)物理エンジンへようこそ」

TI-NspireのLua機能に関してだけでなく、TI-Nspireプラットフォーム全体で見てもおそらく、バージョン3.2のアップデートで加えられた最もドラマティックな機能は、新たなLua物理エンジンでしょう。オープンソースの2D physicsChipmunkエンジンをベースとするこの新たなライブラリーによって、途方もなくパワフルなシミュレーション機能が追加されます。さまざまな力のかかった状態でのオブジェクトの動きをモデリングするツールだけでなく、機械工学や物理学をはじめ多くの分野でさまざまなモデリングを行うためのツールが用意されています。

これまで述べてきたことと同じく、この新たな機能を最大限活用するためには、経験豊富なプログラマーが必要です。しかし、わたくしのようなアマチュアにも、利用価値はかなり高く、利用のしがいがあります。今回から始まる最終シリーズでは、Nspire Lua Chipmunk物理エンジンを学び始める際の基本事項と方向性とについていくつか示します。最終目標が少し理解できるよう、添附のムービー(TIのアルフレッド・ロドリゲスが制作)を御覧ください。

   

http://compasstech.com.au/TNS_Authoring/Scripting/script_tut23/chipmunk0.mov

  

http://compasstech.com.au/TNS_Authoring/Scripting/script_tut23/chipmunk1.mov

 

  1. 物理エンジン入門

我々が最初に取り組むChipmunkプロジェクトは、当然ながら、上の華麗なムービーに比べれば小規模です。まず、1個のボールを画面上で跳ねさせるところから始めましょう。

お気づきかもしれませんが、これは物理エンジンなしでも実現できます。タイマーをうまく利用すれば、それほど苦もなくモノが動かせます。また、自身の位置をタイマーにコントロールさせるサークル・クラスも作成できます。

では、物理エンジンなどという仰々しい名前に見合う価値は何なのでしょうか? 

初めてクラスを紹介したときのことは覚えていますね。クラスは、画面上での自身の位置が認識できるのです。それによってさまざまな可能性が開けますし、クラスがなければ複雑きわまりない多くのことがずっとシンプルになります。

Chipmunkも同じなのです。クラスと同様、オブジェクト(このオブジェクトは“スペース”に存在します)を定義しますが、重要なのは、こうしたオブジェクトに属性が設定できるということです。オブジェクトは、我々の目に見えるボディーとシェイプを持ち、そのどちらも属性を持ちます。属性は、たとえば質量、重力、速度のような単純なものから、慣性モーメント(移動中の物体の回転のしやすさ)のようなもっと興味深いことまで、あるいは摩擦、弾性、さらには、衝突、減衰回転バネ、旋回運動、ラチェット・ジョイント、シンプルなモーターに至るまで、多岐にわたります。

最初に断っておきますが、こんなありとあらゆる専門的な分野にまで踏み込むつもりはありません。入門篇として身近なことについては十分学べるでしょう。あとは、あなた次第です。

  1. クイック・スタート:ボールを弾ませる

platform.apilevel = "2.0"
require "physics"
timer.start(0.01)
function on.resize()
      W = platform.window:width()
     H = platform.window:height()
     space = physics.Space()
     newBody = physics.Body(100, 0)
     newBody:setVel(physics.Vect(1000,1000))
     space:addBody(newBody)
end
function on.paint(gc)
     local width = W/10
     local pos = newBody:pos()
     local vel = newBody:vel()
     local velX = vel:x()
     local velY = vel:y()
     local posX = pos:x()
     local posY = pos:y()
     if posX > W then
          velX = -1 * math.abs(velX)
          posX = W
     elseif posX < 0 then
          velX = math.abs(velX)
          posX = 0
     end
     if posY > H then
          velY = -1 * math.abs(velY)
          posY = H
     elseif posY < 0 then
          velY = math.abs(velY)
          posY = 0
     end
     newBody:setPos( physics.Vect(posX, posY) )
     newBody:setVel( physics.Vect(velX, velY) )
     gc:setColorRGB(0, 0, 255)
     gc:fillArc(posX, posY, width, width, 0, 360)
end
function on.timer()
     space:step(0.01)
     platform.window:invalidate()
end

 

上のスクリプトをTI-Nspire Script Editorに貼り付けて実行してみてください。青い円が画面上を跳ね回るはずです。

この物理エンジンはOS 3.2でしか利用できない機能ですので、APILevelは2.0に設定しなければ成りません。バージョン3.2で特に重要なことは、いくつかのオプションをサポートしている新たなrequireコマンドです。require "physics"は、物理ライブラリーへアクセスするためのコマンドであり、require "color"は、事前定義済み基本色から成るライブラリーへアクセスするためのコマンドです。まずはrequire "physics"を取り上げます。require "color"はあとで示します。

次に、我々の作成するボディーを配置する仮想的な空間(スペース)を定義します。これは、物理現象の世界では特に重要な要素の1つです。あとでこのスペース内で重力などを定義しますが、とりあえず今は重力をゼロとしましょう。

最も重要なことは、我々の作成するオブジェクトが身にまとうボディーを定義することです。ボディーには視覚的要素がまったくないことに注意してください。ボディーは、さまざまな属性を持つ張りぼてのようなものです。ボディーは最終的には、それを包みこむシェープが必要となります(シェープも、弾性(今回の物理世界では反撥力と呼ぶのが妥当でしょう)や摩擦などの属性を持つことが可能です)。physics.Bodyコマンドには、引数が2つあります。1つは質量(今回は100に設定)、もう1つは慣性(今回は0に設定)です。この値を変えるとどうなるか、確認してみてください。

次に、今回のボディーの速度を定義します。速度の引数は、位置やその他多くの物理コマンドと同じく、ベクトルです。ベクトルは、原点からの方向と大きさとを持っていますので、こうした引数にベクトルを用いるのは当然です。今回は、速度をnewBody:setVel(physics.Vect(1000,1000))と定義します。これは、座標(0, 0)から座標(1000, 1000)へ向かうベクトルという意味です。つまり、モーションの最初の方向が水平線から下へ45度であり、ベクトルの長さが速度を表しています。初期座標は、デフォルトでは(0, 0)に設定されます。

最後に、space:addBody(newBody)というコマンドを使って、物理パートとしてこのボディーをスペースに追加します。以上で定義が終わり、使用する準備が整いました。位置や速度などはもちろんいつでも変更できます。上のスクリプトではon.paint函数の中で変更しています。

    

  1. 外観と動きとを豊かにする

on.paint函数内に記述した内容のほとんどは、これまでのレッスンを学んできた皆さんなら理解できるでしょう。ウィンドウの寸法を定義し、今回作成するボディーの各種属性(位置や速度だけでなく、それぞれのxy座標など)にシンプルな名前を付けています。すべて、先にボディーを定義したときに準備は済んでいます。こうしておけば、今回のボディーのコントロールが容易になることはわかりますね。

速度の制御は、物理エンジンがあって初めて可能となります。タイマーを使えば、オブジェクトの位置を変化させること、およびその変化の刻みを変えことで速度をコントロールすることは可能ですが、本当の意味での速度(velocity)、すなわちスピードと方向とを含んだ速度を制御するには物理エンジンが必要です。これは、どんな種類であろうとシミュレーションには非常に重要なことです。

上のスクリプトでは、ボディーが上下左右の壁にぶつかったときに跳ね返るようになっています。そうして、位置と速度の両方については、setPosおよびsetVelを使って新たな座標として定義し直しています。スクリプトの内容を良く理解してください。たっぷり時間をかけてください。

最後にボディーにシェイプを与えます。今回は、ボディーが画面上を動くときに、そのボディーの位置を追いかけるだけの青い円にします。どんな形状でもどんな画像でも、その位置をposX、posYに設定すれば、使用できます。

  1. さあ、始めよう

timerを定義し(space:stepコマンドに注目)、必要に応じて画面をリフレッシュすれば、終わりです。

resize函数の中に初期条件を記述しておくと、ページが初めて作成されたとき、およびページのサイズが変化したときに、その初期条件が呼び出されます。ウィンドウのサイズが変化すると、すべてが最初からもう一度スタートしますが、変化したウィンドウのサイズに応じて実行されます(注:現在(訳註:多分バージョン3.2の時点)、timer.startコマンドにはバグがあるようです。最初呼び出したときには正しく機能するのですが、もう一度呼び出すと、呼び出し直すまでタイマーが停止してしまいます。こうした理由から、on.resize函数の内側ではなくスクリプトの先頭に記述しています。これなら1回しか呼び出されません)。

いつものように、いろいろいじってみてください。

まずは、初速から手をつけることをお勧めします。ベクトルを変えるとどうなるのか試してください。(1000, 1000)を(1000, 0)に変えるとどうなるでしょうか。実行する前に考えてください。(-100, -100)に変更したらどうなるでしょうか。なぜそうなるのか。

始点を変更するときは次のようにします。変数wおよびhに関するウィドウ・サイズ定義をresize函数に追加します(on.paintからコピー・アンド・ペーストするだけです)。そうして、setVelラインの下にnewBody:setPos(physics.Vect(w/2,h/2))などと記述します。

ボールを右下の角から出すことはできますか? 

今回のスクリプトでは、画面の左端および上端ではボールが正しく跳ね返りますが、右端および下端を超えてから跳ね返っていることに気づきましたか? 四辺すべてで正しく跳ね返るようにするにはどうすれば良いでしょうか? 

円の代わりに正方形にするにはどうすれば良いでしょうか? 

その答は、今回のレッスンのサンプル・ファイルchipmunk_simple.tnsに記述してあります。次のレッスンでは、こうした重要なアイディアを復習し、さらに詳しく、応用の利く方法を学びます。

 ------------------------------------------------

(訳註)今回の実行例: 

f:id:ti-nspire:20141215120556g:plain

 --上のスクリプトから少し変えた

platform.apilevel = "2.0"
require "physics"
timer.start(0.01)
function on.resize()
     W = platform.window:width()
     H = platform.window:height()
     space = physics.Space() --Bodyを配置するSpaceを設定
     newBody = physics.Body(100, 0) --(質量, 慣性)
     newBody:setVel(physics.Vect(1000,1000)) --(初期座標)から(x,y)へのベクトル
     space:addBody(newBody) --BodyをSpaceへ投入
end
function on.paint(gc)
     local width = W/10 --ボールの直径
     local pos = newBody:pos()
     local vel = newBody:vel()
     local velX = vel:x() --x方向の速度
     local velY = vel:y() --y方向の速度
     local posX = pos:x() --x方向の位置
     local posY = pos:y() --y方向の位置
     if posX > W-width then --ボールが画面右端にぶつかったら)
          velX = -1 * math.abs(velX) --x方向の速度をマイナスにする(左向きに変える)
          posX = W-width --動きの始点を右端に再設定
     elseif posX < 0 then --ボールが画面左端にぶつかったら
          velX = math.abs(velX) --x方向の速度をプラスにする(右向きに変える)
          posX = 0 --動きの始点を左端に再設定
     end
     if posY > H-width then --ボールが床にぶつかったら
          velY = -1 * math.abs(velY)
          posY = H-width
     elseif posY < 0 then --ボールが天井にぶつかったら
          velY = math.abs(velY)
          posY = 0
     end
     newBody:setPos(physics.Vect(posX, posY) )
     newBody:setVel(physics.Vect(velX, velY) )
     gc:setColorRGB(0, 0, 255)
     gc:fillRect(posX, posY, width, width)
end
function on.timer()
     space:step(0.002)
     platform.window:invalidate()
end