[SOLVED] How can I use Terrains as collision objects?

Latin1

15-07-2011 03:42:45

Hi everyone,

I've recently finished the second OgreBullet tutorial, in which boxes fall and collide with the ground, a flat surface. It does this with two objects: a flat entity based on an Ogre::Plane, and a flat OgreBulletDynamics::RigidBody object:

OgreBulletCollisions::CollisionShape *Shape;
Shape = new OgreBulletCollisions::StaticPlaneCollisionShape(Ogre::Vector3(0,1,0), 0); // (normal vector, distance)
OgreBulletDynamics::RigidBody *defaultPlaneBody = new OgreBulletDynamics::RigidBody(
"BasePlane",
mWorld); // OgreBulletDynamics::DynamicsWorld object
defaultPlaneBody->setStaticShape(Shape, 0.1, 0.8); // (shape, restitution, friction)

How can I replace the plane with heightmap-based Ogre::Terrain object? Obviously, it's much more complicated than using a flat surface, as I would need to fetch the shape of the Terrain and give it to OgreBullet.

There are already a few posts about this, but most of them are somewhat old and involve the Terrain Scene Manager, which will apparently be deprecated in Ogre 1.8.

I've noticed the OgreBulletCollisionsTerrainShape.h file and the HeightmapCollisionShape class, but I haven't found any practical information on them and I'm not sure how to create a RigidBody (or whatever object type is appropriate) from an Ogre::Terrain object.

There aren't that many threads about using Terrains with OgreBullet (or just Bullet), so I feel like people have gotten this figured out; maybe there's a tutorial or a guide I haven't found yet?

Thank you, I hope someone can point me in the right direction.

captaincrunch80

16-07-2011 00:37:18

OgreBullet can not do that with newer versions of Bullet OgreBullet uses the Legacy Constructor of btHeightfieldTerrainShape and somehow messes up the RigidBody creation for the terrain - Result: It does not collide afterwards (only for complete flat terrain or some single vertices, don't ask me why). It is just messed up.
You have to convert the mesh and do it the bullet way!

This snippet will work and collide exact for any Terrain generated by Ogre (But you have no debug wires this way):
-> For all other objects feel free to use BulletOgre - this body exists in the same physics space.



#include <OGRE/Terrain/OgreTerrain.h>
#include "OgreBullet/Collisions/Shapes/OgreBulletCollisionsTerrainShape.h"
#include "string.h" // for the memcpy
.....

/* Prerequisites
// You have set up the OgreBullet OgreBulletDynamics::DynamicsWorld -> and you have a pointer to it (here mp_Physics is used)
// You have created a Ogre::Terrain Tutorial 3 style or with the new Terrain Manager -> and you have a pointer to it (here pTerrain is used)
*/
int terrainPageSize = pTerrain->getSize(); // Number of vertices along x/z axe

// >>> We need to mirror the ogre-height-data along the z axis first!
// This is related to how Ogre and Bullet differ in heighmap storing
float *pTerrainHeightData = pTerrain->getHeightData();
float pTerrainHeightDataConvert[terrainPageSize * terrainPageSize];
for(int i = 0; i < terrainPageSize; ++i)
{
memcpy(pTerrainHeightDataConvert + terrainPageSize * i,
pTerrainHeightData + terrainPageSize * (terrainPageSize - i - 1),
sizeof(float)*(terrainPageSize));
}
// <<< End of conversion

btHeightfieldTerrainShape* pHeightShape
= new btHeightfieldTerrainShape(terrainPageSize,
terrainPageSize,
pTerrainHeightDataConvert,
1, /* Terrains getHeightData() is already scaled perfectly */
pTerrain->getMinHeight(),
pTerrain->getMaxHeight(),
1, /* upVector is Y positive in ogre-, bullet- and our world */
PHY_FLOAT,
true);

// Scale the mesh along x/z
float unitsBetweenVertices = pTerrain->getWorldSize() / (terrainPageSize - 1);
btVector3 scaling(unitsBetweenVertices, 1, unitsBetweenVertices);
pHeightShape->setLocalScaling(scaling);

// Ogre uses DiamonSubdivision for Terrain-mesh, so bullet should use it too
pHeightShape->setUseDiamondSubdivision(true);

// Now we create a btRigidBody
btRigidBody *pBody = new btRigidBody(0.0 /* mass 0.0 means static */,
new btDefaultMotionState(),
pHeightShape);

//
Ogre::Vector3 terrainPosition = pTerrain->getPosition();
pBody->getWorldTransform().setOrigin(btVector3(terrainPosition.x,
terrainPosition.y
+ (pTerrain->getMaxHeight() - pTerrain->getMinHeight()) / 2, // Bullet's position differs from Ogre's. Ogre's y is at the bottom, bullet needs the middle if the height to be positioned right
terrainPosition.z));

pBody->getWorldTransform().setRotation(btQuaternion(Ogre::Quaternion::IDENTITY.x,
Ogre::Quaternion::IDENTITY.y,
Ogre::Quaternion::IDENTITY.z,
Ogre::Quaternion::IDENTITY.w));

mp_Physics->getBulletDynamicsWorld()->addRigidBody(pBody);

// Advanced Body configuration ->
// You can play with these or just leave the defaults:
// pBody->setFriction(1.0);
// pBody->setRestitution(0.0);
// pBody->setHitFraction(0.0);
// pBody->setDamping(0.2, 0.2);



You have no debug wire for this, but you can shoot barrels or something to test the collision.

There is one more Problem you will come across:
Sometimes small and/or fast impact objects might fall trough the mesh. In special when the mesh is streched hard (extreme Pitch, or large worldsize)

In this case you have to consider two things:
* Your physics world scale (Absolute important for realistic effects!!! Or your bullet will think your little stones are worldkiller-meteor size or vice-versa -> what will result in extreme unrealistic motion speed)
-> READ!!! http://bulletphysics.org/mediawiki-1.5.8/index.php/Scaling_The_World

* CCD (Continuous collision detection)
- Sometimes when objects are small and/or fast they will tunnel trough other objects.
This is because Bullet calculates (in this special case) position before and after passing the object, but not while touching it....
You see what happens. Kind of Tunnel-effect ;)

Solution 1: When you scale your world (size, weight, gravity, speed and so on) large enough while keeping the Terrain::worldSize low(do not stretch the terrain vertices to much), this will most likely never happen.

Solution 2: Use CCD - Just enable it on a fast/small body. (Like your test shooting ball)

// Setting CcdMotionThreshold will deactivate it under a specific speed (what saves a lot of CPU cycles - CCD is costy)
ogreBulletRigidBody->getBulletRigidBody()->setCcdMotionThreshold( /* CCD will be activated over this speed for that body */);
// And on the same Body define a Fallback Sphere for CCD that will be used as collision shape while Speed is over threshold.
ogreBulletRigidBody->getBulletRigidBody()->setCcdSweptSphereRadius( /* Make sure it fits the entity size/halfsize (depending on the shape you use) * scale * 0.9 */ );


At high speeds or micro objects a sphere collision is not physical correct but better than tunneling. Experiment with the threshold to save CPU power - 20 colliding CDD objects at the same time can crash the program.

Least but not last: If you come across non colliding bodies check if the collision Filters are set right
http://bulletphysics.org/mediawiki-1.5.8/index.php/Collision_Filtering

-> A higher world scale is better than using CCD! It costs less CPU time.


Ok that's about it, have fun!!! And tell me if it worked. I am interested if this solution helps you.

Best Regards,

captaincrunch80

Latin1

16-07-2011 20:03:41

Seems complicated but I guess I can try to-- Oh my God it worked.

Everything works perfectly! The objects hit the surface and bounce and slide, with remarkable precision.

I just needed to change the last line:
mp_Physics->getDynamicWorld()->getBulletDynamicsWorld()->addRigidBody(pBody);
to this:
mp_Physics->getBulletDynamicsWorld()->addRigidBody(pBody);
because the compiler tells me getDynamicWorld() doesn't exist.

Thank you so much, you've shown me how to do exactly what I've been trying to accomplish for weeks! I don't have any problems with non-colliding objects, as I'm using the default Ogre terrain from the terrain.png heightmap.

I just have two questions though, simply to better understand certain parts of the code:

  1. In the loop, how exactly does the memcpy() function transfer the information? I know what memcopy() does, but in this instance it's hard to understand how it does it. I get the impression that it puts the values from mTerrainHeightData in mTerrainHeightDataConvert one segment of vertices at a time, but I can't figure out what's being copied from mTerrainHeightData every time the loop is executed:

    mTerrainHeightData + terrainPageSize * (terrainPageSize - i - 1)
    Is the [- i - 1] there to remove a segment (- i), so the loop can move on to the next one, then omit the last vertex of the current segment (- 1) to get its correct length in vertices?

    [/*:m]
  2. Concerning Solution 1 (scaling the world to a large size but keeping the terrain's worldSize low), what exactly is the "world" in this case? Is it a PagedWorld object? (I admit I haven't yet learned much about this class.)[/*:m][/list:u]

    Once again, thank you, I really wasn't expecting so much help.

captaincrunch80

17-07-2011 00:41:59

Hi Latin1,

I am glad my advice helped you! :)

You are right about the mp_Physics - I have the OgreBullet Physicsworld as member in another class and the first call was the get-function. So it was a copy and past error and I corrected it in the code above. Thx for the feedback!

About your two questions:

1)----------------------------------------------
Lets use a Simplify-Heuristic to explain that and set terrainPageSize to 5 (it must be 2^n +1, so 5 is valid)
That means the Terrain will have 5 rows and columns of vertices. The heighmap will be 5x5 pixels.
Ogre and Bullet store high fields different. Ogre points to the first row, but bullet thinks the first row is the last.

Ogre Data in memory
* * * * * (Last row)
* * * * *
* * * * *
* * * * *
* * * * * (First Row)

Bullet Data as it should be delivered
* * * * * (First row)
* * * * *
* * * * *
* * * * *
* * * * * (Last Row)

So if we do not convert and you have a Mountain in the positive z direction, the collision shape will be in the negative z direction!
(By the way: Reviewing this code now, there was a memory leak. Bullet copies and stores the data, so no "new" is needed or you must free pTerrainHeightDataConvert later)

Ok now lets do it easy and fill in what we already know terrainPageSize =5

// Get the points from the Ogre::Terrain
float *pTerrainHeightData = pTerrain->getHeightData();
// Make yourself a temporary container (here was the leak, so I changed it)
float pTerrainHeightDataConvert[5 * 5];

// lets assume fictive memory addresses - we will use pseudo code to explain
// pTerrainHeightData is on Address(000) to Address(024) and pTerrainHeightDataConvert should be on Address(100) to Address(124)
// To keep things easy we go to another world where a float fits in one byte memory, so sizeof(float) is now 1 (but not in real life!)
for(int i = 0; i < 5; ++i) // We want to move 5 rows but they are numbered 0,1,2,3,4
{
memcpy(Address(100) + 5 * i, // Target Address
Address(000) + 5 * (5 - i - 1), // Source Address
5); // How many data units will be copied
}


Ok now unroll the for-loop, solve the expressions and see what happens:

// PSEUDOCODE!
i=0
memcpy(Address(100),
Address(020), //The Source data is stored in 0,1,2,...24 -> Without the i-1 we would have copied undefined 5 floats from Address(025)
5);
i=1
memcpy(Address(105),
Address(015),
5);
i=2
memcpy(Address(110),
Address(010),
5);
i=3
memcpy(Address(115),
Address(005),
5);
i=4
memcpy(Address(120),
Address(000),
5);


Done! We have put the last row from the source in the first row of the target and so on.


2)----------------------------------------------
Jeah I see. My sentence was ambiguous.

By scaling the world I mean something like for example scale 10: 1m = 10 Units, Gravity = 100m/s^2, 1kg = 10kg, 1m/s = 10m/s and so on.
By Terrain-Worldsize I mean Ogre::Terrain::worldSize (that is the x/z size in Units, usually about 2000-8000 that the pageSize(heighmap rows/coloums) will be stretched to)

In other Words: Keep your non-static RigidBodys and physics big while not stretching the heighmapdata over too many kilometers.
Better use more terrain pages than making one too big, this is also nicer for the textures, they will not need to be stretched so much.



Good luck and much fun!
(Ok that was much writing, maybe you log in an reward me with a thumbs up Kudo in the upper corner of the message and I will be happy ;) )

Latin1

18-07-2011 20:22:03

Of course! I forgot about giving Kudos, sorry about that.

It seems odd that Ogre and Bullet would invert a terrain's rows, but not the vertices on each row.

Either way, your explanation cleared everything up, and I'm off to learn more about paging and OgreBullet.

Once more, thank you for all your help. :D

Latin1

captaincrunch80

18-07-2011 22:20:01

Anytime!

About the inverted rows...
Jeah it seams a little odd, but in the end it is just a question of where you start to build up the mesh in positive z or negative z direction.

Cya! Thx for Kudo 8)

namwenio

28-12-2013 14:57:51

I've got this somewhat working...

I had to change the pTerrainHeightDataConvert initialization to:
float *pTerrainHeightDataConvert = new float[terrainPageSize * terrainPageSize];

How would I enable the wireframe mode in bullet to see the collision mesh?