Skip to main content
Skeleton Debugger        

Ever wondered what it would be like to be able to see what the bones of your model are actually doing within OGRE without having to look in a modeling package like blender? Well here is the solution.

The skeleton debugger creates two new types of meshes, one for the axis arrows and one for the bones. It then attaches the meshes to the bones themselves using the attachObjectToBone function.

A slightly modified version of ObjectTextDisplay is used to render the names of the bones.

Original forum topic (feel free to make requests/comments/suggestions) : Link

Mandatory picture:

Image

SkeletonDebug.h

Copy to clipboard
#ifndef SKELETONDEBUG_H_INCLUDED #define SKELETONDEBUG_H_INCLUDED #include <Ogre.h> #include <OgreTagPoint.h> #include <vector> #include "ObjectTextDisplay.h" class SkeletonDebug { public: SkeletonDebug(Ogre::Entity *entity, Ogre::SceneManager *man, Ogre::Camera *cam, float boneSize = 0.1f); ~SkeletonDebug(); void setAxesScale(Ogre::Real scale){mScaleAxes = scale;} Ogre::Real getAxesScale(){return mScaleAxes;} void showAxes(bool show); void showNames(bool show); void showBones(bool show); bool axesShown(){return mShowAxes;} bool namesShown(){return mShowNames;} bool bonesShown(){return mShowBones;} void update(); private: std::vector<Ogre::Entity*> mAxisEntities; std::vector<Ogre::Entity*> mBoneEntities; std::vector<ObjectTextDisplay*> mTextOverlays; float mBoneSize; Ogre::Entity *mEntity; Ogre::MaterialPtr mAxisMatPtr; Ogre::MaterialPtr mBoneMatPtr; Ogre::MeshPtr mBoneMeshPtr; Ogre::MeshPtr mAxesMeshPtr; Ogre::SceneManager *mSceneMan; Ogre::Camera *mCamera; Ogre::Real mScaleAxes; bool mShowAxes; bool mShowBones; bool mShowNames; void createAxesMaterial(); void createBoneMaterial(); void createAxesMesh(); void createBoneMesh(); }; #endif // SKELETONDEBUG_H_INCLUDED

SkeletonDebug.cpp

Copy to clipboard
#include "SkeletonDebug.h" using namespace Ogre; SkeletonDebug::SkeletonDebug(Ogre::Entity* entity, Ogre::SceneManager *man, Ogre::Camera *cam, float boneSize) { mEntity = entity; mSceneMan = man; mCamera = cam; mScaleAxes = 1.0; mBoneSize = boneSize; createAxesMaterial(); createBoneMaterial(); createAxesMesh(); createBoneMesh(); mShowAxes = true; mShowBones = true; mShowNames = true; int numBones = mEntity->getSkeleton()->getNumBones(); for(unsigned short int iBone = 0; iBone < numBones; ++iBone) { Ogre::Bone* pBone = mEntity->getSkeleton()->getBone(iBone); if ( !pBone ) { assert(false); continue; } Ogre::Entity *ent; Ogre::TagPoint *tp; // Absolutely HAVE to create bone representations first. Otherwise we would get the wrong child count // because an attached object counts as a child // Would be nice to have a function that only gets the children that are bones... unsigned short numChildren = pBone->numChildren(); if(numChildren == 0) { // There are no children, but we should still represent the bone // Creates a bone of length 1 for leaf bones (bones without children) ent = mSceneMan->createEntity("SkeletonDebug/BoneMesh"); tp = mEntity->attachObjectToBone(pBone->getName(), (Ogre::MovableObject*)ent); mBoneEntities.push_back(ent); } else { for(int i = 0; i < numChildren; ++i) { Vector3 v = pBone->getChild(i)->getPosition(); // If the length is zero, no point in creating the bone representation float length = v.length(); if(length < 0.00001f) continue; ent = mSceneMan->createEntity("SkeletonDebug/BoneMesh"); tp = mEntity->attachObjectToBone(pBone->getName(), (Ogre::MovableObject*)ent); mBoneEntities.push_back(ent); tp->setScale(length, length, length); } } ent = mSceneMan->createEntity("SkeletonDebug/AxesMesh"); tp = mEntity->attachObjectToBone(pBone->getName(), (Ogre::MovableObject*)ent); // Make sure we don't wind up with tiny/giant axes and that one axis doesnt get squashed tp->setScale((mScaleAxes/mEntity->getParentSceneNode()->getScale().x), (mScaleAxes/mEntity->getParentSceneNode()->getScale().y), (mScaleAxes/mEntity->getParentSceneNode()->getScale().z)); mAxisEntities.push_back(ent); Ogre::String name = mEntity->getName() + "SkeletonDebug/BoneText/Bone_"; name += iBone; ObjectTextDisplay *overlay = new ObjectTextDisplay(name, pBone, mCamera, mEntity); overlay->enable(true); overlay->setText(pBone->getName()); mTextOverlays.push_back(overlay); } showAxes(false); showBones(false); showNames(false); } SkeletonDebug::~SkeletonDebug() { } void SkeletonDebug::update() { std::vector<ObjectTextDisplay*>::iterator it; for(it = mTextOverlays.begin(); it < mTextOverlays.end(); it++) { ((ObjectTextDisplay*)*it)->update(); } } void SkeletonDebug::showAxes(bool show) { // Don't change anything if we are already in the proper state if(mShowAxes == show) return; mShowAxes = show; std::vector<Ogre::Entity*>::iterator it; for(it = mAxisEntities.begin(); it < mAxisEntities.end(); ++it) { ((Ogre::Entity*)*it)->setVisible(show); } } void SkeletonDebug::showBones(bool show) { // Don't change anything if we are already in the proper state if(mShowBones == show) return; mShowBones = show; std::vector<Ogre::Entity*>::iterator it; for(it = mBoneEntities.begin(); it < mBoneEntities.end(); ++it) { ((Ogre::Entity*)*it)->setVisible(show); } } void SkeletonDebug::showNames(bool show) { // Don't change anything if we are already in the proper state if(mShowNames == show) return; mShowNames = show; std::vector<ObjectTextDisplay*>::iterator it; for(it = mTextOverlays.begin(); it < mTextOverlays.end(); it++) { ((ObjectTextDisplay*)*it)->enable(show); } } void SkeletonDebug::createAxesMaterial() { Ogre::String matName = "SkeletonDebug/AxesMat"; mAxisMatPtr = MaterialManager::getSingleton().getByName(matName); if (mAxisMatPtr.isNull()) { mAxisMatPtr = MaterialManager::getSingleton().create(matName, ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME); // First pass for axes that are partially within the model (shows transparency) Pass* p = mAxisMatPtr->getTechnique(0)->getPass(0); p->setLightingEnabled(false); p->setPolygonModeOverrideable(false); p->setVertexColourTracking(TVC_AMBIENT); p->setSceneBlending(SBT_TRANSPARENT_ALPHA); p->setCullingMode(CULL_NONE); p->setDepthWriteEnabled(false); p->setDepthCheckEnabled(false); // Second pass for the portion of the axis that is outside the model (solid colour) Pass* p2 = mAxisMatPtr->getTechnique(0)->createPass(); p2->setLightingEnabled(false); p2->setPolygonModeOverrideable(false); p2->setVertexColourTracking(TVC_AMBIENT); p2->setCullingMode(CULL_NONE); p2->setDepthWriteEnabled(false); } } void SkeletonDebug::createBoneMaterial() { Ogre::String matName = "SkeletonDebug/BoneMat"; mBoneMatPtr = MaterialManager::getSingleton().getByName(matName); if (mBoneMatPtr.isNull()) { mBoneMatPtr = MaterialManager::getSingleton().create(matName, ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME); Pass* p = mBoneMatPtr->getTechnique(0)->getPass(0); p->setLightingEnabled(false); p->setPolygonModeOverrideable(false); p->setVertexColourTracking(TVC_AMBIENT); p->setSceneBlending(SBT_TRANSPARENT_ALPHA); p->setCullingMode(CULL_ANTICLOCKWISE); p->setDepthWriteEnabled(false); p->setDepthCheckEnabled(false); } } void SkeletonDebug::createBoneMesh() { String meshName = "SkeletonDebug/BoneMesh"; mBoneMeshPtr = MeshManager::getSingleton().getByName(meshName); if(mBoneMeshPtr.isNull()) { ManualObject mo("tmp"); mo.begin(mBoneMatPtr->getName()); Vector3 basepos[6] = { Vector3(0,0,0), Vector3(mBoneSize, mBoneSize*2, mBoneSize), Vector3(-mBoneSize, mBoneSize*2, mBoneSize), Vector3(-mBoneSize, mBoneSize*2, -mBoneSize), Vector3(mBoneSize, mBoneSize*2, -mBoneSize), Vector3(0, 1, 0), }; // Two colours so that we can distinguish the sides of the bones (we don't use any lighting on the material) ColourValue col = ColourValue(0.5, 0.5, 0.5, 1); ColourValue col1 = ColourValue(0.6, 0.6, 0.6, 1); mo.position(basepos[0]); mo.colour(col); mo.position(basepos[2]); mo.colour(col); mo.position(basepos[1]); mo.colour(col); mo.position(basepos[0]); mo.colour(col1); mo.position(basepos[3]); mo.colour(col1); mo.position(basepos[2]); mo.colour(col1); mo.position(basepos[0]); mo.colour(col); mo.position(basepos[4]); mo.colour(col); mo.position(basepos[3]); mo.colour(col); mo.position(basepos[0]); mo.colour(col1); mo.position(basepos[1]); mo.colour(col1); mo.position(basepos[4]); mo.colour(col1); mo.position(basepos[1]); mo.colour(col1); mo.position(basepos[2]); mo.colour(col1); mo.position(basepos[5]); mo.colour(col1); mo.position(basepos[2]); mo.colour(col); mo.position(basepos[3]); mo.colour(col); mo.position(basepos[5]); mo.colour(col); mo.position(basepos[3]); mo.colour(col1); mo.position(basepos[4]); mo.colour(col1); mo.position(basepos[5]); mo.colour(col1); mo.position(basepos[4]); mo.colour(col); mo.position(basepos[1]); mo.colour(col); mo.position(basepos[5]); mo.colour(col); // indices mo.triangle(0, 1, 2); mo.triangle(3, 4, 5); mo.triangle(6, 7, 8); mo.triangle(9, 10, 11); mo.triangle(12, 13, 14); mo.triangle(15, 16, 17); mo.triangle(18, 19, 20); mo.triangle(21, 22, 23); mo.end(); mBoneMeshPtr = mo.convertToMesh(meshName, ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME); } } void SkeletonDebug::createAxesMesh() { String meshName = "SkeletonDebug/AxesMesh"; mAxesMeshPtr = MeshManager::getSingleton().getByName(meshName); if (mAxesMeshPtr.isNull()) { ManualObject mo("tmp"); mo.begin(mAxisMatPtr->getName()); /* 3 axes, each made up of 2 of these (base plane = XY) * .------------|\ * '------------|/ */ mo.estimateVertexCount(7 * 2 * 3); mo.estimateIndexCount(3 * 2 * 3); Quaternion quat[6]; ColourValue col[3]; // x-axis quat[0] = Quaternion::IDENTITY; quat[1].FromAxes(Vector3::UNIT_X, Vector3::NEGATIVE_UNIT_Z, Vector3::UNIT_Y); col[0] = ColourValue::Red; col[0].a = 0.3; // y-axis quat[2].FromAxes(Vector3::UNIT_Y, Vector3::NEGATIVE_UNIT_X, Vector3::UNIT_Z); quat[3].FromAxes(Vector3::UNIT_Y, Vector3::UNIT_Z, Vector3::UNIT_X); col[1] = ColourValue::Green; col[1].a = 0.3; // z-axis quat[4].FromAxes(Vector3::UNIT_Z, Vector3::UNIT_Y, Vector3::NEGATIVE_UNIT_X); quat[5].FromAxes(Vector3::UNIT_Z, Vector3::UNIT_X, Vector3::UNIT_Y); col[2] = ColourValue::Blue; col[2].a = 0.3; Vector3 basepos[7] = { // stalk Vector3(0, 0.05, 0), Vector3(0, -0.05, 0), Vector3(0.7, -0.05, 0), Vector3(0.7, 0.05, 0), // head Vector3(0.7, -0.15, 0), Vector3(1, 0, 0), Vector3(0.7, 0.15, 0) }; // vertices // 6 arrows for (size_t i = 0; i < 6; ++i) { // 7 points for (size_t p = 0; p < 7; ++p) { Vector3 pos = quat[i] * basepos[p]; mo.position(pos); mo.colour(col[i / 2]); } } // indices // 6 arrows for (size_t i = 0; i < 6; ++i) { size_t base = i * 7; mo.triangle(base + 0, base + 1, base + 2); mo.triangle(base + 0, base + 2, base + 3); mo.triangle(base + 4, base + 5, base + 6); } mo.end(); mAxesMeshPtr = mo.convertToMesh(meshName, ResourceGroupManager::INTERNAL_RESOURCE_GROUP_NAME); } }

Modified version of ObjectTextDisplay.h

Copy to clipboard
#ifndef __MovableTextOverlay_H__ #define __MovableTextOverlay_H__ #include <Ogre.h> #include <OgreFont.h> #include <OgreFontManager.h> using namespace Ogre; class ObjectTextDisplay { public: ObjectTextDisplay(Ogre::String name, Ogre::Bone* p, Ogre::Camera* c, Ogre::Entity *ent) { m_p = p; m_ent = ent; m_c = c; m_enabled = false; m_text = ""; // create an overlay that we can use for later m_pOverlay = Ogre::OverlayManager::getSingleton().create(name); m_pContainer = static_cast<Ogre::OverlayContainer*>(Ogre::OverlayManager::getSingleton().createOverlayElement( "Panel", name + "container")); m_pOverlay->add2D(m_pContainer); m_pText = Ogre::OverlayManager::getSingleton().createOverlayElement("TextArea", name + "shapeNameText"); m_pText->setDimensions(1.0, 1.0); m_pText->setMetricsMode(Ogre::GMM_PIXELS); m_pText->setPosition(0, 0); m_pText->setParameter("font_name", "testfont"); m_pText->setParameter("char_height", "16"); m_pText->setParameter("horz_align", "center"); m_pText->setColour(Ogre::ColourValue(1.0, 1.0, 1.0)); m_pContainer->addChild(m_pText); m_pOverlay->show(); } virtual ~ObjectTextDisplay() { // overlay cleanup -- Ogre would clean this up at app exit but if your app // tends to create and delete these objects often it's a good idea to do it here. m_pOverlay->hide(); Ogre::OverlayManager *overlayManager = Ogre::OverlayManager::getSingletonPtr(); m_pContainer->removeChild("shapeNameText"); m_pOverlay->remove2D(m_pContainer); overlayManager->destroyOverlayElement(m_pText); overlayManager->destroyOverlayElement(m_pContainer); overlayManager->destroy(m_pOverlay); } void enable(bool enable) { m_enabled = enable; if (enable) m_pOverlay->show(); else m_pOverlay->hide(); } void setText(const Ogre::String& text) { m_text = text; m_pText->setCaption(m_text); } void update(); //protected: Ogre::Bone* m_p; Ogre::Camera* m_c; Ogre::Entity *m_ent; bool m_enabled; Ogre::Overlay* m_pOverlay; Ogre::OverlayElement* m_pText; Ogre::OverlayContainer* m_pContainer; Ogre::String m_text; }; #endif /* __MovableTextOverlay_H__ */

Modified version of ObjectTextDisplay.cpp

Copy to clipboard
#include "ObjectTextDisplay.h" Ogre::Vector3 GetBoneWorldPosition(Ogre::Entity* ent, Ogre::Bone* bone) { Vector3 world_position = bone->_getDerivedPosition(); //multiply with the parent derived transformation Ogre::Node *pParentNode = ent->getParentNode(); Ogre::SceneNode *pSceneNode = ent->getParentSceneNode(); while (pParentNode != NULL) { //process the current i_Node if (pParentNode != pSceneNode) { //this is a tag point (a connection point between 2 entities). which means it has a parent i_Node to be processed world_position = pParentNode->_getFullTransform() * world_position; pParentNode = pParentNode->getParent(); } else { //this is the scene i_Node meaning this is the last i_Node to process world_position = pParentNode->_getFullTransform() * world_position; break; } } return world_position; } void ObjectTextDisplay::update() { if (!m_enabled) return; //Ogre::Vector3 v = m_p->_getDerivedPosition() ; Ogre::Vector3 v = GetBoneWorldPosition(m_ent, m_p); Vector3 ScreenPos = m_c->getProjectionMatrix() * m_c->getViewMatrix() * v; ScreenPos.x = (ScreenPos.x)/2; ScreenPos.y = (ScreenPos.y)/2; float max_x = ScreenPos.x; float min_y = -ScreenPos.y; m_pContainer->setPosition(max_x, min_y + 0.5); }


Notes:

To use the code simple instantiate a SkeletonDebug object. In your render loop call the update() method.

The text display code will ASSERT unless the font it uses is present in your resources.
Edit the ObjectTextDisplay.h file and change the "font_name" parameter to be the font you'd like to use.
You must create a .fontdef file and put the font binary file in your resources.
http://www.ogre3d.org/docs/manual/manual_44.html

The size of the axes drawn can be set by changing the SkeletonDebug.cpp file.
Change this code:

Copy to clipboard
::Ogre::Vector3 pos = quat[i] * basepos[p];
to this (adjust 0.25f to whatever suits your world scale):
Copy to clipboard
::Ogre::Vector3 pos = quat[i] * ( basepos[p] * 0.25f );