全方位に投げつけろ!

 昔、うんこ投げるゴリラいたな。

 いや、そうじゃない。

 

 前回の実装は運動方程式に基づいて動かしてるので、当然、上に投げると重力で落ちてくるようになってます。

 

 ただ、カメラポジションとか立方体の大きさなんかも、初期高度を示す変数altitudeに依存させちゃってるので、altitudeを0mとかにすると画面が変になります。なので、元々のaltitudeは画面最大高度ということにし、新しく高度用変数positionを追加することにしました。で、このpositionを逐次更新することにしdisplacementは廃止します。

	var altitude = 828;		//	初期高度画面最大高度 m
	var startDate;			//	開始時刻
	var lastDate;			//	最後の移動処理の時刻
	var velocity = 0;		//	初速度 m/s
	var displacement = 0;		//	変位 m
	var position = 0;		//	高度 m

 それと、空気抵抗も落ちるの前提だったので、これを進行方向に逆らう形に直す。

	function sign(value) {
		return (value < 0) ? -1 : 1;
	}

	function updateTime(deltasec) {		
		・・・
		var spv2 = s / 2 * p * velocity * velocity * sign(velocity);
		・・・
		velocity += (-9.8 + (-a * Lnv - b * spv2) / m) * deltasec;
		・・・
	}

 こうしないと、投げ上げる時に空気抵抗で上方向に加速しちゃうことになるんでな。

 新しく追加したsignメソッドは、引数で受け取ったvalueの値が正なら1、負なら-1を返すというもの。その中でやってる

		return (value < 0) ? -1 : 1;

 は三項演算子( ? : )を使ったもので、条件によって値を変えたい時に使うもんです。

 

    条件 ? 条件が真の時の値 : 条件が偽の時の値

 

 結果、以下のように書いたことと同じ意味になります。

	if (value < 0) {
		return -1;
	} else {
		return 1;
	}

 後は、displacementの削除、positionの追加に合わせた細かな調整なので、説明は省略。重要なのはupdateTime関数の計算の変更で、こうすることで速度が上向きなら下向きに、下向きなら上向きに空気抵抗が働くことになる。

 これで準備OKなんで、例えば20m/sの初速度を与えれば…

 

    var velocity = 20;                    //    初速度 m/s

 ↓こうなる

 

http://www.tetera.jp/xcc/ameba/free-throw.html

 

 なかなかいい動きします。

 で、ここまできたら、せっかくの3次元表示なんで、縦横斜め、八方になげられるようにしたいですよね。私はしたいです。

 

 全然関係無いけど、よく言われる四方八方の8や、八極拳の8は3次元空間のx,y,z3軸、左右、上下、前後の組み合わせの8だから、平面上で8方向想像してた人、間違いだからそれ。一極集中(ビッグクランチ)の太極拳と、全方位膨張(ビッグバン)の八極拳です。小学校の頃、二極拳〜七極拳とかもあるのかと、そんな風に考えていた時期が俺にもありました。

 
 なので高度を扱うだけの上下方向のみだった速度や加速度、位置を3次元に拡張します。

 スカラ:scalarからベクタ:vectorへ。

 スカラってのは、これまで使ってきた単独の量のことね。

 

  5とか、2とか

 

 ベクタってのは、複数のスカラで構成される量。

 例えば、x軸、y軸、2つの座標値をペアにした2次元ベクトルってのを習ったと思いますが、あれがベクタです。

 1つの点の位置を表現するのに2つのスカラ値(x、y)を使っていたでしょ。

 

  (5, 2)とか

 

 

 ベクトルもベクタも、英語のvectorをどう読むかであって、全く同じだけど、日本では、ベクトルは「向き」ってイメージが強いっすね。

 

 でも複素数をベクタで表現(実数部, 虚数部)したりもするんで、一概に「向き」って言っちゃうのはどうかと思われ。スカラ、ベクタ混在で構成するベクタとかもあるし。そもそもスカラ値のプラスマイナスからして数直線上の向きって言えなくもないし…

 ちなみに複素数の複素平面(ガウス平面というのじゃよ)を見て、あれ、これ2次元表現するのにちょうどよくね?って極座標使ってeのなんちゃらってやってsin、cosの式を簡潔に書いたりするとか、3次元でも使える複素数あるんじゃねとか言ってハミルトンが4元数とか言い出す話はまた今度。

 

 まあどっちでもいい。

 

 で、Three.jsにも3次元ベクタがオブジェクトとして定義されてるんで、こいつを使って、先のプログラムを書き直しました。

	var velocity = new THREE.Vector3( 0.7, 20, 0);	//	初速度 m/s
	var position = new THREE.Vector3( 0, 1.7, 0 );	//	初期位置 m

 THREE.Vector3はx,y,zプロパティを持つオブジェクトです。

 プロパティ?オブジェクト?って人は「走れJavaScriptちゃん!」から出直してこい!

 ま、それはいいとして、上のように書くと、velocityは(x、y、z)要素の順に(0.7, 10, 0)が設定されたTHREE.Vector3オブジェクトを示すようになり、positionは( 0, 1.7, 0 )が設定されたTHREE.Vector3オブジェクトを示すようになります。

 で、このpositionのx、y、z値をsphereのpositionにcopyメソッドを使って設定しています。実はTHREE.Meshの持つpositionプロパティもTHREE.Vector3なのでこんなことができる。「超高校級のMikuMikuDance」で話した「positionはカメラオブジェクトのプロパティで、3次元空間でのカメラ座標を示す。この3D座標を表現するプロパティ自身もオブジェクト」もTHREE.Vector3。

	function arrengeObjects() {
		・・・
		sphere.position.copy(position);

 それと床に立方体を使うのはやめて平面(THREE.PlaneGeometry)にしました。ここら辺は自分で調べてみてください。

 updateTimeメソッドもTHREE.Vector3に合わせて変更してるけど、x、y、zそれぞれの要素に対して運動方程式で計算してるだけで違いはないです。

 加速度のy成分だけ重力加速度が追加されるわけやね。

		var F = new THREE.Vector3();
		F.x = - nR * velocity.x 
			- D * (velocity.x * velocity.x) * sign(velocity.x);
		F.y = - nR * velocity.y 
			- D * (velocity.y * velocity.y) * sign(velocity.y); 
		F.z = - nR * velocity.z 
			- D * (velocity.z * velocity.z) * sign(velocity.z); 

		var acc = F.divideScalar(m);
		acc.y += -9.8;

 それと人間を投げあげるのは豪快すぎるので、バレーボール投げあげることにします。

 

  半径:10cm

  質量:200g

 

としました。それに合わせ、粘性抵抗

 

 

については、バレーボールにしたことだし素直にナビエストークの式使って

 

    F = 6πrηv

 

とします。結局人間用のαがわからんかったからね。

 

 THREE.Vector3のaddメソッドは、引数で渡したTHREE.Vector3のx,y,z値を加算するというものです。

 例えば、a、b、二つのTHREE.Vector3オブジェクトがあれば

 

        a.add(b)

 

でaのx,y,zは、それぞれbのx,y,zが加算された状態になります。

 

   a.x += b.x;

   a.y += b.y;

   a.z += b.z;

 

 こんな感じ。「+=」は左辺の変数(またはプロパティ)に右辺の値を加算という意味ね。

 multiplyScalarの場合は、引数で渡した値がx,y,z値にかけられます。

 

        a.multiplyScalar(2)

 

なら

 

        a.x *= 2;

        a.y *= 2;

        a.z *= 2;

 

とやったことと同じです。「*=」は「+=」の掛け算版です。

 でもってdivideScalarはその割り算版。

 cloneメソッドは複製で

 

    b = a.clone();

 

でbは、aのx,y,z値を持つ、新しいTHREE.Vector3オブジェクトを示すことになります。

 

 詳細はupdateTimeメソッド見てもらうとして、これでようやく斜め発射もできるようになる。

 で、ここまできたら床で跳ね返らせたいわけですよ。

 なので、床に当たると速度ベクタを運動エネルギー保存則使って変換します。

 

   v' = v - 2(v・n)n

 

   v':衝突後の速度ベクタ

   v:衝突時の速度ベクタ

   n:床の法線ベクタ

   

 v・nってのは内積です。

 THREE.Vector3にはdotメソッドとして用意されてます。便利。

 完全弾性衝突なんで、エネルギー減らず、減るのは空気中の移動による抵抗だけってことで、ボールはしつこく跳ね続けます。

 

 ↓それが、これだ!

http://www.tetera.jp/xcc/ameba/free-throw-3d.html

 

    ↓jsdo.it版

http://jsdo.it/reborn_xcc/ULWR

 

 完全弾性衝突とか内積とか、法線ベクタとか、詳細は次回!

AD