Unlike vitek's code, this code parses the nodes as they are loaded, and acts only depending on user parameters. It also allows for loaded objects to respond to collisions. But thank you vitek for your code, that was very valuable information to me.
Irredit is a great tool, however the first time you load an irr file, you get disappointed because you don't have collision detection in place. In this tutorial, I'm going to show you how to automatically setup collision detection in your scene based on XML attributes that you define on each node. We're going to add two custom attributes for each node : Collider, and Collidee. I don't know if those terms are proper english, collider is the moving objets, collidee is the static objets recieving the blow.
Each of these two values can be either true of false. Our code is going to read those attributes, and setup collision detection acordingly.
At the time or writing however, Irredit can't put custom attributes in your irr files. We'll have to do it ourselves. For those who don't know, irr files are XML files, and can be read with any text editor. We're going to add a <userData> ../.. </userData> tag under our mesh in the XML tree, as such :
Code: Select all
<irr_scene>
<attributes>
../..
</attributes>
<node type="cube">
<attributes>
../..
</attributes>
<userData>
<attributes>
<bool name="Collider" value="false" />
<bool name="Collidee" value="true" />
</attributes>
</userData>
<materials>
<attributes>
../..
</attributes>
</materials>
</node>
</irr_scene>
Now for some code. We need a way to parse those attributes in our code, and setup collision detection accordingly. This is done through an interface class called ISceneUserDataSerializer. You may have noticed that the loadScene method takes two parameters : a file or path to a file, and one of those classes. During scene loading, when the parser encounters a <userData> bloc, it passes it to the userDataSerialiser that you passed as a parameter, if you did. So we're going to make our parser class, derived from ISceneUserDataSerializer :
Code: Select all
class CollisionMaker : public irr::scene::ISceneUserDataSerializer
{
irr::IrrlichtDevice* mDevice;
irr::scene::IMetaTriangleSelector* mSelector;
public:
CollisionMaker(irr::IrrlichtDevice* pDev);
irr::scene::IMetaTriangleSelector* getSelector();
irr::io::IAttributes* createUserData (irr::scene::ISceneNode *forSceneNode); //override from
void OnReadUserData (irr::scene::ISceneNode *forSceneNode, irr::io::IAttributes *userData);
void LoadScene(const c8 *filename);
};
Let's go over all the functions in the class.
The constructor will store the device pointer (we'll need it), and create an empty metaselector :
Code: Select all
CollisionMaker::CollisionMaker(irr::IrrlichtDevice* pDev)
{
mDevice = pDev;
mSelector = mDevice->getSceneManager()->createMetaTriangleSelector();
}
Code: Select all
irr::scene::IMetaTriangleSelector* CollisionMaker::getSelector()
{
return mSelector;
}
Code: Select all
irr::io::IAttributes* CollisionMaker::createUserData (irr::scene::ISceneNode *forSceneNode)
{
//Sending 0 caus' we don't care
return (irr::io::IAttributes*)0;
}
For this tutorial's purpose, I will make the triangle selector from mesh if the node is octree, and from bounding box if it's something else. This is to show both methods, so you can do what you want afterwards.
Code: Select all
void CollisionMaker::OnReadUserData (irr::scene::ISceneNode *forSceneNode, irr::io::IAttributes *userData)
{
//if the attribute is not set, it's considered false. If it's false, we don't do anything.
if(userData->getAttributeAsBool("Collidee"))
{
//Detecting node type
if(forSceneNode->getType()==irr::scene::ESNT_OCT_TREE)
{
//Node is an octree, let's get mesh info
irr::io::IAttributes *attributes = mDevice->getFileSystem()->createEmptyAttributes();
forSceneNode->serializeAttributes(attributes);
irr::scene::IAnimatedMesh* mesh = mDevice->getSceneManager()->getMesh(attributes->getAttributeAsString("Mesh").c_str());
attributes->drop();
//Creating selector based on mesh
irr::scene::ITriangleSelector* thisSelector = mDevice->getSceneManager()->createOctTreeTriangleSelector(mesh->getMesh(0), forSceneNode, 128);
//Adding the selector to the meta selector
mSelector->addTriangleSelector(thisSelector);
thisSelector->drop();
}
else
{
//Node isn't an octree, we build a triangle selector based on bounding box
irr::scene::ITriangleSelector* thisSelector = mDevice->getSceneManager()->createTriangleSelectorFromBoundingBox(forSceneNode);
//And we add it to the metaSelector
mSelector->addTriangleSelector(thisSelector);
thisSelector->drop();
}
}
if(userData->getAttributeAsBool("Collider"))
{
//Now we animate nodes that are supposed to respond to collisions based on the metaselector
//create an animator using the metaselector. Collision values are arbitrary. Elipsoid is made according to mesh bounding box
irr::scene::ISceneNodeAnimatorCollisionResponse* anim = mDevice->getSceneManager()->createCollisionResponseAnimator(
mSelector, forSceneNode, forSceneNode->getBoundingBox().MaxEdge,
irr::core::vector3df(0,-1,0), irr::core::vector3df(0,0,0));
//Adding the animator to the node
forSceneNode->addAnimator(anim);
anim->drop();
}
}
Code: Select all
void CollisionMaker::LoadScene(const c8 *filename)
{
mDevice->getSceneManager()->loadScene(filename, this);
}
Now as a test, we can load a level mesh (a room for example) as an octree, and add a cube some distance above the floor. Set the level's collidee value to true, the cube's collider value to true. You'll notice the cube falling down on the floor and laying flat : Collision response.
A current bug in this code is that a node can't be collider and collidee at the same time. If you do that, it will collide with itself and won't move. To solve this, a different method should be used, where we make a different metaselector for each node, with all the nodes excluding itself. But with what you've learned, I'm sure you're going to manage that easily
Can't wait for user data to work in IrrEdit !
Happy coding everyone !