Boom

You are humanity’s most violent convict, fighting your way out of Space  Prison with a good ol’ Double Barrel.

PhysX Foundation

For the third game project of my second year at The Game Assembly we created a first peson shooter. The project required many new features, one of which were reliable physics with support for bouncing objects. Nvidia's PhysX became a good option as it is open sourced and widely used.

Setting up PhysX is relatively easy. Download the SDK, compile the library, create a new project in your solution and paste in the compiled files.

 

Once the source code is there and the dependencies are linked, the PxFoundation can be created.

       

        myFoundation = PxCreateFoundation(PX_PHYSICS_VERSION, myAllocator, myErrorCallback)

Next up is creating the Physics object which is used to create gemoetry and the scene.

 

 

We create the scene using a PxSceneDesc. The description has many optional properties, setting the filterShader to use the default value is enough however. 

 

Finally we add our first actor to the scene by creating a world plane.

Adding more actors to the scene is done through a framework which returns pointers to the objects.

        

        physx::PxTolerancesScale scale;
        scale.length = 1;
        scale.speed = 10;

        myPhysics = PxCreatePhysics(PX_PHYSICS_VERSION, *myFoundation, scale);

        

        physx::PxSceneDesc sceneDesc(scale);        

        sceneDesc.gravity = physx::PxVec3(0.0f, -9.81f, 0.0f);

        sceneDesc.filterShader = physx::PxDefaultSimulationFilterShader;       

        myScene = myPhysics->createScene(sceneDesc);

 

        myScene->addActor(*physx::PxCreatePlane(*myPhysics, physx::PxPlane(physx::PxVec3(0, 1, 0), 0), *myPhysics->createMaterial(1, 1, 0));

 

class CFramework
    {
    public:
        CFramework() = default;
        ~CFramework() = default;
    
        void Init();
        void Update(float aDeltaTime);

        physx::PxRaycastBuffer CastRay(const Vector3f& aOrigin, const Vector3f& aDirection, const float& aLength, unsigned int aFilter);
        physx::PxController* CreateBoxController(const Vector3f& aPosition, const Vector3f& aSizeFromCenter, unsigned int aWord0, unsigned int aWord1, CGridObject* aUserData, float aStepOffset);
        physx::PxRigidDynamic* CreateDynamic(const physx::PxTransform& aTransform, const physx::PxGeometry& aGeometry, const physx::PxVec3& aVelocity = physx::PxVec3(0));
        physx::PxRigidStatic* CreateRigidStatic(const physx::PxTransform& aTransform, const physx::PxBoxGeometry& aGeometry);


    private:
        void Simulate(float aDeltaTime);
        void CleanupPhysics();
        void InitScene();

    private:

        physx::PxDefaultAllocator            myAllocator;
        physx::PxDefaultErrorCallback    myErrorCallback;

        physx::PxFoundation*                  myFoundation;
        physx::PxPhysics*                          myPhysics;
        physx::PxMaterial*                        myMaterial;

        physx::PxScene*                            myScene;
        physx::PxPvd*                                myPvd;
        physx::PxControllerManager*     myControllerManager;

    };

PhysX Interface

These types, however, are PhysX API which makes it frustrating to use with the rest of the in-house engine's source code. Therefore another layer of interface using our own API to wrap the types is an optimal solution. Here we make use of the singleton pattern, since the developement of our in-house engine is constantly evolving, the applied usage of PhysX is unkown. This makes it favorable to have ease of access and usage as fast as possible.

CPhysicsController is compositioned in the actor which will move

The actor then uses its controller to move and sets its position accordingly

To create the internal type, PxController, a PxControllerManager is created and used by passing a controller description.

The created controller takes a "userData" which we enforce to always be a pointer to the GridObject which has the controller compositioned. In the case of the player, it is an actor which in turn is a modelinstance and a gridobject.

The collision filter for the controller is set by using bitwise operators to make it easily readable.

 

By proper assignment of filterdata for every PhysX object, rays can be cast and hit expected actors.

Since we enforce that all PhysX objects store a pointer to the GridObject it's compositioned to, we can get the hit actor of our raycast.

Here is where we reap the benefits of storing the user data of the object. Once an object is hit, the GridObject can be casted to its subclass and operated on.

 

For us, this proved a powerful technique which enabled us to write gameplay features more fluently within the engine.

 

class CPhysics : public Singleton<CPhysics>
{
public:
    CPhysics() = default;
    ~CPhysics() = default;

    void Init();
    void Update(float aDeltaTime);

    CDynamic* CreateDynamic(const Vector3f& aPosition, const Vector4f& aQuaternion, const float aRadius, const Vector3f aVelocity);
    CPhysicsController* CreateBoxController(const Vector3f& aPosition, const Vector3f& aSizeFromCenter, unsigned int myFilter, unsigned int myCollideAgainstFilter, CGridObject* aUserData);
    CRigidStatic* CreateRigidStatic(const Vector3f& aPosition, const Vector4f& aQuaternion, const Vector3f& aSize);
    CRigidStatic* CreateRigidStatic(const Vector3f& aMin, const Vector3f& aMax);
    RayCastHit CastRay(const Vector3f& aOrigin, const Vector3f& aDirection, float aLength, unsigned int aFilter);


private:
    Physics::CFramework* myFramework;
};

 

        CPhysicsController* physicsController = CPhysics::GetInstance()->CreateBoxController(spawnPosition, size, collisionFilter0, collisionFilter1, this);

 

        myPhysicsController->Move(movement, deltatime);
        myPosition = myPhysicsController->GetPosition();

 

        myControllerManager = PxCreateControllerManager(*myScene);

        physx::PxBoxControllerDesc desc;
        desc.position = { aPosition.x, aPosition.y, aPosition.z };
        desc.material = actorMaterial;

        desc.halfSideExtent = aSizeFromCenter.x;
        desc.halfHeight = aSizeFromCenter.y;
        desc.halfForwardExtent = aSizeFromCenter.z;

        desc.contactOffset = .1f;
        desc.stepOffset = aStepOffset;

        physx::PxController* controller = myControllerManager->createController(desc);

 

        physx::PxRigidDynamic* actor = controller->getActor();
        actor->userData = aUserData;

 

        physx::PxFilterData data;
        data.word0 = aWord0;
        data.word1 = aWord1;
        data.word3 = 1;

        physx::PxShape* shape;
        actor->getShapes(&shape, 1);
        shape->setSimulationFilterData(data);
        shape->setQueryFilterData(data);

 

enum CollisionFilter
{
    ECollisionFilter_None            = 1 << 0,
    ECollisionFilter_Controller    = 1 << 1,
    ECollisionFilter_Static            = 1 << 2,
    ECollisionFilter_Dynamic      = 1 << 3,
    ECollisionFilter_Player          = 1 << 4,
};

 

    physx::PxRaycastBuffer CFramework::CastRay(const Vector3f& aOrigin, const Vector3f& aDirection, const float& aLength, unsigned int aFilter)
    {
        physx::PxVec3 origin(aOrigin.x, aOrigin.y, aOrigin.z);
        physx::PxVec3 direction(aDirection.x, aDirection.y, aDirection.z);

        physx::PxQueryFilterData filterData;
        filterData.data.word0 = aFilter;

        physx::PxRaycastBuffer hit;
        myScene->raycast(origin, direction, aLength, hit, physx::PxHitFlag::eDEFAULT, filterData);
        return hit;
    }

 

    physx::PxRaycastBuffer buffer = myFramework->CastRay(aOrigin, aDirection, aLength, aFilter);

    RayCastHit hit;
    if (buffer.hasBlock)
    {
        hit.myHit = true;
        if (buffer.block.actor)
        {
            hit.myObjectHit = (CGridObject*)(buffer.block.actor->userData);
            hit.myDistance = buffer.block.distance;
            hit.myPosition = Vector3f(buffer.block.position.x, buffer.block.position.y, buffer.block.position.z);
            hit.myNormal   = Vector3f(buffer.block.normal.x, buffer.block.normal.y, buffer.block.normal.z);
        }
    }
    return hit;

 

    RayCastHit hit = CPhysics::GetInstance()->CastRay(position, direction, length, filter);

    if (hit.myHit)
    {
        CMovingPlatform* platform = static_cast<CMovingPlatform*>(hit.myObjectHit);
        if (platform)
        {
            Vector3f platformMovement = platform->GetMovement();
            myPhysicsController->Move(platformMovement, aDeltaTime);
        }
    }