原始人のプログラミング日記

println(++C++); // println(C++), ++C;

円筒と球の衝突判定 はじめました

円筒と球の衝突判定って簡単なのでは?

円筒って便利ですよね?

キャラクター同士の衝突判定を考えたとき、「キャラクターの当たり判定を球にして移動の軌跡を円筒にする」っていうのが一番最初にぱっと思いつきますよね?そうでもないですか?

だけどネットにそこまで落ちてない

ネットで衝突判定のアルゴリズム探すんだけど、検索方法が悪いのかいまいち見かけない気がする。

簡単だから

仕方ないので図を書いて考えると、あっさり答えが出てしまった。
なるほど、線分と球の衝突判定をほんの少し拡張しただけなので、あえてネットにまで書く必要がないんですね。(というか、円筒って太さ持った線分だから、そんなの当然の如く分かるでしょ的スタンスなのかもしれない。)

実装は?


f:id:kawazuini:20170728011215j:plain
適当な図(左が線分と球、右が線分から円筒にする考え方)

線分と球の衝突判定
bool KSphere::operator*(const KSegment& aSegment) const {
    KVector vec1(mPosition - aSegment.mVec1);
    KVector vec2(mPosition - aSegment.mVec2);
    KVector seg(aSegment.mVec2 - aSegment.mVec1);
    KVector para(vec1.extractParallel(seg));

    if (seg.length() == 0.0f) { // 線分の長さがないときは点として判定
        return vec1.length() < mRadius;
    }

    //  中心座標から線分に垂らした垂線との交点による内分比
    float t(para.length() / seg.length());

    float dist; // 中心座標と線分の最短距離
    if (0 < t && t < 1) dist = (para - vec1).length();
    else { // 交点が線分上にない(線分の端との距離を計測)
        if (t < 0) dist = vec1.length();
        if (1 < t) dist = vec2.length();
    }
    return dist < mRadius;
}
円筒と球の衝突判定*1
bool KSphere::operator*(const KCylinder& aCylinder) const {
    KSegment seg(aCylinder.mVec1, aCylinder.mVec2);
    if (!aCylinder.mRounding) {
        KVector dir(seg.direction());
        float len(seg.length() / 2);
        KVector center(seg.mVec1 + dir * len);
        float lenDiff(Math::max(0.0f, len - aCylinder.mRadius));
        seg = KSegment(center + dir * lenDiff, center - dir * lenDiff);
    }
    return KSphere(mPosition, mRadius + aCylinder.mRadius) * seg;
}

円筒と球の衝突判定ってどちゃくそ重い

見たらわかるけど、分岐処理があほみたいに多い。
たとえば「キャラクターの当たり判定を球にして移動の軌跡を円筒にする」みたいなことは、処理が重すぎて毎フレームできたものじゃない。
本末転倒だけど、ネットにないのは実用性がないからなのかもしれない。

妥協案として、キャラクターは自分の半径以上1回の処理で動けないってルールを追加するのが妥当な*2気がする。*3
キャラクターを速く動かしたいなら、球と球の衝突判定にしてフレームレート上げましょう!!

いつ使うんですか?

めったに使えたものではないので、銃弾とかの稀に発生するオブジェクトなら使えるかもしれない。
ただ、それでも線分として処理をした方が速い。

極太レーザーならありなのかもしれない。

*1:そこまで検証してない

*2:アニメーション的にも無理がなくなる

*3:もっといい方法、あったら教えてください。