はじめに

 hgimg4では、ノードオブジェクトにトルク(回転方向の力)を加えることができます。 トルクとは、物体を回転させようとする力のことを言います。 物体にトルクが加わると、物体は重心を中心に回転しようとします。

 gppapply命令でトルクを加える際の設定については、次の2種類から選択することができるとHDLに記載されています。

GPPAPPLY_TORQUE トルク(ねじる)力を与える
GPPAPPLY_TORQUE_IMPULSE トルク+衝撃を与える

 またしても違いがよくわからない表現…。しかし力を加えるときと同じ表現なので力と力積のような違いだろうと推察できます。

 今回もサンプルで実験しつつ、挙動の意味を考察してみました。 少し使い分けができるようになると思います。

gppapply 力のモーメント・トルク

 実際にノードオブジェクトに力のモーメント(トルク)を加えて使い方を確認してみました。 片側が重たいシーソーを作って、バランスをとる大きさのトルクをシーソーに加えてみます。 バランスが取れれば、シーソーは水平になるはずです。


#include "hgimg4.as"

title "HGIMG4 トルク"

	;	物理エンジンテスト

	; 重力加速度(SI単位):9.80665 m/s^2
	grav = 9.8

	height = 0.5		; 支点高さ
	h_板 = 0.5 	; 天秤の板厚
	mass_板  = 5.0	; 板の重さ
	
	;	シーン作成
	gpreset
	setcls CLSMODE_SOLID, $808080		; 画面クリア設定

	;	カメラを作成
	setpos   GPOBJ_CAMERA, 4, 0.3, 10
	gplookat GPOBJ_CAMERA, 4, 0.3, 0

	;	床を作成
	gpfloor id_floor, 30,30, $404040
	setpos  id_floor, 0, 0, 0
	gppbind id_floor, 0
	
	;	箱を作成
	; 見えないところに配置
	gptexmat id_texmat, "res/qbox.png"
	gpbox   id_box, 1, -1, id_texmat		; 箱ノードを追加
	setpos  id_box,	100, 1, -10
	;gppbind id_box, 1, 0.5

	; 支点
	gpclone  id_boxc(0), id_box
	setscale id_boxc(0), 1, 1, 2
	setang   id_boxc(0), 0, 0, deg2rad(45)
	setpos   id_boxc(0), 0, height - sqrt(0.5), 0	; 高さ height
	gppbind  id_boxc(0), 0, 1000
	;gppset   id_boxc(0), GPPSET_KINEMATIC, 1	; 質量0にした場合は必要ありません。

	; 板
	gpclone  id_boxc(1), id_box
	setpos   id_boxc(1), 4, height + h_板/2, 0
	setscale id_boxc(1), 10, h_板, 2
	gppbind  id_boxc(1), mass_板, 10000

*main
	stick key,15
	if key&128 : end

	;	板の位置
	; 支点から板の重心までの距離を計算
	; 支点のxは0なので、板のx座標がそのまま距離になります。
	; また、板の中心座標は重心座標と同じです。
	getpos id_boxc(1), kyori_板,y,z

	; トルク = 距離 * 力
	;        = 距離 * 質量 * 重力加速度
	torque = kyori_板 * mass_板 * grav		; シーソー板分のトルク

	; シーソーにトルクをかける
	gppapply id_boxc(1), GPPAPPLY_TORQUE, 0,0, torque

	;	描画
	redraw 0			; 描画開始
		gpdraw				; シーンの描画
	
		color 255,255,255
		pos 8,8:mes "HGIMG4 sample"

	redraw 1			; 描画終了
	await 1000/60			; 待ち時間

	goto *main

 なんとかバランスが取れました。板の厚さがあるので重心が支点より高いところに来てしまい、構造上水平には収束しないのですが、まぁだいたいバランスが取れているようです。確認という意味では十分でしょう。

 さて、トルク[N・m]の計算は次のような式になっています。

トルク = 重心からの距離 * 力

 板を支えているのは支点だけなので、板は支点から自重分の力を上方向に受けています。 この式での距離は、板の重心から支点までの距離。この結果、板には右回転のトルクが発生しています。

 板が支点から受けている右回転のトルクを打ち消すため、左回転のトルクをgppapply命令で入力します。


	;	板の位置
	; 支点から板の重心までの距離を計算
	; 支点のxは0なので、板のx座標がそのまま距離になります。
	; また、板の中心座標は重心座標と同じです。
	getpos id_boxc(1), kyori_板,y,z

	; トルク = 距離 * 力
	;        = 距離 * 質量 * 重力加速度
	torque = kyori_板 * mass_板 * grav		; シーソー板分のトルク

	; シーソーにトルクをかける
	gppapply id_boxc(1), GPPAPPLY_TORQUE, 0,0, torque

 板は支点に固定されていないので、わずかに滑って距離が変わってしまう現象が起きます。このためgetpos命令で毎回計算しています。

静的剛体とKinematic剛体

 シーソーのサンプルの支点の作りが気になった方もいると思います。 地面にめり込んでいるのにまったく動かず、物理設定をされていない物体のようにすり抜けることもありません。 物理設定されているのに物理設定されていないような挙動もする不思議な状態です。 少し補足説明しておきます。

 hgimg4の物理設定はBulletと同様、ノードオブジェクトには次の3つの種類があります。

  • 動的剛体
    • ゼロより大きい質量。
    • 物理挙動をする。他の物体と衝突すれば動く。
  • 静的剛体
    • 質量ゼロ。
    • 他の物体と衝突はするが、動かない。
    • setposでは動かせない。
  • Kinematic剛体
    • 質量ゼロ。
    • 他の物体と衝突はするが、動かない。
    • setposで動かすことができる。動的剛体を押したりすることはできるが、オブジェクトからは影響を受けない。

 gppbindで適当な質量を指定して物理設定を行ったオブジェクトは、動的剛体になります。 gppbindで質量0を指定すると、静的剛体になります。 gppset命令でGPPSET_KINEMATICを指定すると、Kinematic剛体になります。

 シーソーのサンプルの支点には、静的剛体を使用しています。 静的剛体は、他の物体と衝突はしますが動くことはありません。支点が動くのは困るので今回使用しています。 また、今回のケースについては Kinematic剛体を指定しても同じ効果が得られます。


gppbind  id_boxc(0), 0, 1000
;gppset   id_boxc(0), GPPSET_KINEMATIC, 1	; 質量0にした場合は必要ありません。

 物理挙動を使ったゲームを作る場合は、結構頻繁に使う機能だと思います。 静的剛体があれば空中に足場を作ったりできますし、Kinematic剛体があればメダルゲーム機みたいなコインを押し出す壁も作れます。

gppapply 力積のモーメント、力積モーメント、角力積

 トルク+衝撃を加えるサンプルを作成してみました。 実行すると3つの立方体が表示されます。 左からGPPSET_ANGULAR_VELOCITYGPPAPPLY_TORQUE_IMPULSEGPPAPPLY_TORQUEで回転させたものです。 すべて1秒間に1回転するように調整してあります。(なっているかは今は置いておく。)

GPPSET_ANGULAR_VELOCITY 1秒間に1回転するサンプルを比較見本として配置しています。
GPPAPPLY_TORQUE_IMPULSE 真中 トルク+衝撃で1秒間に1回転するように調整した力を加えています。 見本と同じ回転速度になっています。
GPPAPPLY_TORQUE トルクを使って10秒間かけて回転数を調整しています。10秒後には、見本と同じ1秒間に1回転… に近い回転速度となっています。

#include "hgimg4.as"
#include "d3m.hsp"	; d3timer

title "HGIMG4 test"

	;	物理エンジンテスト

	; 質量 [kg]
	mass = 1.0

	; 慣性モーメント
	; 1辺の長さ1mの四角柱の慣性モーメント計算式
	; I = 1.0/12.0*mass*(1^2+1^2)
	inertia = 1.0 / 12.0 * mass * (1*1+1*1)

	; フレーム数カウント
	frame = 0

	;	シーン作成
	gpreset
	setcls CLSMODE_SOLID, $808080		; 画面クリア設定

	;	カメラを作成
	if 0 {
		;	透視投影(パース)
		; 通常の投影です。
		setpos GPOBJ_CAMERA, 0,1,6			; カメラ位置を設定
		gplookat GPOBJ_CAMERA, 0,1,0		; カメラから指定した座標を見る
	} else {
		;	平行投影
		; 立体感のない投影です。
		; 回転速度の比較は、こちらの方が見やすいと思います。
		gpnull   id_camera				; ヌルノードを生成する
		gpcamera id_camera, 4, double(ginfo_winy)/ginfo_winx, , , 1	; カメラとして設定する
		gpusecamera id_camera			; 使用するカメラを選択する
		setpos   id_camera, 0,1,6		; カメラ位置を設定する
		gplookat id_camera, 0,1,0
	}
	
	;	箱を作成
	; 減衰無効、重力無効
	gptexmat id_texmat, "res/qbox.png"
	gpbox   id_box, 1, -1, id_texmat		; 箱ノードを追加
	setpos  id_box,	-2, 1, 0			; 右に配置
	gppbind id_box, mass
	gppset  id_box, GPPSET_DAMPING, 0,0,0
	gppset  id_box, GPPSET_GRAVITY, 0,0,0

	;	箱(クローン)を作成
	; TORQUE
	gpclone id_boxcT, id_box		; 箱をクローン
	setpos  id_boxcT, 2, 1, 0		; 左に配置
	gppbind id_boxcT, mass
	gppset  id_boxcT, GPPSET_DAMPING, 0,0,0
	gppset  id_boxcT, GPPSET_GRAVITY, 0,0,0
	; TORQUE_IMPULSE
	gpclone id_boxcTI, id_box		; 箱をクローン
	setpos  id_boxcTI, 0, 1, 0		; 真ん中に配置
	gppbind id_boxcTI, mass
	gppset  id_boxcTI, GPPSET_DAMPING, 0,0,0
	gppset  id_boxcTI, GPPSET_GRAVITY, 0,0,0


	;	GPPAPPLY_TORQUE_IMPULSE
	; 衝撃トルクのテスト
	;  N:トルク [Nm]
	;  I:慣性モーメント [kg・m^2]
	;  a:角加速度 [rad/s^2]
	;  ω:角速度 [rad/s]
	;  L:角運動量[kg・m^2/s]
	;
	; トルク
	; N = I * a
	;   = I * dω/dt
	;
	; 角運動量
	; L = Iω
	;
	; 力積のモーメント、力積モーメント、角力積[kg・m^2]
	; N*dt = dL
	;      = L' - L
	;
	; N*dt = I * dω
	;      = Iω' - Iω
	;
	w = 1.0 * 2.0 * M_PI				; 1 [rps] = 2π [rad/s]
	Ndt = inertia * w - inertia * 0.0		; 力積モーメント
	gppapply id_boxcTI, GPPAPPLY_TORQUE_IMPULSE, 0, 0, Ndt

	;	回転速度の比較用
	; GPPSET_ANGULAR_VELOCITY
	; 角速度ωを直接入力
	gppset id_box, GPPSET_ANGULAR_VELOCITY, 0, 0, w

	; 時間計測用
	timecount0 = d3timer()

*main
	stick key,15
	if key&128 : end


	;	GPPAPPLY_TORQUE
	;  N:トルク [Nm]
	;  I:慣性モーメント [kg・m^2]
	;  a:角加速度 [rad/s^2]
	;  ω:角速度 [rad/s]
	;
	; トルク
	; N = I * a
	; a = N / I
	;
	; 角速度ω[rad/s] と 角加速度a[rad/s^2] の関係式
	; ω = a * t
	;   = (N / I) * t
	;
	; 回転毎秒
	; rps = ω * 2π [rps]
	;     = (N / I * t) / 2π
	;
	; 10秒間一定トルクを与えて、1rpsに到達させるために必要なトルクを計算。
	; N = rps *   I /  t   * 2π
	;   = 1.0 * 1/6 / 10.0 * 2.0*M_PI
	;   = 0.1047… [Nm]

	; 10秒間加速
	if frame < 600 {		; 1/60 [秒/コマ] * 600 [フレーム] = 10 [秒]
	; if timecount<=10.0 {	; 10 秒
		gppapply id_boxcT, GPPAPPLY_TORQUE, 0, 0, inertia / 10.0 * 2.0*M_PI
	}


	;	描画
	redraw 0			; 描画開始
		gpdraw				; シーンの描画
	
		color 255,255,255
		pos 8,8:mes "HGIMG4 sample"
		mes "フレーム数:" + frame
		timecount = double( d3timer() - timecount0 ) / 1000
		mes "経過時間 :" + strf("%5.3f秒", timecount )

		; GPPAPPLY_TORQUE
		gpcnvaxis x,y,z, 2, 0, 0.5, 0
		if (z>=0) & (z<=1) : pos x-27,y : mes "TORQUE"
		
		; GPPAPPLY_TORQUE_IMPULSE
		gpcnvaxis x,y,z, 0, 0, 0.5, 0
		if (z>=0) & (z<=1) : pos x-65,y : mes "TORQUE_IMPULSE"

		; 回転見本
		gpcnvaxis x,y,z, -2, 0, 0.5, 0
		if (z>=0) & (z<=1) : pos x-18,y : mes "見本"

	redraw 1			; 描画終了
	await 1000/60		; 待ち時間
	frame++
	goto *main

 カメラは見やすいように平行投影にしてあります。通常のパースが良ければ切り替えてください。


	;	カメラを作成
	if 0 {
		;	透視投影(パース)
		; 通常の投影です。
		setpos GPOBJ_CAMERA, 0,1,6			; カメラ位置を設定
		gplookat GPOBJ_CAMERA, 0,1,0		; カメラから指定した座標を見る
	} else {
		;	平行投影
		; 立体感のない投影です。
		; 回転速度の比較は、こちらの方が見やすいと思います。
		gpnull   id_camera				; ヌルノードを生成する
		gpcamera id_camera, 4, double(ginfo_winy)/ginfo_winx, , , 1	; カメラとして設定する
		gpusecamera id_camera			; 使用するカメラを選択する
		setpos   id_camera, 0,1,6		; カメラ位置を設定する
		gplookat id_camera, 0,1,0
	}

 いずれの箱も減衰があると回転が止まってしまうので、減衰を0にしています。 ここでは減衰(damping)は、空気抵抗みたいなものだと考えてよさそうです。 また床が回転の邪魔なので、重力で落下しないように無重力にしています。


	gppset  id_box, GPPSET_DAMPING, 0,0,0
	gppset  id_box, GPPSET_GRAVITY, 0,0,0

力積のモーメント

 力積のモーメントは、角運動量の変化に等しい。ということなので、今回のサンプルでは角運動量の変化から算出して使用しました。 角運動量がゼロから1rps(1秒あたり1回転)へと変化するために必要な力積のモーメントを計算しています。

 まずは、慣性モーメントが必要ですね。サンプルでは単純な1辺1mの立方体を使用するので、一般式から求めてみました。 hgimg4の内部でも同じ方法で算出しているかわからないのですが、やって見るとうまく予想通りの結果になりました。多分合ってるのだと思います。


	; 慣性モーメント
	; 1辺の長さ1mの四角柱の慣性モーメント計算式
	; I = 1.0/12.0*mass*(1^2+1^2)
	inertia = 1.0 / 12.0 * mass * (1*1+1*1)

 計算式はこちらを参考にしました。

慣性モーメント(イナーシャ)Jの計算式・公式 一覧
https://www.mikipulley.co.jp/JP/Services/Tech_data/tech24.html

 角速度ωは、1[rps] = 2π[rad/s] なので力積のモーメントは次のようにして求めることができます。 また求めた値は、GPPAPPLY_TORQUE_IMPULSEを使ってノードオブジェクトに1回だけ力を加えます。


	;	GPPAPPLY_TORQUE_IMPULSE
	w = 1.0 * 2.0 * M_PI				; 1 [rps] = 2π [rad/s]
	Ndt = inertia * w - inertia * 0.0		; 力積モーメント
	gppapply id_boxcTI, GPPAPPLY_TORQUE_IMPULSE, 0, 0, Ndt

 実行結果を確認すると、見本とぴったり同じ回転速度になりました。 微妙に変動しうる1フレームの経過時間の影響を受けずに、求めたい回転変化が安定して得られるのでGPPAPPLY_TORQUE_IMPULSEってなかなかいいですね。

 慣性モーメントは、形や大きさ、質量によって異なります。またhgimg4側からも取得できないので簡単にはわかりません。 このためGPPAPPLY_TORQUE_IMPULSEを使用する際は、結果を見ながら力積のモーメントを調整することになると思います。 調整する際のポイントは、次のようになります。

  • 質量を倍にすると回転しにくさも倍になる。
  • 同じ質量でも形が回転軸の半径方向に大きいオブジェクトは回転しにくい。(=慣性モーメントが大きい)
  • 回転しにくさが大きくなった場合、力積のモーメントも同じ比率で大きくすれば同じように回転する。
  • 力積のモーメントだけを倍にすると、角速度の変化量も倍になる。

まとめ

 gppapplyで当たることができる力は4種類。

力の種類(タイプ) 内容 物理的な意味 単位
(SI単位の場合)
GPPAPPLY_FORCE 移動しようとする力を加算するN
GPPAPPLY_IMPULSE 瞬間的な衝撃を与える 力積 N・s = kg・m/s
GPPAPPLY_TORQUE トルク(ねじる)力を与える 力のモーメント
(トルク)
N・m
GPPAPPLY_TORQUE_IMPULSEトルク+衝撃を与える 力積のモーメント N・m・s = kg・m2/s

 GPPAPPLY_FORCEGPPAPPLY_TORQUEは、力を入力し続ける場合に使用する。 長い時間力を加える場合に使用する。たとえば車のアクセルを踏むと進行方向に進む力が発生するので、この場合はGPPAPPLY_FORCEが良さそう。 後ろに _IMPULSE がついているもので代用しようとすると、(計算上静止するはずが静止しないなど)安定した結果が得られない。

 GPPAPPLY_IMPULSEGPPAPPLY_TORQUE_IMPULSEは、加える力の総量(力積)が決まっている場合に使用する。 短時間(1フレーム程度)でノードオブジェクトに力を加えたい場合に使用する。 たとえば爆発で吹っ飛ぶなどの用途で使用する。 後ろに _IMPULSE がついていないもので代用しようとすると、結果(吹っ飛ぶ飛距離など)にバラツキが発生してしまう。

 使い分けが難しいようなら、細かい事を気にせず GPPAPPLY_FORCEGPPAPPLY_TORQUE だけを使えばいいと思います。 また物理挙動だけではなく、gppset命令も使った方が気持ちの良い動きになる場合もあります。(例:マリオは空中でも左右に移動できる。) よりいい結果が得られる方法を選びたいですね。

補足

 いろいろと説明をすっ飛ばして書いたので、補足を書いておきます。

角速度

 サンプルの「見本」は角速度を直接与えています。

 角速度 ω[rad/s]は名前の通り角度方向(回転)の速度のことで、1秒間に何ラジアン(radian)回っているという値です。ラジアンは角度の単位で、1[回転]=360[度]=2π[rad]です。 1秒間にn回転する場合の角速度を求めたい場合、ω = n×2π[rad]として計算できます。サンプルでは1秒間に1回転を指定しているのでこのように記述しています。


	w = 1.0 * 2.0 * M_PI	; 1 [rps] = 2π [rad/s]

GPPSET_ANGULAR_VELOCITYを使えば、ノードオブジェクトを指定の角速度で回転させることができます。


	;	回転速度の比較用
	; GPPSET_ANGULAR_VELOCITY
	; 角速度ωを直接入力
	gppset id_box, GPPSET_ANGULAR_VELOCITY, 0, 0, w

 ストップウォッチ片手に回転数を目で数えて確認してください。

 ちなみに角速度の変化率を角加速度 α[rad/s2]と言います。1秒あたりにどのくらい角速度 ω[rad/s]が変化するかという値です。 加速度 a[m/s2]に対する速度 v[m/s]みたいな関係です。

慣性モーメント

 質量m[kg]が物体の移動しにくさを表す値であるように、慣性モーメント[kg・m2]は物体の回転しにくさを表す値です。 値が大きいほど回転しにくく、小さいほど回転しやすくなります。


F = m * a  …  力=質量×加速度
  = I * dv/dt

N = I * α  …  トルク=慣性モーメント×角加速度
  = I * dω/dt

 慣性モーメントは通常、回転軸の位置と形状、質量から算出できます。hgimg4では、内部で自動計算され表に出てくることはありません。 今回は動作の検証が行いたかったので、立方体の慣性モーメントを一般式から算出して使用してみました。


	; 慣性モーメント
	; 1辺の長さ1mの四角柱の慣性モーメント計算式
	; I = 1.0/12.0*mass*(1^2+1^2)
	inertia = 1.0 / 12.0 * mass * (1*1+1*1)

慣性モーメント(イナーシャ)Jの計算式・公式 一覧
https://www.mikipulley.co.jp/JP/Services/Tech_data/tech24.html

こちらのサイトの「直方体の慣性モーメント計算式」を使いました。

 計算式から読み取れる通り、慣性モーメントは形が変わると変化します。 身近な例として、椅子に座って回転しているときに、両腕を水平に広げると回転がゆっくりに、両腕を胸に引き寄せて縮めると回転が速くなります。 このように質量(体重)が変化しなくても、形が変われば慣性モーメントも変化します。

角運動量

 角運動量は、運動量の回転方向版と考えておけばだいたい大丈夫だと思います。

名称運動量 角運動量(運動量のモーメント)
意味物体の運動を表す物理量運動量のモーメント
p = mv L = Iω
記号の意味
p 運動量 [kg・m/s]
m 質量 [kg]
v 速度 [m/s]
L 角運動量 [kg・m2/s]
I 慣性モーメント[kg・m2]
ω角速度 [rad/s]
α角加速度 [rad/s2]

力積のモーメント、力積モーメント、角力積

 力積のモーメントは、力積の回転方向版と考えておけばだいたい大丈夫だと思います。 角運動量の変化量を力積のモーメントと言います。

 力積モーメント(moment of impulse)は、資料によっては「力積のモーメント」、「角力積」と書かれていることがあります。

名前 力積 力積のモーメント
意味 運動量の変化 角運動量の変化
力積 = mv' - mv力積モーメント = Iω' - Iω
式(時間変化)F = dp/dt N = dL/dt
記号の意味
F [N]
N トルク [N・m]
dp運動量変化 [kg・m/s]
dL角運動量変化 [kg・m2/s]
dt時間 [s]
v'変化後の速度 [m/s]
ω'変化後の角速度[rad/s]

 力(のモーメント)の大きさは時間変化と一緒に変わることもあるので、力積(のモーメント)を「力(のモーメント)×経過時間」と簡単には表せません。 力(のモーメント)の時間積分を言葉で言い表すと、物体に加えた力(のモーメント) を 力(のモーメント)を加えた時間の間 だけすて足したもの。とか、物体が受けた力(のモーメント)の総量みたいな感じでしょうか。