円衝突内の円


9

私のプロジェクトの1つに、円形のゲームエリアがあります。この円の中に別の小さな円が動き回っています。私がしたいのは、小さな円が大きな円の外に移動しないようにすることです。下のフレーム2では、小さな円の一部が外側にあることがわかります。外側に移動する直前に戻す方法が必要です。これはどのように行うことができますか?

基本的な例

また、小さな円の速度を更新できるように、大きな円の円弧に沿った衝突点が必要です。この点を計算するにはどうすればよいでしょうか?

小さな円を移動する前に、次の位置を予測し、それが外にある場合は、t = 0とt = 1(t = 1フルタイムステップ)の間の衝突時間を見つけます。衝突時間tがある場合、フルタイムステップではなく、tの間に小さな円を移動するだけです。しかし、繰り返しになりますが、問題は、2つの円ともう1つの円の内側に衝突したときに衝突が発生したときに、その検出方法がわからないことです。

編集:

衝突点の例(緑)見つけたい。たぶん写真は少しずれているかもしれませんが、あなたはアイデアを得ます。

ここに画像の説明を入力してください

回答:


10

大きな円の中心Aと半径がRあり、小さな円の中心Bと半径がr場所に向かって移動するとしますC

この問題を解決するためのエレガントな方法は、使用して、あるMinkovski和(実際には減算を、):半径のディスクを交換するR半径の円板でR-r、半径のディスクr半径のディスクで0すなわち。にある簡単なポイントB。問題は、線と円の交差問題になります。

次に、距離ACがより小さいかどうかを確認するだけですR-r。そうであれば、円は衝突しません。それが大きい場合は、の距離でポイントDを見つけてください。これが小さな円の中心の新しい位置になります。これは、次のようなものを見つけることと同じです。BCR-rAk

  vec(BD) = k*vec(BC)
and
  norm(vec(AD)) = R-r

ギブvec(AD)で置き換えるvec(AB) + vec(BD)

AB² + k² BC² + 2k vec(AB).vec(BC) = (R-r

初期位置が大きな円の内側にある場合、この2次方程式にkは1つの正の根があります。擬似コードで方程式を解く方法は次のとおりです。

b = - vec(AB).vec(BC) / BC²    // dot product
c = (AB² - (R-r)²) / BC²
d = b*b - c
k = b - sqrt(d)
if (k < 0)
    k = b + sqrt(d)
if (k < 0)
    // no solution! we must be slightly out of the large circle

このの値でkは、小さな円の新しい中心はのDようになりBD = kBCます。

編集:二次方程式の解を追加


おかげで、それはエレガントに見えますが、私が理解していることはよくわかりません。たとえば、「BC上の点DをAの距離Rrで見つけるだけ」です。 よく理解できるようにを描いた。したがって、B(AX、AY-(Rr))から始めて、Cが現在の速度になるところです。引用されたテキストを理解する方法:AからRrの距離である線分BC上の点Dを見つけます。しかし、私が描いた図でそれを見る方法は、BだけがAからRr離れているということです。その他のポイントは> AからRr離れます。何が欠けていますか?
dbostream

@dbostream何も欠落していません。2つの円がすでに接触している場合、検出する実際の衝突はありません。衝突はB、とで発生しk=0ます。今、それがあなたが望む衝突解決であるならば、それはオブジェクトの物理的性質についての知識を必要とするので、私の答えではそれをカバーしていません。何が起こるはずですか?内側の円は内側に跳ねるべきですか?またはロール?掃く?
sam hocevar

小さな円が大きな円の弧に沿ってスライドし始めたいです。だから私が間違っていないなら、大きな円の円弧上の衝突点が欲しいので、その法線を使って速度を更新できます。
dbostream

@dbostream動きをこのように制限する必要がある場合は、できるだけ早くその制限に従うことをお勧めします。速度がの場合V、内側の円を円V*tの円周に沿って進めR-rます。これは、V*t/(R-r)ポイントを中心とした角度ラジアンの回転を意味しAます。また、速度ベクトルも同様に回転できます。法線(常にとにかく円の中心に向いている)を知る必要も、他の方法で速度を更新する必要もありません。
sam hocevar 2012年

回転する前に、小さな円を衝突点に移動する必要があります。そして、回転行列を使用して位置を回転させようとしたとき、新しい位置は正確に(ほとんど)Rrが大きな円の中心から離れていませんでしたが、その小さな違いで、次の実行で衝突テストが失敗しました。新しいものを見つけるために位置を回転させる必要がありますか?何かがまっすぐな壁に衝突した場合のようにベクトル操作を使用することはできませんか?
dbostream

4

大きな円が円Aで、小さな円が円Bだとします。

BがA内にあるかどうかを確認します。

distance = sqrt((B.x - A.x)^2 + (B.y - A.y)^2))
if(distance > A.Radius + B.Radius) { // B is outside A }

フレームn-1BがAの内側にあり、フレームnBがAの外側にあり、フレーム間の時間が大きすぎなかった場合(別名Bがあまり速く移動していなかった場合)、Bの相対座標のデカルト座標を見つけるだけで衝突点を概算できますAへ:

collision.X = B.X - A.X;
collision.Y = B.Y - A.Y;

次に、この点を角度に変換できます。

collision.Normalize(); //not 100% certain if this step is necessary     
radians = atan2(collision.Y, collision.X)

t初めてBがAの外側にあるものをより正確に知りたい場合は、すべてのフレームで光線と円の交差を行い、Bから衝突点までの距離が大きいかどうかを比較します。現在の速度。もしそうなら、衝突の正確な時間を計算することができます。


ありがとう、しかしその交差テストをするとき、小さな円の中心から光線を発射することは本当に正しいですか?この写真の真ん中のシナリオになってしまいませんか?つまり、小さな円の円弧上の最初の点が大きな円と衝突するということは、必ずしも速度方向の円弧上の点ではないということです。リンクした画像の下のシナリオのようなものが必要だと思います。最初の投稿に、必要だと思う例を示す新しい写真を追加しました。
dbostream

うーん、シナリオは可能だと思います。多分A.からfurthersあるC上の点Aとし、次いでトレーニングその衝突は(持っているがところで、これを試していない)場合B.Radius + Bの最大移動このフレームは、チェックを持つ新しい円Cと試験
ロイT.

3
少ない単語を使用する場合:if(distance(A、B))>(Ra-Rb)衝突が発生し、小さな円を移動してRa-Rbに等しい距離を取得します。それ以外の場合は、通常、小さな円を移動します。ちなみに、@ dbostreamは投機的連絡先の簡略化された形式に似たものを使用しています。それを検索してみてください。
Darkwings

@Darkwings +1正解です。これにより、サウンドがはるかにシンプルになります。
ロイT.

必要な基本ジオメトリをすべて取り除いたので、簡単に聞こえます。それを「衝突」と名付ける代わりに、それが実際にはそれであるという理由で、それをboundABと名付けることができます:(0,0)にバインドされた自由ベクトルAB 正規化すると、平行からABの直線のバンドルの方程式と、便利な単位ベクトルの両方が得られます。次に、距離Dに対してその単位ベクトルを乗算し、新しく見つかったパラメーターをAに追加して、必要な衝突点を見つけます:C(Ax + Dx、Ay + Dy)。今ではもっと複雑に聞こえますが同じです:P
Darkwings '27

0

(Xa、Ya)を大きな円の位置と半径R、(Xb、Yb)を小さな円の位置と半径rとします。

これら2つの円が衝突するかどうかを確認できます。

DistanceTest = sqrt(((Xa - Xb) ^ 2) + ((Ya - Yb) ^ 2)) >= (R - r)

衝突の位置を見つけるには、バイナリ検索を使用して、ステップ数を固定して、円が衝突する正確な時間を見つけます。ゲームの作成方法に応じて、コードのこの部分を最適化できます(小さなボールの動作に依存しないようにこのソリューションを提供しました。一定の加速または一定の速度がある場合、コードのこの部分を最適化して、単純な式に置き換えられます)。

left = 0 //the frame with no collision
right = 1 //the frame after collision
steps = 8 //or some other value, depending on how accurate you want it to be
while (steps > 0)
    checktime = left + (right - left) / 2
    if DistanceTest(checktime) is inside circle //if at the moment in the middle of the interval [left;right] the small circle is still inside the bigger one
        then left = checktime //the collision is after this moment of time
        else right = checktime //the collision is before
    steps -= 1
finaltime = left + (right - left) / 2 // the moment of time will be somewhere in between, so we take the moment in the middle of interval to have a small overall error

衝突時間がわかったら、最終的な時間での2つの円の位置を計算します。最終的な衝突点は

CollisionX = (Xb - Xa)*R/(R-r) + Xa
CollisionY = (Yb - Ya)*R/(R-r) + Ya

0

Sam Hocevarによって記述されたアルゴリズムを使用して、jsfiddleの円で跳ねるボールのデモを実装しました。

http://jsfiddle.net/klenwell/3ZdXf/

連絡先を識別するJavaScriptは次のとおりです。

find_contact_point: function(world, ball) {
    // see https://gamedev.stackexchange.com/a/29658
    var A = world.point();
    var B = ball.point().subtract(ball.velocity());
    var C = ball.point();
    var R = world.r;
    var r = ball.r;

    var AB = B.subtract(A);
    var BC = C.subtract(B);
    var AB_len = AB.get_length();
    var BC_len = BC.get_length();

    var b = AB.dot(BC) / Math.pow(BC_len, 2) * -1;
    var c = (Math.pow(AB_len, 2) - Math.pow(R - r, 2)) / Math.pow(BC_len, 2);
    var d = b * b - c;
    var k = b - Math.sqrt(d);

    if ( k < 0 ) {
        k = b + Math.sqrt(d);
    }

    var BD = C.subtract(B);
    var BD_len = BC_len * k;
    BD.set_length(BD_len);

    var D = B.add(BD);
    return D;
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.