Archive 17/01/2023.

Physics Multi-Sphere Cast

SirNate0

I needed to be able to get multiple collisions for a sphere cast. There was a brief guide for how to do it somewhere on the bullet forum (basically combine the code from the multi-ray-cast with the code for the sphere-cast). Here is the result:

// For the header
void SphereCastMultiple(PhysicsWorld* pw, Vector<PhysicsRaycastResult>& results, const Ray& ray, float radius, float maxDistance, unsigned collisionMask = M_MAX_UNSIGNED);


// In the .cpp file
#include <Urho3D/ThirdParty/Bullet/btBulletCollisionCommon.h>
#include <Urho3D/ThirdParty/Bullet/btBulletDynamicsCommon.h>
#include <Urho3D/Physics/PhysicsUtils.h>


struct AllHitsConvexResultCallback : public btCollisionWorld::ConvexResultCallback
{
    AllHitsConvexResultCallback(const btVector3& rayFromWorld, const btVector3& rayToWorld)
        : m_convexFromWorld(rayFromWorld),
          m_convexToWorld(rayToWorld)
    {
    }

    btAlignedObjectArray<const btCollisionObject*> m_collisionObjects;

    /// used to calculate hitPointWorld from hitFraction
    btVector3 m_convexFromWorld;        
    btVector3 m_convexToWorld;

    btAlignedObjectArray<btVector3> m_hitNormalWorld;
    btAlignedObjectArray<btVector3> m_hitPointWorld;
    btAlignedObjectArray<btScalar> m_hitFractions;


    virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& rayResult, bool normalInWorldSpace)
    {
        m_collisionObjects.push_back(rayResult.m_hitCollisionObject);
        btVector3 hitNormalWorld;
        if (normalInWorldSpace)
        {
            hitNormalWorld = rayResult.m_hitNormalLocal;
        }
        else
        {
            //need to transform normal into worldspace
            hitNormalWorld = rayResult.m_hitCollisionObject->getWorldTransform().getBasis() * rayResult.m_hitNormalLocal;
        }
        m_hitNormalWorld.push_back(hitNormalWorld);
        btVector3 hitPointWorld;
        m_hitPointWorld.push_back(rayResult.m_hitPointLocal);
        m_hitFractions.push_back(rayResult.m_hitFraction);
        return m_closestHitFraction;     
    }
};


void SphereCastMultiple(PhysicsWorld* pw, Vector<PhysicsRaycastResult>& results, const Ray& ray, float radius, float maxDistance, unsigned collisionMask)
{

    if (maxDistance >= M_INFINITY)
        URHO3D_LOGWARNING("Infinite maxDistance in physics sphere cast is not supported");

    btSphereShape shape(radius);
    Vector3 endPos = ray.origin_ + maxDistance * ray.direction_;

    AllHitsConvexResultCallback
        convexCallback(ToBtVector3(ray.origin_), ToBtVector3(endPos));
    convexCallback.m_collisionFilterGroup = (short)0xffff;
    convexCallback.m_collisionFilterMask = (short)collisionMask;

    pw->GetWorld()->convexSweepTest(&shape, btTransform(btQuaternion::getIdentity(), convexCallback.m_convexFromWorld),
        btTransform(btQuaternion::getIdentity(), convexCallback.m_convexToWorld), convexCallback);

    for (unsigned i = 0; (int)i < convexCallback.m_hitFractions.size(); ++i)
    {
        PhysicsRaycastResult result;
        result.body_ = static_cast<RigidBody*>(convexCallback.m_collisionObjects[i]->getUserPointer());
        result.position_ = ToVector3(convexCallback.m_hitPointWorld[i]);
        result.normal_ = ToVector3(convexCallback.m_hitNormalWorld[i]);
        result.distance_ = convexCallback.m_hitFractions[i] * (endPos - ray.origin_).Length();
        result.hitFraction_ = convexCallback.m_hitFractions[i];
        results.Push(result);
    }
}