Hello,
I've made some updates to MOC that I wanted to share with the community. I have added both smooth stepping and falling, which also makes MOC more easy to use with 3rd person camera systems where the character is being past into updateY() instead of just the camera. Now stairs and other objects can be climbed up to a specified height, and the scene node (camera or character) will smoothly step up (if you want it even smoother, it is easy to tweak the value to your own desire). When falling, you will now fall smoothly from a high distance rather than of instantaneously warping to the ground.
Nauk, you're also more than welcome to modify it and include it in your next release as you see fit. I have only tested it out with objects, and not either of the terrain systems, but it should work fine with them as well.
If you find any bugs, fixes, or optimizations, please post them for the community. If anyone is up to also implement sliding, you'll have yourself a nice character controller! Please share your sliding implementation as well!
Here is the changed code:
Code from CollisionTools.cpp:
Updated the constructor to add some member initialization.
Code: Select all
CollisionTools::CollisionTools(Ogre::SceneManager *sceneMgr)
{
mSceneMgr = sceneMgr;
mRaySceneQuery = mSceneMgr->createRayQuery(Ogre::Ray());
if (NULL == mRaySceneQuery)
{
// LOG_ERROR << "Failed to create Ogre::RaySceneQuery instance" << ENDLOG;
return;
}
mRaySceneQuery->setSortByDistance(true);
mTSMRaySceneQuery = mSceneMgr->createRayQuery(Ogre::Ray());
_heightAdjust = 0.0f;
_origY = 0.0f;
_newY = 0.0f;
}
Updated calculateY().
Code: Select all
/*
* This function can be called to calculate the Y coordinate of a scene node
* so it will follow ground, terrain, or other objects. This function now also
* supports both smooth stepping and falling.
*
* initial
* Is this the first call to set the initial height?
*
* steppingSize
* The location we should cast our ray down from. This is useful when we want
* to be able to step up on objects up to a certain height. This also adds
* realism so we don't just step up onto anything that is below our scene node,
* as realistically, we could step up on a 50 meter building.
*/
void CollisionTools::calculateY(Ogre::SceneNode *n, const bool initial, const bool doTerrainCheck,
const bool doGridCheck, const float gridWidth,
const float steppingSize, const Ogre::uint32 queryMask)
{
// Get the current position of the scene node:
Ogre::Vector3 pos = n->getPosition();
// Save the y position to a member variable as we may need to use it later:
_origY = pos.y;
float x = pos.x;
float z = pos.z;
float y = pos.y + steppingSize - _heightAdjust; // Use the actual height of the object and the stepping size.
Ogre::Vector3 myResult(0,0,0);
Ogre::MovableObject *myObject=NULL;
float distToColl = 0.0f;
float terrY = 0, colY = 0, colY2 = 0;
if( raycastFromPoint(Ogre::Vector3(x,y,z),Ogre::Vector3::NEGATIVE_UNIT_Y,myResult,myObject, distToColl, queryMask)){
if (myObject != NULL) {
colY = myResult.y;
} else {
colY = -99999;
}
}
//if doGridCheck is on, repeat not to fall through small holes for example when crossing a hangbridge
if (doGridCheck) {
if( raycastFromPoint(Ogre::Vector3(x,y,z)+(n->getOrientation()*Ogre::Vector3(0,0,gridWidth)),Ogre::Vector3::NEGATIVE_UNIT_Y,myResult, myObject, distToColl, queryMask)){
if (myObject != NULL) {
colY = myResult.y;
} else {
colY = -99999;
}
}
if (colY<colY2) colY = colY2;
}
// set the parameter to false if you are not using ETM or TSM
if (doTerrainCheck) {
#ifdef ETM_TERRAIN
// ETM height value
terrY = mTerrainInfo->getHeightAt(x,z);
#else
// TSM height value
terrY = getTSMHeightAt(x,z);
#endif
if(terrY < colY ) {
_newY = colY+_heightAdjust;
} else {
_newY = terrY+_heightAdjust;
}
} else {
if (!doTerrainCheck && colY == -99999)
colY = y;
_newY = colY+_heightAdjust;
}
// If this is the first time the function is called to set an initial y position,
// we aren't stepping or falling, so rather than animating toward the new position
// over time, we just set it now:
if (initial) {
n->setPosition(x, _newY, z);
}
}
Added updateY().
Code: Select all
/*
* This function should be called to smoothly update the Y coordinate of a
* scene node so it follows the ground or other objects. This function
* supports both smooth stepping and falling.
*/
void CollisionTools::updateY(Ogre::SceneNode *n)
{
// Get the position of the scene node:
float currY = n->getPosition().y;
// If we don't need to update, just return:
if (_newY == currY)
return;
// Determine the distance as the difference between the new height
// and the original height:
float distY = _newY - _origY;
// If the distance is greater than 2 meters, fall at a rate of 4%,
// otherwise, fall or step at a rate of 20%, both giving the appearance
// of a smooth transition:
float newY;
if (abs(distY) > 2)
newY = distY * 0.04;
else
newY = distY * 0.2;
// If the current update will put us higher or lower than the amount we are
// supposed to go, just use the amount we're supposed to go instead. We do
// this for precision:
if ( ( (newY >= 0) && ((currY + newY) > _newY) ) || ( (newY < 0) && ((currY + newY) < _newY) ) )
{
newY = _newY;
n->translate( 0, newY - currY, 0 );
}
else
{
// Otherwise, move from the current position by the new Y amount:
n->translate( 0, newY, 0 );
}
}
Sample code from a camera controller using the new features:
Code: Select all
//--------------------------------------------------------------------------------------
// Function used to test for collisions between the camera and objects in the scene.
//--------------------------------------------------------------------------------------
void Camera1P::collisionTestWithMOC(Real timeSinceLastFrame)
{
// Continue moving toward a new Y position until it is reached:
mCollisionTools->updateY(mMoveCamNode);
// If the camera is moving via input:
if (mDirection != Vector3::ZERO)
{
// Save the last position
Vector3 oldPos = mMoveCamNode->getPosition();
// Commit move
moveCamera(timeSinceLastFrame);
// Calculate the new Y position. Check terrain and all objects flagged with ENTITY_MASK.
// Multiple masks are possible like e.g. ENTITY_MASK|MY_MASK|ETC_MASK.
// doGridCheck casts a 2nd ray. gridWidth=2.0f ogre units away from the exact camera position to
// avoid falling through small holes or gaps in hangbridges for example.
mCollisionTools->calculateY(mMoveCamNode, false, false, true, 0.1f, 1.0f, CollisionTools::WALKABLE_OBJECTS);
// Start moving toward the new Y position immediately after finishing calculations:
mCollisionTools->updateY(mMoveCamNode);
// Check if we are colliding with anything. We pass in the camera's previous position,
// the camera's new position that will occur if there is no collision, and a collision radius
// of 1.0 units, which means that we will leave 1.0 units between the player and a collision.
// The next parameter is the height we want the ray to originate from, and the last parameter
// is the query mask which we have assigned all objects we want to check against for collisions.
// The query mask is used for efficiency so only objects we want tested are tested.
// If we collide, we undo the move:
if (mCollisionTools->collidesWithEntity(oldPos, mMoveCamNode->getPosition(), 1.0f,
-((mMoveCamNode->getPosition()).y * .8), CollisionTools::STATIONARY_OBJECTS))
mMoveCamNode->setPosition(oldPos);
}
}
Enjoy!
-- Corn