[BULLET] Various collision problems

Started by
4 comments, last by jezham 8 years, 2 months ago

I'm having a lot of problems with collisions between objects, and I'm kind of lost on what I can do to combat these issues.
I've recorded a video which should make it more clear what I mean:

[video]https:

[/video]
• The player (Kinematic Capsule Controller) pushes dynamic objects away at full force. This is especially problematic, because objects can be pushed out of the world this way. I'd like to know if I can either 1) disable being able to push objects completely, so I can write my own implementation, or 2) decrease the force by which objects are being pushed if possible.
• Dynamic physics objects can push other objects out of the world, as demonstrated in the video. All of the objects are of type btRigidBody (Including the ragdoll body parts). The mass of the box is 80.
• Small objects are sometimes very jittery when on the ground due to gravity (Noticable with the ragdoll). However, without applying gravity on the ground, objects wouldn't slide down slopes, so I'm not sure what to do against the jittering.
• Constraints are too lenient. As you can see in the video, when the player walks into the ragdoll, the ragdoll parts sort of 'stretch'. This is because the ragdoll parts are moving apart even though they shouldn't be able to, and then return to the correct position. All of the ragdoll constraints are conetwist constraints with softness = 1, bias factor = 0.3 and relaxation factor = 1.

My simulation rate is 60 ticks per second. CCD is enabled for all objects.
Some of these problems might be related, but I'm out of ideas at this point.

Advertisement

Many games implement their own character controllers since they are extremely nuanced and particular from game to game. To me it looks like Bullet is solving collisions between the character and the ragdoll in a way that it pushes the ragdoll into the ground (as in position correction is run between the kinematic body and the dynamic bodies), and then after this occurs penetration is attempted to be resolved between the ground and the ragdoll. Unfortunately this process will probably invalidate the ragdoll constraints, which is why they are freaking out trying to resolve themselves. Also ragdoll pieces can get caught between nasty triangle configurations unable to get back out.

I'm not sure why small objects are having a jitter problem if CCD is enabled. Hopefully this is just side effects of what I mentioned above.

To prevent these issues make sure CCD is on between the ragdoll and the static geometry (dynamic vs static). Then the character controller shouldn't ever be able to directly move other dynamic objects, and instead only effect velocity/acceleration. This will let the physics simulation take these velocity changes and run them through CCD to prevent penetration.

Character controllers can take a ton of work to implement (see here), just a fair warning. If you don't want to implement this all yourself maybe you can try modifying the bullet kinematic controller. You should be able to easily turn off collisions between kinematic/dynamic on a global setting. If not bullet should at least let you filter collision yourself to prevent unwanted collisions. Then you can try applying your own custom velocities/forces to dynamic objects to simulate running into them or pushing them away.

..are you using heightfields for the terrain or convex mesh ? If the ragdoll is completely kinematic and sufficient force is applied to it via another kinematic object, then it may cause inter-penetration ( which should not happen if CCD is enabled, but I've seen similar behavior with PhysX ), which will be compounded if the force keeps getting applied. In general this should not happen if the object the kinematic object is being forced into is also kinematic. If penetration depth can be modified, I would try doing that. But I think then tunneling you are experiencing with the ragdoll is related to the fact that the rag-doll simulation is still oscillating and you are introducing more force into the equation throwing it even more out of equilibrium. First try to get the ragdoll simulation to a point where its not as jittery ( not familiar with Bullet here, but you seem to have an handle on this ), if there are spring constant, joint rest value, restitution coefficient that could be fined tune, try those. Get the character controller out of the equation( for checking the ragdoll) by applying external forces by some other means ( may dragging with the mouse or a key stroke ).

I had penetration issues with bullet recently, so I had a peek at their CCD code; from what I could tell, it just embeds a sphere inside each body, and performs CCD using these spheres in an attempt to prevent tunnelling, but doesn't actually do anything to help stop penetration in general :(
I ended up increasing my simulation rate to 600Hz to achieve stability almost as good as PhysX gave me at 60Hz...
Bullet's vanilla Kinematic Character Controller is far from finished and polished compared to the KCCs that ship with commercial engines. I've had to give up on it and roll my own (using the Bullet GJK and manually resolving collisions against a capsule shape) in my current Bullet project.

I keep promising to submit mine to Bullet but the problem is it is so tied up into my own wrapper system around Bullet it has hard to break it out, and it is only okay for my particular domain (Mario-style 3D platformer) and probably not of any use in any other context.

My advice - learn enough to implement your own KCC, based on Bullet's collision and ray test functionality so you can tweak it to suit your particular requirements. Not easy though - I had already implemented my own (very poor) GJK before I felt comfortable handing that over to Bullet for a stable result.

Some pointers on the way mine works - I have a capsule shape representing the character, and also constantly cast a ray down from its base to measure the distance to the floor, only applying fake gravity if the controller is above a certain threshold above the "floor" and snapping it to a fixed distance above the floor if within the threshold. This avoids the need to use any kind of friction to prevent slope sliding. I use the Bullet broadphase then the GJK pair test to get separation vectors against the world geometry for other collisions. There are some additional complications when penetrating into steep slopes that require a bit of vector math to figure out.

Massive code dump ahoy...

#include "Kcc.h"

#include "maths/Vec2.h"
#include "maths/Vec3.h"
#include "maths/Matrix.h"
#include "maths/Quaternion.h"
#include "maths/Ray.h"

#include "physics/Physics.h"
#include "physics/components/Body.h"

#include "debug/DebugRender.h"

namespace
{

float minFloorDistance(float radius, const Vec3 &normal, float margin)
{
    return sphericalDistanceToNormal(radius, normal) + (margin * 2);
}

RayResult findFloor(Physics &physics, float radius, const Vec3 &base)
{
    RayResult r = physics.rayCast(Ray(base, Vec3(0, -1, 0)), 100);

    if(r.valid() && dotVectors(r.normal(), Vec3(0, 1, 0)) >= 0.8f && r.distance() < minFloorDistance(radius, r.normal(), 0.15f))
    {
        return r;
    }

    return RayResult();
}

Vec3 alignToFloor(const Vec3 &velocity, const Vec3 &normal)
{
    return transformNormal(velocity, rotationToQuaternion(Vec3(0, 1, 0), normal));
}

float lockedY(const RayResult &r, float radius, float height)
{
    return r.worldPoint().y + minFloorDistance(radius, r.normal(), 0.05f) + ((height / 2) - radius);
}

bool lockToFloor(Physics &physics, Vec3 &pos, float radius, float height)
{
    Vec3 base(pos.x, pos.y - ((height / 2) - radius), pos.z);
    RayResult r = findFloor(physics, radius, base);

    if(r.valid())
    {
        pos.y = lockedY(r, radius, height);
        return true;
    }

    return false;
}

Vec3 slopeCorrection(const Vec3 &separation)
{
    float d = dotVectors(normalizeVector(separation), Vec3(0, 1, 0));
    if(d >= 0 && d < 0.8f)
    {
        return getDownVector(separation) * vectorLength(separation);
    }

    return Vec3(0, 0, 0);
}

Vec3 separatingVector(Physics &physics, const Shape &shape, const Vec3 &at)
{
    Vec3 result = at;
    Matrix to = translationMatrix(result);

    BroadphaseResult br = physics.broadphaseAabb(shape.aabb(to).expanded(2));

    if(br.valid())
    {
        int it = 0;
        bool loop = true;

        while(it < 5 && loop)
        {
            loop = false;

            for(auto &b: br.bodies)
            {
                ConvexResult r = physics.convexIntersection(shape, to, b->shape(), b->transform());

                if(r.valid())
                {
                    result += r.separatingVector();
                    to = translationMatrix(result);

                    loop = true;
                }
            }

            ++it;
        }
    }

    return result - at;
}

}

Kcc::Kcc(float radius, float height, const Vec3 &position) : pos(position), prev(position), shape(radius, height)
{
}

void Kcc::setPosition(const Vec3 &value)
{
    pos = value;
    prev = pos;
}

void Kcc::move(Physics &physics, MoveFlags flags, const Vec3 &step, float epsilon)
{
    Vec3 tp = pos;
    prev = pos;

    bool tg = gr;

    implementMove(tp, tg, physics, flags, step);

    if(vectorLength(Vec2(tp.x, tp.z) - Vec2(pos.x, pos.z)) >= epsilon)
    {
        pos.x = tp.x;
        pos.z = tp.z;
    }

    pos.y = tp.y;
    gr = tg;
}

bool Kcc::canMove(Physics &physics, MoveFlags flags, const Vec3 &step, float epsilon) const
{
    Vec3 tp = pos;
    bool tg = gr;

    implementMove(tp, tg, physics, flags, step);

    return vectorLength(Vec2(tp.x, tp.z) - Vec2(pos.x, pos.z)) >= epsilon;
}

float Kcc::distanceToFloor(Physics &physics) const
{
    RayResult r = physics.rayCast(Ray(pos + Vec3(0, -shape.height() / 2, 0), Vec3(0, -1, 0)), 100);
    if(r.valid())
    {
        return r.distance() + (shape.radius() - minFloorDistance(shape.radius(), r.normal(), 0.01f));
    }

    return -1.0f;
}

bool Kcc::booleanIntersection(Physics &physics, const Matrix &transform, const BodyFilter &filter) const
{
    return physics.booleanIntersection(shape, transform, filter);
}

void Kcc::implementMove(Vec3 &position, bool &grounded, Physics &physics, MoveFlags flags, const Vec3 &step) const
{
    bool flying = step.y > 0;
    float hh = shape.height() / 2;

    RayResult floor = findFloor(physics, shape.radius(), position + step + Vec3(0, -(hh - shape.radius()), 0));

    Vec3 dv(step.x, 0, step.z);
    Vec3 mv = floor.valid() && !flying ? alignToFloor(dv, floor.normal()) : dv;

    mv.y = step.y;

    Vec3 sep = separatingVector(physics, shape, position + mv);

    if(!flying)
    {
        mv += slopeCorrection(sep);
    }

    position += mv + sep;

    grounded = false;
    if(step.y <= 0)
    {
        grounded = lockToFloor(physics, position, shape.radius(), shape.height());
    }
}

btKinematicCharacterController seemed counter-intuitive for me, but worked solid after experimenting with call order.

Calling preStep() after playerStep() eliminated penetration, though I seem to call preStep twice per physics step. (old project)

This topic is closed to new replies.

Advertisement