custom force callback for basic kinetic control

Arcanor

14-04-2007 17:44:42

I've been trying to implement an RPG-style character that requires immediate control, in a "non-physical" way. i.e. no waiting for acceleration, stop means stop, etc. If you want to have your character Jump, you can apply an upward impulse in your code. This force callback sets velocity in the X and Z axes only, while leaving the Y axis for gravity to deal with (or your own forces, like Jumping, etc.).

Thanks to helpful comments from many people here on this forum (and a lot of trial and error), here is my code so far. I hope it can be useful to someone wanting to implement such a character.

void ArcWorldMgr::CustomForceCallback(OgreNewt::Body* body)
{
// start with 0 force and torque:
Ogre::Vector3 netForce(0, 0, 0);
Ogre::Vector3 netTorque(0, 0, 0);

// get the associated game object:
ArcObject* obj = (ArcObject*) body->getUserData();

// first, check if we're on the ground:
bool bOnGround = obj->isOnGround();

// gather some other data about our body:
Ogre::Real mass;
Ogre::Vector3 inertia;
body->getMassMatrix(mass, inertia);

Ogre::Vector3 position;
Ogre::Quaternion orientation;
body->getPositionOrientation(position, orientation);

Ogre::Vector3 velocity = body->getVelocity();

// if we're on the ground, and being controlled, apply the proper velocity:
if (bOnGround && obj->isControllable())
{
// first determine our heading:
Ogre::Vector3 vHeading = Ogre::Vector3(obj->getGroundNormal().crossProduct(orientation * Ogre::Vector3::UNIT_X)).normalisedCopy();

// now detect the slope of the ground beneath us:
Ogre::Real groundSlope = 90.0f - obj->getGroundNormal().getRotationTo(obj->getGroundNormal() * Ogre::Vector3(1, 0, 1)).getRoll().valueDegrees();
if (groundSlope == 90.0f)
{
// correct value if the normal is pointing straight up (this invalidates previous calculation)
groundSlope = 0.0f;
}

// now detect the ground slope ahead of us, with respect to our heading:
Ogre::Real mySlope = Ogre::Vector3::NEGATIVE_UNIT_Z.getRotationTo(vHeading).getPitch().valueDegrees();

if (obj->isWalking())
{
// if we're facing down a significant slope, we can walk horizontally, i.e. "step off" the slope.
if (mySlope < -60.0f)
{
vHeading.y = 0;
vHeading = vHeading.normalisedCopy();
}
// if we're facing up a slope, we can only climb slopes up to 60 degrees.
if (mySlope < 60.0f)
{
body->setVelocity(vHeading * obj->getWalkSpeed() * Ogre::Vector3(1, 0, 1) + Ogre::Vector3(0, velocity.y, 0));
}
else
{
// falling:
netForce += obj->getGroundNormal().normalisedCopy() * 50.0f;
}
}
else if (obj->isWalkingBackwards())
{
// if we're facing up a significant slope, we can walk backwards horizontally, i.e. "step off" the slope.
if (mySlope > 60.0f)
{
vHeading.y = 0;
vHeading = vHeading.normalisedCopy();
}
// if we're facing down a slope, we can only safely climb down slopes up to 60 degrees.
if (mySlope > -60.0f)
{
body->setVelocity(vHeading * obj->getWalkSpeed() * Ogre::Vector3(1, 0, 1) * -1.0f + Ogre::Vector3(0, velocity.y, 0));
}
else
{
// falling:
netForce += obj->getGroundNormal().normalisedCopy() * 50.0f;
}
}
else
{
body->setVelocity(velocity * Ogre::Vector3::UNIT_Y);
//body->setVelocity(Ogre::Vector3::ZERO);
}
}

// if we're on the ground, and being controlled, apply the proper rotation speed:
if (bOnGround && obj->isControllable())
{
if (obj->isTurningLeft())
{
body->setOmega(Ogre::Vector3::UNIT_Y * obj->getTurnRate());
}
else if (obj->isTurningRight())
{
body->setOmega(Ogre::Vector3::NEGATIVE_UNIT_Y * obj->getTurnRate());
}
else
{
body->setOmega(Ogre::Vector3::ZERO);
}
}

// add gravity force, if it's turned on, and if we're not on the ground already:
if (!bOnGround && mbGravityOn)
{
// gravity force is 9.8 m/s in negative Y direction:
netForce += Ogre::Vector3(0, -9.8f, 0) * mass;
}

// TODO: place limits on total torque/force?

// apply our force to the body:
body->addForce(netForce);
body->addTorque(netTorque);
}


Of course, please let me know of any comments you have, including suggestions for improvements I could make, or errors in my logic.

Thanks.