PhysX - handling picked up/carried objects

Started by
4 comments, last by irreversible 8 years, 4 months ago

I'm implementing object carrying. As my game is from a top-down 3rd person perspective, this needs extra care as I cannot cheat in screen space or use some other fakery to achieve the illusion of carrying and colliding an object in the player's hands.

After going through a joint-based and a kinematic test solution, I've managed to tweak a semi-kinematic approach to the point where it almost looks okay. Essentially I'm doing what the guy here suggests: keeping the carried object as a regular non-kinematic dynamic actor and setting its linear (and optionally angular) velocity to match the necessary displacement so the object would end up in front of the player every time the character controller moves and/or turns.

Note that a joint-based solution does not work, because while keeping the carried object relative to the player isn't that hard, having the it face int the same direction as the player requires a call to setGlobalPose(), which bypasses collision detection and causes the carried actor to clip into geometry.

Similarly, turning the carried object into a kinematic body and using setKinematicTarget() doesn't work, because kinematic bodies in PhysX implicitly have infinite mass and push all dynamic actors out of the way. Also, they do not collide with static geometry. By design. Which is basically saying that kinematics weren't meant to be used like this (although I suppose one could use some far too complicated contact point modification voodoo to fake it).

This leaves the third solution, which requires some tweaking to properly align the carried object in front of the character controller (which itself is kinematic), but otherwise has the most potential so far.

(TL;DR - questions be here) Nevertheless, there are three issues with it:

1) collision with static geometry causes the carried actor to jump erratically as it's pressed against a wall. This forces it to wobble badly and clip into geometry and/or slide up until the actor ends up on top of the static blocker (I really can't tell why, but both seem to happen, sometimes during the same run). The behavior I want is far less intrusive: essentially I want the carried object to just stop if blocked and I want to run my own test that will determine if it's too far off the player's orientation vector and needs to be dropped (essentially simulating joint breakage).

2) I do not want the carried object to affect dynamic objects. I've tried setting its mass to zero, but it still pushes everything out of its way. Dynamic objects normally block the CCT (character controller) as if they were fixed geometry, but for some reason a dynamic actor with mass zero and velocity set via setLinearVelocity() still affects them (and is affected by them).

3) the third problem is the most nuanced and may well be borne out by my poor knowledge of the API. Essentially it has to do with when PxCharacterController::move() should be called. Notably, for the sake of consistency, I need to use the actual distance between positions measured before and after PxCharacterController::move() to move the carried object. However, as far as I can tell, PxCharacterController::move() is not executed as part of the simulations step (eg it's not deferred the way setKinematicTarget() calls are), but rather acts like a separate write call to PxScene, which is run independently. That being said, calling PxCharacterController::move() before a simulation step for a given tick will yield a proper offset, but will either potentially collide the CCT with the carried object, thus slowing it down, or if I disable collision for the object for the duration of the move() call, potentially clip the CCT into the carried object in case it is blocked by a solid on the other side. Calling move() after the simulation step makes more sense, but can have the carried object jitter as it tries to move to a projected position (and then snaps back during the next update) in case the CCT is blocked.

Truth be told, I'm not overly concerned about the last point for the time being. I am, however, not really sure what to do about points 1 and 2, which are both game-breaking. I've tried quite a few things and I'm running out of ideas... Any thoughts?

* note: while I think all of the above points are a matter of working out a few details, a fourth option would be to go back to a joint-based solution and use setAngularVelocity() similarly to my current solution to bypass the call to setGlobalPose(). I don't like the joint solution, though, as it's more complicated than the semi-kinematic one.

Advertisement
Does the carried object need to simulate physics?

Unless it is a must for a good reason, I would simply disable physics for any carried object. Once you drop it you can re enable physics.
My current game project Platform RPG

Does the carried object need to simulate physics?

Unless it is a must for a good reason, I would simply disable physics for any carried object. Once you drop it you can re enable physics.

That's the problem with 3rd person camera - I can't just have it go through solids and render it with depth disabled. So yeah, it really needs to respond to physics...

The thing is, I've been experimenting with this for several days now and I can't figure out why it's interpenetrating static solids. The carried object is definitely reporting collision in the filter shader and does push other dynamics out of the way. But I don't understand why it's clipping into static geometry (while also acting like it's colliding, eg wobbling) or why it's not getting completely blocked by other dynamics when I set its mass to zero.

Truth be told, this solution seems ridiculously simple and foolproof as it only takes advantage of regular collision response and relies on no gimmicks at all. Which is why I really want to think that I'm missing something simple somewhere.

Hmpf. This is seriously getting to me.

Here are what I think are all relevant bits of code that should set up collision between all actors. Perhaps someone would not mind skimming through it and maybe point out a mistake in case I've made any.

Filter setup at scene creation:


    PxU32 groupCollisionFlags[32]

    //everything collides with everything
    for(unsigned i = 0; i < 32; i++)
    { groupCollisionFlags[i] = 0xffffffff; }

    //disable with the disabled group - leaving this in for reference
    setGroupPairCollisionFlag(PHYSICS_GROUP_STATIC, PHYSICS_GROUP_DISABLED, false);
    setGroupPairCollisionFlag(PHYSICS_GROUP_PICKUP, PHYSICS_GROUP_DISABLED, false);

    sceneDesc.filterShaderData = groupCollisionFlags;

...


void IPhysXPhysicsEnvironment::setGroupPairCollisionFlag(PxU32 groups1, PxU32 groups2, bool enable)
{
    PX_ASSERT(groups1 < 32 && groups2 < 32);
    if(enable)
        {
        groupCollisionFlags[groups1] |= (1 << groups2);
        groupCollisionFlags[groups2] |= (1 << groups1);
        }
    else
        {
        groupCollisionFlags[groups1] &= ~(1 << groups2);
        groupCollisionFlags[groups2] &= ~(1 << groups1);
        }
}

Actor creation and filter mask setting:


void IPhysXPhysicsEnvironment::CreateRigidActor(...)
{
   PxRigidActor* rigidActor = NULL;

   if(bDynamic)
        {
        rigidActor = physics->createRigidDynamic(PxTransform(PxReal(vPositionInRegion[0]), PxReal(40), PxReal(vPositionInRegion[2])));
        vHalfExt.y = 15;

        PxRigidBodyExt::updateMassAndInertia(*(PxRigidBody*)rigidActor, 10.0f);
        }
    else
    { rigidActor = physics->createRigidStatic(PxTransform(PxReal(vPositionInRegion[0]), PxReal(vPositionInRegion[1]), PxReal(vPositionInRegion[2]))); }

    PxShape* rigidShape = NULL;

    if(!STRCMP(actor.mesh->descMesh->sBasicType, "sphere"))
        {
        rigidShape = rigidActor->createShape(PxCapsuleGeometry(actor.mesh->descMesh->vSize.x * PxReal(0.5), 20), *aMaterial);
        PxTransform relativePose(PxQuat(PxHalfPi, PxVec3(0, 0, 1)));
        rigidShape->setLocalPose(relativePose);
        rigidActor->setGlobalPose(PxTransform(PxVec3(vPositionInRegion[0], 0, vPositionInRegion[2])));
        }
    else
        {
        rigidShape = rigidActor->createShape(PxBoxGeometry(vHalfExt.x, vHalfExt.y, vHalfExt.z), *aMaterial);
        }

    rigidActor->setActorFlags(PxActorFlag::eDISABLE_GRAVITY | PxActorFlag::eVISUALIZATION);

    if(bDynamic)
    { SetCollisionFlagsForAllShapes(*rigidActor, PHYSICS_GROUP_PICKUP); }
    else
    { SetCollisionFlagsForAllShapes(*rigidActor, PHYSICS_GROUP_STATIC); }

    rigidShape->setFlag(PxShapeFlag::eVISUALIZATION, true);
    rigidShape->setFlag(PxShapeFlag::eSCENE_QUERY_SHAPE, true);
    rigidShape->setFlag(PxShapeFlag::eSIMULATION_SHAPE, true);
}



template<typename T>
void IPhysXPhysicsEnvironment::SetCollisionFlagsForAllShapes(IN const T& actor, IN const DWORD flags) const
{
    PxShape* shapes[32];

    for(int32 j = 0; ; j++)
        {
        ZeroMemory(shapes, sizeof(PxShape*) * 32);

        if(actor.getShapes(shapes, 32, j * 32) == 0)
            { goto _doneSetFlags; }

        for(int32 i = 0; i < (int32)32; ++i)
            {
            if(shapes[i] == NULL)
                { goto _doneSetFlags; }

            PxFilterData filterData;
            filterData.word0 = flags;
            shapes[i]->setSimulationFilterData(filterData);
            shapes[i]->setQueryFilterData(filterData);
            }

        }

_doneSetFlags:
    ;
}

Collision filter shader:


PxFilterFlags MySimulationFilterShader(PxFilterObjectAttributes attributes0, PxFilterData filterData0,
                                       PxFilterObjectAttributes attributes1, PxFilterData filterData1,
                                       PxPairFlags& pairFlags, const void* constantBlock, PxU32 constantBlockSize)
{
    //some reference code commented out

    //PxFilterFlags filterFlags = PxDefaultSimulationFilterShader(attributes0,
                                filterData0, attributes1, filterData1, pairFlags, constantBlock, constantBlockSize);

    //pairFlags |= PxPairFlag::eSOLVE_CONTACT;


    pairFlags = PxPairFlag::eSOLVE_CONTACT;
    pairFlags |= PxPairFlag::eCONTACT_DEFAULT;
    pairFlags |= PxPairFlag::eDETECT_DISCRETE_CONTACT;
    pairFlags |= PxPairFlag::eDETECT_CCD_CONTACT;

    return PxFilterFlag::eDEFAULT;
}

Handling the carried actor during a physics sim step:


    PxRigidDynamic dynBody* = GetCarriedObject();
    vecToHoldPosition = GetPosInFrontOfPlayer() - PosCurrent(dynBody);

    if(vecToHoldPosition.LengthSquared() > 0)
        {
        vecToHoldPosition *= timeScale;
        //don't do any rotational adjustment for now
        dynBody->setLinearVelocity(Vec3ToPxVec3(vecToHoldPosition));
        }
    else
        {
        //reset all movement if already in correct place
        dynBody->setLinearVelocity(PxVec3(0, 0, 0));
        dynBody->setAngularVelocity(PxVec3(0, 0, 0));
        dynBody = NULL;
        }

    scene->simulate(dt);
    scene->fetchResults(true);

    if(dynBody)
        {
        dynBody->setLinearVelocity(PxVec3(0, 0, 0));
        dynBody->setAngularVelocity(PxVec3(0, 0, 0));
        }

Follow-up #1. I'm writing this in case it will be useful to someone.

In short, things changed after I started experimenting with gravity and substepping.

My initial test settings were:

scale = ~30 units per box axis (30 pixels/units in my case)

step_delta = 16.6 ms (~60 Hz)

numSubsteps = 4

gravity = -9.81f * (step_delta / (1000.f * numSubsteps))

This did not work. Substepping forced the actor to perform better at collision, but it also caused it to lag behind the player. To be honest, substepping seemed unneccessary in the first place, since I was running the simulation at a fixed 60 Hz interval, which isn't extreme any way you look at it. That being said, substepping helped a ton, as it visibly verified that the actors were actually colliding - the carried object didn't penetrate completely into static geometry. But it did still jostle and wobble as outlined in my previous posts.

My initial idea was to undo the rotational offset by rotating the carried object to an upright position during each simulation step using setAngularVelocity(). This didn't work and on closer inspection it shouldn't. I tried setGlobalPose() on the actor by setting only its rotational component, but this would expectedly cause the actor to clip into geometry. It was a step in the right direction, though, as the actor would no longer go completely through statics. After some more googling I learnt about the existence of frozen rotational axes in PhysX 2.7, which seemed like a really convenient solution. As it happens, however, there is no equivalent to this in PhysX 3.2.2, which, in turn, led me to this. While the rotational fix worked, the D6 joint fixed the actor to a single position in space, disallowing any movement imposed by setLinearVelocity(). As far as I can tell, it would work as intended if the body is kinematic (and you're using setKinematicTarget()), though. Which mine isn't.

All the while gravity was causing all kinds of weird behavior, which I cannot fully explain, mostly because I (think I) don't really have enough experience with the PhysX API to start with (and also because it didn't click for me that it was gravity what was causing these problems in the first place). Firstly, dynamic objects would become unstable and start fluctuating arbitrarily across the scene. I'm guessing this can be attributed to my poor choice of or possibly missing mass/inertia/material parameters, although I did try a variety of values as best I could. Secondly, if I set the spawn point for dynamic objects high enough (500 or more on the abovementioned scale), dynamic actors would no longer end up jittering erratically, but would rather fall to the ground and end up stationary (they would not from lower heights for some reason). However, they would stop at odd angles without settling on a level plane like one would expect them to. Which still baffles me.

So finally I disabled gravity by setting the PxActorFlag::eDISABLE_GRAVITY flag on all dynamic objects.

Which changed a lot. I no longer get any jitter/rotational unstability and my carried object is no longer penetrating any solids. That being said, I still have much more tweaking to do to define whether or how the it can move through the player (this needs addressing, since I have a top down camera, and the player can flip the CCT by moving the mouse across its origin).

While all that makes sense, I don't really know what happened. It's one of those times when "that next build" fixed everything (well, not everything) without me changing all that much (notably, because I'd tried disabling gravity before!). What I did notice, though, is that setting the carried actor's mass to zero causes it to go through static geometry and setting it to something really small (like 0.1) still causes much heavier dynamic actors to move out of the way as if they're made of aerogel. So that's something I still need to figure out.

As far as gravity goes, I personally don't need it for this game, since my game is perceptually 2D. However, I would imagine either setting/unsetting the eDISABLE_GRAVITY flag when an object is picked up/set down, or applying a custom gravity vector for each dynamic actor would solve the problem of not having gravity enabled implicitly.

PS - I'm calling this the follow-up post #1, because I'm fairly sure my problems aren't over. Since this does not seem like a too uncommon issue and I couldn't find any substantial sources of information when searching, I'll try to post updates if and as needed.

And here's the second follow-up.

I did get it working, although I haven't tested it too rigorously yet. Still, I wrote a journal entry outlining what I ended up doing. Hope someone finds it useful.

This topic is closed to new replies.

Advertisement