円と矩形が衝突した際にスムーズに移動させるには

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
コレジャナイ

円と矩形が衝突した際にスムーズに移動させるには

#1

投稿記事 by コレジャナイ » 11年前

_| |_ 
 ○
この様な形で矩形の角に円が衝突した際に

上入力がされれば円を右へ微移動させつつスライドして通路に入る(現状では角に衝突して停止)

させるような処理を施すにあたってどのような計算をすれば宜しいのでしょうか?

現在当たり判定は完了してます。
色々調べまわっていたのですが、当たり判定の参考サイト様は多数見つかるのですが、その先の事はとんと少なく・・・。

力技で
円から見て障害物が左上にある時、上入力がされたら ~~~ の様に場合分けしていけば出来なくは無いのですが、恐らく不自然なスライドになるかと思います。

もっと一般的な方法があれば、考え方などで構いませんのでご存知の方いらっしゃったら宜しくお願いします。

アバター
GRAM
記事: 164
登録日時: 13年前
住所: 大阪

Re: 円と矩形が衝突した際にスムーズに移動させるには

#2

投稿記事 by GRAM » 11年前

自然なスライドというのがどういうことなのかを明文化する必要があると思いますが、
取りあえず当たったら補正というだけならこんな感じはどうでしょう?

以下コード
► スポイラーを表示
プロジェクトの形で載せておきます。
dxtest.zip
(638.89 KiB) ダウンロード数: 149 回
あんまり衝突後についての記述がないのは、
「当たってるかどうか」というのがある意味2択の問題なのに対して
「当たっていたらどうするか」というのはぶっちゃけその時によってやりたいことが変わるからだと思います。
まず具体的な挙動が定まらないことには(たとえば↑のコードだと補正時に円の速度が位置に応じて変わってしまうが、実はこれは一定に保ちたいんだとか)
コードは書けないんじゃないかと思います。

コレジャナイ

Re: 円と矩形が衝突した際にスムーズに移動させるには

#3

投稿記事 by コレジャナイ » 11年前

ご返答有難う御座いますm(_ _)m

ソースコードにプロジェクトまで、本当に感謝致します。

イメージとしては障害物の角が円の外周の上を回るようにしてスライドしていく形を想像しておりましたが、まさにGRAMさんのプロジェクト通りでした。
そしてご指摘通り、出来れば速度は一定に保ちたいです。
ただ角ではなく辺に衝突した際に斜め入力で自然にスライドする処理も角の処理と同じなため、単純に衝突判定後 円の移動速度を増加させて再処理するのでは駄目だ、という所までは理解しました。

このままでも本当に充分ですし、速度の処理が面倒(高度な数学知識を要する)などであればこのままで行きたいと思います。
ソースコードでなくとも考え方やどういう風にすれば良いなどだけで構いませんので、お時間の空いた時にでもお返事頂けると助かります。

コレジャナイ

Re: 円と矩形が衝突した際にスムーズに移動させるには

#4

投稿記事 by コレジャナイ » 11年前

すみません、先程とは真逆の返答となってしまいますが、これ以上のステップアップを計る前に提示して頂いたソースコードを理解する事に全力を尽くしたいと思います。

本当助かりました!
ご返答有難う御座いましたm(_ _)m

コレジャナイ

Re: 円と矩形が衝突した際にスムーズに移動させるには

#5

投稿記事 by コレジャナイ » 11年前

C言語の範囲内でも恐らくちゃんと理解出来てる人にはすんなり分かるのでしょうが、未熟者ゆえ分からない箇所が出てきてしまいましたOTL
長いこと考えてみたり、検索で調べたりしてみたのですがどうしても分からず・・・OTL
丸写しで座標箇所のみ変えるなどはしたくなく、しっかり理解したかったので解決後に申し訳ないのですが、分からない箇所をコメントしましたのでお暇な時にお返事頂けると助かりますorz

コード:

#include <DxLib.h>
#include <Vector.h>
 
typedef Linear::Vector<double> Vec;
 
const double dt = 1.0/60.0;
 
 
 
 
struct AABB
{
    AABB( Vec s, Vec l)
        :s_(s),l_(l / 2.0)
    {
    }
 
    void Draw() const
    {
        DxLib::DrawBox( int( s_.x - l_.x ), int( s_.y - l_.y ), int( s_.x + l_.x ), int( s_.y + l_.y ), 0x00ff00, 0);
    }
 
    Vec s_;     //中心
    Vec l_;     //各辺の長さの半分
};
 
//AABBと位置から最近傍点を求める
Vec GetClosestPointAABB( AABB aabb, Vec p ) ///pは入力関数から受け取った円の方向ベクトル・・・?
{
    for( int i = 1; i <= 2; ++i )	/////2回処理を行うのはX,Yの二回でしょうか・・・?
    {
        if( p[i] < aabb.s_[i] - aabb.l_[i] )  ////中心ー各辺の長さの半分 > 方向ベクトル・・・、ここの処理が難しいですorz
            p[i] =  aabb.s_[i] - aabb.l_[i];
        if( p[i] > aabb.s_[i] + aabb.l_[i] )
            p[i] =  aabb.s_[i] + aabb.l_[i];
    }
    return p;
}
 
 
struct Circle
{
    Circle( Vec s, double r )
        :s_(s),r_(r)
    {
    }
 
    void Update()
    {
        char key[256];
        GetHitKeyStateAll( key );
        if( key[ KEY_INPUT_RIGHT  ] )
        {
            s_.x += 60.0*dt;
        }
        if( key[ KEY_INPUT_LEFT  ] )
        {
            s_.x -= 60.0*dt;
        }
        if( key[ KEY_INPUT_UP  ] )
        {
            s_.y -= 60.0*dt;
        }
        if( key[ KEY_INPUT_DOWN  ] )
        {
            s_.y += 60.0*dt;
        }
    }
 
    //衝突判定と補正
    void Detection( const AABB& aabb ) ////障害物の構造体受け取り
    {
        Vec cp = GetClosestPointAABB( aabb, s_ );	////cpはs_を上記関数で何らかの補正がされた数・・・。
 
        //衝突していた場合
        double r2 = Linear::VDistanceSq( cp, s_ ); //この関数は2点間の距離の2乗を返す
        if( r2 < r_*r_ )
        {
            //方向ベクトルを求めて位置を補正
            Vec orientation = cp - s_;
            orientation.Normalize();            //ベクトルの正規化
            orientation *= -( r_ - sqrt( r2 ) );
            s_ += orientation;
        }
    }
    void Draw() const
    {
        DxLib::DrawCircle( int( s_.x ), int( s_.y ),r_, 0xFF0000 );
    }
 
public:
 
    Vec s_;     //位置
    double r_;  //半径
 
};
 
int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow ){
    if( ChangeWindowMode(TRUE) != DX_CHANGESCREEN_OK || DxLib_Init() == -1 ) return -1; //初期化処理
    SetDrawScreen( DX_SCREEN_BACK );        //裏画面に設定
    AABB aabb1( Vec( 100.0, 300.0, 0.0 ), Vec ( 200.0, 200.0, 0.0 ) );
    AABB aabb2( Vec( 400.0, 300.0, 0.0 ), Vec ( 200.0, 200.0, 0.0 ) );
    Circle c( Vec( 250.0,300.0, 0.0 ), 20 );
    while(!ProcessMessage() && !ClearDrawScreen()){
        c.Update();
        c.Detection( aabb1 );
        c.Detection( aabb2 );
 
        aabb1.Draw();
        aabb2.Draw();
        c.Draw();
 
        clsDx();
        ScreenFlip();//裏画面を表画面に反映
 
    }
    DxLib_End();
    return 0;
}
主にVec GetClosestPointAABBで何が行われているかが一番難解でしたorz

全体の流れとしては
円と障害物の距離を測る→衝突判定→衝突していたらめり込んだ分の位置を戻す
という事だと思うのですが・・・。

100行ほどのコードですのに理解出来ず、本当すみませんOTL

アバター
Tatu
記事: 445
登録日時: 13年前
住所: 北海道

Re: 円と矩形が衝突した際にスムーズに移動させるには

#6

投稿記事 by Tatu » 11年前

GetClosestPointAABBについて
この関数は矩形の中で円の中心に最も近い点がどこなのかを求めています。

引数のpは円の中心の位置ベクトルですね。
for文で2回処理しているのはx座標とy座標の2回ですね。

矩形で円の中心に最も近い点の座標について
ここではx座標だけを考えます。

まず、円の中心が矩形の左端よりもさらに左にある場合、
矩形の中で最も円に近い場所のx座標は矩形の左端になります。

次に円の中心が矩形の右端よりもさらに右にある場合、
矩形の中で最も円に近い場所のx座標は矩形の右端になります。

では、円の中心のx座標が矩形の左端と右端の間にある場合はどうなるのでしょうか?
ここでコードを見てみるとreturn p;と書かれています。

関数を呼び出したとき、pには引数で指定した円の中心の位置ベクトルが入っています。
もし、pが全く変更されなければ返り値は円の中心の座標のままです。
円の中心のx座標が矩形の左端と右端の間にある場合、
矩形の中で円の中心に最も近い点のx座標は円の中心のx座標と等しくなるため、
円の中心のx座標を変更する必要がありません。
よって円の中心のx座標が矩形の左端と右端の間にある場合の処理は必要ありません。


補正について

円の中心から矩形の一番近い点への方向ベクトルを求めて、正規化。
これでめり込んでいる方向の単位ベクトルが得られます。

-(r_-sqrt(r2))はどのくらい戻すのかという補正ベクトルの大きさを求めています。
-をかけているのはめり込んでいる方向の逆に戻さないとますますめり込んでしまうからです。
大きさと正規化した方向ベクトルをかけることで補正ベクトルが求められます。
あとは円を補正ベクトルによってずらしています。

アバター
GRAM
記事: 164
登録日時: 13年前
住所: 大阪

Re: 円と矩形が衝突した際にスムーズに移動させるには

#7

投稿記事 by GRAM » 11年前

>>Tatuさん
なんかもはや言うことがなくなってしまいました。ありがとうございますw

コード:

//AABBと位置から最近傍点を求める
Vec GetClosestPointAABB( AABB aabb, Vec p ) 
{
    for( int i = 1; i <= 2; ++i )   /////2回処理を行うのはX,Yの二回でしょうか・・・?
    {
        if( p[i] < aabb.s_[i] - aabb.l_[i] )  ////中心ー各辺の長さの半分 > 方向ベクトル・・・、ここの処理が難しいですorz
            p[i] =  aabb.s_[i] - aabb.l_[i];
        if( p[i] > aabb.s_[i] + aabb.l_[i] )
            p[i] =  aabb.s_[i] + aabb.l_[i];
    }
    return p;
}

まず
最近傍点という言葉ですが、これはAABB(四角形)上の点で、かつ任意の点に対して最も近い点という意味です(図参照)
最近傍点.png
最近傍点.png (7.11 KiB) 閲覧数: 3841 回
説明不足で申し訳なかったのですが、
Vector<T> クラス(ここではtypedefしてVecという名前になっている)は

Vec v(1.0, 1.0, 1.0 ); のようにしてインスタンス化した後
double x = v.x; //これはx成分を取り出す
x = v[1];     //これもx成分を取り出す(添え字を利用)

というようにxが1, yが2, zが3に対応した添え字でも値を取り出せるようになっています。(もちろん値はどちらで取り出しても同じです)
理由は普段使う時にはメンバ変数としてアクセスしたほうが自分的に読みやすく、
上記のコードのような繰り返しの処理には添え字アクセスのほうがコピペを防げるからです
GetClosestPointAABB関数ですが
目的:上記のようなある点に対するAABB上の最近傍点を求める

まず
///pは入力関数から受け取った円の方向ベクトル・・・?
方向ベクトルというのは単に用語の間違いなのか、そもそも考え方が間違っているのかわかりませんが
敢えて言うならばpは円の中心の位置ベクトルです。つまり単純に円中心の座標が入っています。
if( p < aabb.s_ - aabb.l_ ) ////中心ー各辺の長さの半分 > 方向ベクトル・・・、ここの処理が難しいですorz

ベクトルという言葉を仰々しくとらえすぎかと思います。
aabb.s_とは単純にAABBの中心の座標です。
一方aabb.l_にはAABBの各辺の(つまりaabb.l_.xにはx方向の辺の)長さが入っています。
iに具体的な数字を入れてみて、たとえば
aabb.s_[1] - aabb.l_[1]とすると、これは上記のように
aabb.s_.x - aabb.l_.xと同じ意味なので
中心のx座標からx方向の辺の長さの半分を引いたものなので、要は四角形の左側の辺のx座標になります。
同様にaabb.s_[1] + aabb.l_[1]は四角形の右側の辺のx座標になります。

・・・この関数について言うならば、後はTatuさんの説明のとおりです。
結果として最近傍点が返ります。
あとは
①最近傍点と円の中心との距離r2の二乗を求めて、その大きさが円の半径の2乗以上ならあたっている
 (2乗なのはそのほうが計算が軽いから)
②あたっているならば、円の中心から最近傍点までの方向ベクトルを求めて正規化(大きさを1に)
③そのベクトルに、補正量(これは円の半径と√r2との差つまり円が四角にめり込んでいる量)を求めて方向ベクトルに掛ける
④そうやって求めた補正ベクトル分だけ円の中心をずらす

ということをやっています

コレジャナイ

Re: 円と矩形が衝突した際にスムーズに移動させるには

#8

投稿記事 by コレジャナイ » 11年前

申し訳御座いません、実装試している間にGRAMさんにまでご迷惑をorz

お二方とも本当に有難う御座います!
お陰様でどうにかC言語の範囲内にて実装する事が出来ました!

これは円と円の衝突時には近傍点を円と円の交点にしてあげれば問題ないですよね?

かなり時間が掛かってしまいましたが、すっきり理解する事が出来ました。
本当に有難う御座いました!

コレジャナイ

Re: 円と矩形が衝突した際にスムーズに移動させるには

#9

投稿記事 by コレジャナイ » 11年前

円と円の衝突は2交点の中点が近傍点でした、すみません。

アバター
Tatu
記事: 445
登録日時: 13年前
住所: 北海道

Re: 円と矩形が衝突した際にスムーズに移動させるには

#10

投稿記事 by Tatu » 11年前

円と円の場合、近傍点を求める必要はないと思います。

円と円の場合、円の中心ともう一つの円の中心の距離を調べ、
それが二つの円の半径の合計よりも小さい場合に補正をします。
(円と円が近すぎて重なっている状態の時に補正をする)

補正する方向は障害物である円の中心からもう一つの円の中心までの方向になり、
大きさは二つの円の半径の合計から2つの円の中心の距離を引いたものになります。

コレジャナイ

Re: 円と矩形が衝突した際にスムーズに移動させるには

#11

投稿記事 by コレジャナイ » 11年前

あぁ~・・・なるほど!

(割と簡単な処理だったので)既に実装してしまったのであれですが、次回以降はそちらの方法でしっかり実装しますー!
有難う御座いますm(_ _)m

閉鎖

“C言語何でも質問掲示板” へ戻る