ページ 11

斜めの線分に衝突後の移動成分

Posted: 2009年3月14日(土) 17:06
by スー
こんにちは。
ブロック崩し的なゲームを今作っています。
斜めの壁にボールがぶつかった時の移動量の計算がうまくいきません。
うまくいかないと言うのが、
1、斜めの壁にぶつかった時にボールが止まる時がある。(傾斜が緩い所で特に)
2、壁にぶつかった後に飛んでいく角度がおかしい
   /
  /
 / 
  ↑
  ○
上記の場合ぶつかった後は→に行くのが正しいですよね?
なのに 右下に行きます。

弾のクラスです。
void Shot::Action(){
	for(int l=0;l<10;l++){
		x[0] += vx/10;
		y[0] += vy/10;
		//当たり判定
		for(i=0;i<MIRRORMAX;i++){
			if(mir.used == true){
				double D = distance(mir.sx, mir.sy, mir.ex, mir.ey, x[0], y[0]);
				if(D < 10 && hit == false){
					Bound(i);
					hit = true;
				} else if(D >= 10)hit = false;
			}
		}
	}

	for(int k=0;k<9;k++){
		x[9-k] = x[8-k];
		y[9-k] = y[8-k];
	}
	if(x[9] < -32 || x[9] > 640+32 || y[9] < -32 || y[9] > 480+32)used = false;
}
void Shot::Bound(int i){
	float kata_x = ( mir.ex - mir.sx );
	float kata_y = ( mir.ey - mir.sy );
	float hou_vx = kata_x;
	float hou_vy = -kata_y;
	float sei_x = hou_vx / sqrt( pow(hou_vx, 2) + pow(hou_vy, 2) );
	float sei_y = hou_vy / sqrt( pow(hou_vx, 2) + pow(hou_vy, 2) );
	float touei = (-vx * sei_x) + (-vy * sei_y);
	float Px = touei * sei_x;
	float Py = touei * sei_y;
	vx = Px;
	vy = Py;
}


http://www5f.biglobe.ne.jp/~kenmo/progr ... turi5.html
参考にさせて頂いているサイトです。

どこがおかしいか教えてください。

環境は
WindowsXP Home
VC++2005
DXライブラリを使っています。

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月14日(土) 17:48
by fumi
役に立てるかわかりませんが…

いくつか聞きたいことがあります。
1.Dはdistance関数で取得しているようですが、ここで渡している引数は全て入力ですか?
2.mir構造体配列のメンバは何を示すますか?

ちょっとこの辺を説明してくれないとわからないです。

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月14日(土) 17:56
by fumi
すみません、質問の追記です。

vxは画面の右向きが正、vyは画面の下向きが正…でよろしいでしょうか?

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月14日(土) 18:16
by スー
ごめんなさい。情報が少なすぎましたね;;

>vxは画面の右向きが正、vyは画面の下向きが正…でよろしいでしょうか?
はい。vxが右向きに+ vyが下向きが+です。

Mir構造体は線分です。(Mirrorですw)
sx,syが線の始点の座標
ex,eyが線の終点の座標です。
#define MIRRORMAX 256

struct Mirror{
	int sx,sy,ex,ey;
	float angle;
	int cr;
	bool used;
};
Mirror mir[MIRRORMAX];
void AddMir(int SX,int SY, int EX,int EY, int CR1, int CR2, int CR3){
	for(i=0;i<MIRRORMAX;i++){
		if(mir.used ==false){
			mir.used = true;
			mir.sx = SX;
			mir.sy = SY;
			mir.ex = EX;
			mir.ey = EY;
			mir.cr = GetColor(CR1,CR2,CR3);
			break;
		}
	}
}
void DrawMir(){
	for(i=0;i<MIRRORMAX;i++){
		if(mir.used == true){
			DrawLine( mir.sx , mir.sy , mir[i].ex , mir[i].ey , mir[i].cr );
		}
	}
}


>1.Dはdistance関数で取得しているようですが、ここで渡している引数は全て入力ですか?
入力が何なのかよくわからないですが、線の始点の座標と終点の座標と弾の座標を引数にしています。

// 線分と円とのあたり判定
// 線分の始点x、y 終点x、y 円の中心x、y
struct Vector{
	float x, y;
};
double distance(float x1,float y1,float x2,float y2,float x3,float y3){
    Vector A,B;
	double a,b,t,D;
    A.x = x2-x1;
    A.y = y2-y1;
	a = pow(A.x, 2)+pow(A.y, 2);
	b = A.x*(x1-x3) + A.y*(y1-y3);
	t = -b/a;
	if (t < 0) t = 0;
	if (t > 1) t = 1;
    B.x = x1 + A.x*t;
    B.y = y1 + A.y*t;
	D = sqrt (pow((x3-B.x), 2) + pow((y3-B.y), 2));
	return D;
}

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月14日(土) 18:59
by SCI
少し重くなりますが、

(1)y軸を始線とし、壁の角度だけ速度ベクトルを負方向に回転
(2)y成分を反転
(3)正の回転で元に戻す

が直感的でしょうか。最適化とか考えていませんが(笑)

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月14日(土) 19:36
by fumi
もしかしたら、当たり判定がちゃんとできていないのかもしれません。

distance関数の返り値が10未満なら「当たっている」としたいんですよね?

distance関数内の
a = pow(A.x, 2)+pow(A.y, 2);
b = A.x*(x1-x3) + A.y*(y1-y3);

t = -b/a;

if (t < 0) t = 0;

if (t > 1) t = 1;
この辺は何をしているのでしょうか?(特にtとbについて)

ちなみに入力か?というのは、関数に引数を渡す際、その値が関数内で変更されないか?
ということを聞きたかっただけです。わかりにくくてすみません(汗)

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月14日(土) 20:16
by SCI
fumiさん
ベクトルの内積を利用して、「線分と点の距離」を計算しているようです。
垂線が線分の範囲を超える場合は、端点からの距離を返しているので、特に問題はないと思います。

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月14日(土) 23:06
by fumi
ちょっとこちらのdistance関数と入れ替えてみてください↓
float distance(float x1,float y1,float x2,float y2,float x3,float y3)
{
	//点1と円の中心ベクトルを求める
	float x1_x3 = x3 - x1;
	float y1_y3 = y1 - y3;

	//点1と点2からなるベクトルを求める
	float x1_x2 = x2 - x1;
	float y1_y2 = y1 - y2;

	//ノルムの2乗を求める
	float a = pow(x1_x2, 2.0f)+pow(y1_y2, 2.0f);

	//内積
	float nai = x1_x3*x1_x2+y1_y3*y1_y2;

	//射影
	float syaei_x = nai*x1_x2/a;
	float syaei_y = nai*y1_y2/a;
	

	//円と法線のベクトルを求める
	float housen_x = x1_x3 - syaei_x;
	float housen_y = syaei_y - y1_y3;
	
	//距離を円の中心と線分の距離を求める
	float dist = sqrt( pow(housen_x, 2.0f)+pow(housen_y, 2.0f) );

	

	return dist;
}
たぶんやってることは同じだと思うので、変わらないかもしれませんが…

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月14日(土) 23:11
by SCI
fumiさん
それは「点と直線の距離」です。

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月14日(土) 23:36
by SCI
反射計算時に座標を回転する方法の場合、

V' = (V * R(-θ) * [1, 0; 0, -1]) * R(θ)

といった感じになります。
Rは回転行列、θはy軸を始線としたときのベクトルの偏角です。
[...]は要はy座標だけ負に反転する2次正方行列です。

動作確認していないので欠陥があるかも知れませんが・・・

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月14日(土) 23:46
by SCI
なんか表記が逆に・・・
V' = R(θ) * ([1, 0; 0, -1] * R(-θ) * V)
ですね。

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月15日(日) 00:23
by fumi
本当ですね…

見事に線分の延長線上でも当たったことになってましたorz

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月15日(日) 00:52
by fumi
Bound関数内の

float hou_vx = kata_x;

float hou_vy = -kata_y;



float hou_vx = kata_y;

float hou_vy = -kata_x;

にしてみて下さい。

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月15日(日) 00:52
by スー
fumiさん、SCIさん返事ありがとうございます。

>fumiさん
線分と円の判定は龍神録プログラミングの館の34章に載っているで是非見てみてください

>SCIさん
>V' = R(θ) * ([1, 0; 0, -1] * R(-θ) * V)
回転行列とかはあまり詳しくないのですが、この式は線分を縦にする式でよいのでしょうか?

>(1)y軸を始線とし、壁の角度だけ速度ベクトルを負方向に回転
>(2)y成分を反転
>(3)正の回転で元に戻す
あと是非この式を使ってみたいのですが、イメージが掴めないです。
もうちょっとわかりやすく教えて頂けないでしょうか?
数学の知識はベクトルの初歩程度なので;;

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月15日(日) 00:55
by スー
>fumiさん
float hou_vx = kata_y;
float hou_vy = -kata_x;
に変更してみましたが、結果は変わらなかったです。

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月15日(日) 00:57
by SCI
イメージはそんな感じです。始線はx軸でしたね(汗
壁をx軸上に回転させる→y成分を反転→元に戻す、です。多分、できるはずです。

後ほど、ちゃんとした式と図を書きますね。

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月15日(日) 02:16
by スー
>後ほど、ちゃんとした式と図を書きますね。

よろしくお願いします。

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月15日(日) 02:19
by SCI
コードを書きました。汚いです(笑)
動体をちゃんと作るのは面倒なので、なんだかすごいことになってますが、豆腐が反射します。
反射の処理はreflect関数です。
最適化の余地は十分あると思いますが、日本語を素直にコードにするとこんな感じだと思います。
参照使ってますが、ポインタでも同じですね。

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月15日(日) 02:26
by fumi
原因がわかりました。(おそらくですが。違っていたらすみませんm(_ _)m)

Bound関数内の部分の
float kata_x = ( mir.ex - mir.sx );

	float kata_y = ( mir.ey - mir.sy );

	float hou_vx = kata_x;

	float hou_vy = -kata_y;

	float sei_x = hou_vx / sqrt( pow(hou_vx, 2) + pow(hou_vy, 2) );

	float sei_y = hou_vy / sqrt( pow(hou_vx, 2) + pow(hou_vy, 2) );

	float touei = (-vx * sei_x) + (-vy * sei_y);

	float Px = touei * sei_x;

	float Py = touei * sei_y;

	vx = Px;

	vy = Py;

float kata_x = ( mir.ex - mir.sx );

	float kata_y = ( mir.ey - mir.sy );

	float hou_vx = kata_y;  //入れ替えている

	float hou_vy = kata_x;  //同上(上とここの符号は揃えないといけない…かもしれない)

	float sei_x = hou_vx / sqrt( pow(hou_vx, 2) + pow(hou_vy, 2) );

	float sei_y = hou_vy / sqrt( pow(hou_vx, 2) + pow(hou_vy, 2) );

	float touei = (-vx * sei_x) + (-vy * sei_y);

	float Px = touei * sei_x;

	float Py = touei * sei_y;

	vx += 2*Px;  //ここに注意

	vy += 2*Py;  //同上


参考にされたページでは、(求めるベクトル)=Va+2Pとなっていますが、おかしいのではないでしょうか?

符号の正負はちょっとよくわからないんですが、一応、こちらではしっかり90度反射しました。

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月15日(日) 02:29
by fumi
訂正ですm(_ _)m

参考にされたページでは、(求めるベクトル)=Va+2Pとなっていますが、「スーさんのコードのこのあたりが」おかしいのではないでしょうか?

Re:斜めの線分に衝突後の移動成分

Posted: 2009年3月15日(日) 03:56
by スー
>SCIさん
わざわざソースを書いて頂きありがとうございます。
とてもわかりやすかったです。
これだけの計算式で書けるのは魅力的ですね。

>fumiさん
fumiさんの言われた所を追加してみたところ無事に綺麗な角度で反射しました。
つまるところ、ただの私の見落としだったってことです><;

fumiさん、SCIさんありがとうございました。