[C++] Character Controller (convexSweepTest) problem

Ripiz
Posts: 47
Joined: Mon Aug 16, 2010 10:43 am

[C++] Character Controller (convexSweepTest) problem

Post by Ripiz »

I used to use default Kinematic Character Controller, but it was falling through heightmap, I assumed it's logic problem, so decided to make own controller. Here's my logic:

At first, I have only vector, which is (direction * speed):
Image

Then I move vector a bit up (imaginary slope). Basically character will try to climb slope, if there's slope is the way which is higher he'll just collide (y position += max slope * vector length), at this point I use convexSweepTest() to move character:
Image

And finally, I move character down, I move down twice by same slope (first it compensating upslope I went, and 2nd is downslope, same slope but in different direction) and 3rd force is gravity, again, using convexSweepTest to move it:
Image

I imagine it should happen about this way:
Image

However, sometimes I fall through things (only straight downwards), I am quite unsure what's the problem. I ran over this post: http://bulletphysics.org/Bullet/phpBB3/ ... f=9&t=6336 and though it might be similar problem, so I lower hitFraction by a little. It fixed a lot of fall through, but it still happens. I though maybe someone knows what is wrong, here's my code:

Code: Select all

struct characterController : public btActionInterface{
	struct ClosestCallback : public btCollisionWorld::ClosestConvexResultCallback{
		ClosestCallback() : ClosestConvexResultCallback(btVector3(0.0, 0.0, 0.0), btVector3(0.0, 0.0, 0.0)){
		}
		virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace){
			return ClosestConvexResultCallback::addSingleResult(convexResult, normalInWorldSpace);
		}
	};

	btPairCachingGhostObject* ghostObject;
	btConvexShape* characterCollisionStanding;
	btConvexShape* characterCollisionCrouching;
	btVector3 velocityCharacter, velocityGravity;

	bool onGround;
	float maxSlope;

	characterController(btVector3 &position){
		onGround = false;
		maxSlope = sin(PI / 5.0f);

		characterCollisionStanding = new btCapsuleShape(0.3f, 1.2f);
		characterCollisionCrouching = new btCapsuleShape(0.3f, 0.6f);

		ghostObject = new btPairCachingGhostObject();

		btTransform transform;
		transform.setIdentity();
		transform.setOrigin(position);
		ghostObject->setWorldTransform(transform);
		ghostObject->setCollisionShape(characterCollisionStanding);
		physics->addAction(this);
		physics->addCollisionObject(ghostObject, btBroadphaseProxy::SensorTrigger, btBroadphaseProxy::AllFilter & ~btBroadphaseProxy::SensorTrigger);

		velocityCharacter = velocityGravity = btVector3(0, 0, 0);
	}

	virtual ~characterController(){
		physics->removeAction(this);
		physics->removeCollisionObject(ghostObject);
		SAFE_DELETE(ghostObject);
		SAFE_DELETE(characterCollisionStanding);
		SAFE_DELETE(characterCollisionCrouching);
	}

	void SetStanding(){
		if(ghostObject->getCollisionShape() == characterCollisionCrouching){
			btTransform transform = ghostObject->getWorldTransform();
			transform.setOrigin(transform.getOrigin() + btVector3(0, 0.3f, 0));

			ClosestCallback callback;
			ghostObject->convexSweepTest(characterCollisionStanding, transform, transform, callback);

			if(!callback.hasHit()){
				ghostObject->setWorldTransform(transform);
				ghostObject->setCollisionShape(characterCollisionStanding);
			}
		}
	}

	void SetCrouching(){
		if(ghostObject->getCollisionShape() == characterCollisionStanding){
			btTransform transform = ghostObject->getWorldTransform();
			transform.setOrigin(transform.getOrigin() - btVector3(0, 0.3f, 0));

			ghostObject->setWorldTransform(transform);
			ghostObject->setCollisionShape(characterCollisionCrouching);
		}
	}

	void SetVelocity(btVector3 &vel){
		velocityCharacter = vel;
	}

	virtual void updateAction(btCollisionWorld* world, btScalar deltaTimeStep){
		stepLinear(deltaTimeStep);
		stepDown(deltaTimeStep);
	}

	void stepLinear(btScalar deltaTimeStep){
		btVector3 verticalMovement = btVector3(0, velocityCharacter.length() * maxSlope, 0) * deltaTimeStep;
		btVector3 attemptMovement = velocityCharacter * deltaTimeStep + verticalMovement;
		btTransform transformOld = ghostObject->getWorldTransform();
		btTransform transformNew = transformOld;
		transformNew.setOrigin(transformNew.getOrigin() + attemptMovement);

		ClosestCallback callback;
		ghostObject->convexSweepTest((btConvexShape*)ghostObject->getCollisionShape(), transformOld, transformNew, callback);

		btVector3 orig = ghostObject->getWorldTransform().getOrigin();
		btVector3 movement = attemptMovement * (callback.m_closestHitFraction - 0.01f);
		ghostObject->getWorldTransform().setOrigin(orig + movement);

		onGround = false;
	}

	void stepDown(btScalar deltaTimeStep){
		velocityGravity += physics->getGravity() * deltaTimeStep  * deltaTimeStep * 10.0f;
		btVector3 verticalMovement = btVector3(0, velocityCharacter.length() * -maxSlope * 2, 0) * deltaTimeStep;
		btVector3 attemptMovement = btVector3(0, -maxSlope, 0) * deltaTimeStep + velocityGravity + verticalMovement;
		btTransform transformOld = ghostObject->getWorldTransform();
		btTransform transformNew = transformOld;
		transformNew.setOrigin(transformNew.getOrigin() + attemptMovement);

		ClosestCallback callback;
		ghostObject->convexSweepTest((btConvexShape*)ghostObject->getCollisionShape(), transformOld, transformNew, callback);

		btVector3 orig = ghostObject->getWorldTransform().getOrigin();
		btVector3 movement = attemptMovement * (callback.m_closestHitFraction - 0.01f);
		ghostObject->getWorldTransform().setOrigin(orig + movement);

		if(callback.hasHit()){
			if(attemptMovement.y() < 0.0f)
				onGround = true;
			velocityGravity = btVector3(0, 0, 0);
		}
	}

	void Jump(float power){
		if(velocityGravity.y() <= 0 && onGround){
			velocityGravity += btVector3(0, power, 0);
			onGround = false;
		}
	}

	void SetPlayerPosition(btVector3 &pos){
		if(ghostObject->getCollisionShape() == characterCollisionStanding){
			ghostObject->getWorldTransform().setOrigin(pos + btVector3(0, 0.6f, 0));
		}
		else if(ghostObject->getCollisionShape() == characterCollisionCrouching){
			ghostObject->getWorldTransform().setOrigin(pos + btVector3(0, 0.3f, 0));
		}
	}

	virtual void debugDraw(btIDebugDraw* debugDrawer){
	}
};
P.S. Vectors in images aren't scaled properly, just trying to give general idea.

Edit:
I seem to be able to fall through heightmaps, when I move at high speed along edge where heightmaps connect.
If I stand on flat surface of trimesh and jump (jump power 1.5f), falling causes me to go half through it, just fall through it, and in rare case it actually collides.
User avatar
BrightBit
Posts: 6
Joined: Fri Dec 31, 2010 8:51 pm

Disable GhostObject SweepTest

Post by BrightBit »

My character controller (based on btKinematicCharacterController) also fell through the terrain. I solved it by adding:

Code: Select all

m_useGhostObjectSweepTest = false;
...to its constructor.
Ripiz
Posts: 47
Joined: Mon Aug 16, 2010 10:43 am

Re: [C++] Character Controller (convexSweepTest) problem

Post by Ripiz »

It caused some problems with btKinematicCharacterController, but seems mine controller is fixed now. Thank you.