MOGRE MovableText        

About

A MovableText is a kind of {LEX()}billboard{LEX}, created from a String using a Font (loaded as resource) and placed as any {LEX()}SceneNode{LEX} or {LEX()}Bone{LEX}.

Features

  • The text is attached to a Node and therefore follows its movements and gets smaller when further away
  • The text is always facing the camera
  • The text can be positioned horizontally and vertically (center, left, etc...)
  • The text can be translated on the UNIT_Y vector

Some uses

  • Text Balloons over a Character/Avatar
  • Dynamic text over a Sign
  • Roll Announcements (like in a Score Board on a NBA Stadium)


To add the class MovableText to the Mogre Namespace, you have to build Mogre from source. Add the file MovableText.h to include directory and MovableText.cpp to src directory of your Mogre Sources path.

The orginal version from a thread in MOGRE Forum was updated in order to compile with MOGRE 0.2.0 for Ogre 1.4.0. Some changes are not validated. Please report any issue in Mogre forum.

help Open question: How to remove the MovableText from a SceneNode? (see here)

Info Consinder to use MOGRE MovableText by Billboards instead. It's similar, but doesn't need a Mogre compilation.

Usage example

// create entity
MovableText msg = new MovableText("txt001", "Terminator", "BlueHighway", 4, new ColourValue(200,50,200));
msg.SetTextAlignment(MovableText.HorizontalAlignment.H_CENTER, MovableText.VerticalAlignment.V_ABOVE);
msg.AdditionalHeight = 80.0f            

// attach to a -SceneNode
node.AttachObject(msg);

See also

Source

MovableText.h

#pragma once

#include "MogreSimpleRenderable.h"

/**
 * File: MovableText.h
 *
 * description: This create create a billboarding object that display a text.
 *
 * @author  2003 by cTh see gavocanov@rambler.ru
 * @update  2006 by barraq see nospam@barraquand.com
 * @update  2007 by GermanDZ see Ogre Add-On Forums
 */
namespace Ogre {

class MovableText : public SimpleRenderable
{
    /******************************** MovableText data ****************************/
public:
    enum HorizontalAlignment    {H_LEFT, H_CENTER};
    enum VerticalAlignment      {V_BELOW, V_ABOVE};

protected:
   String         mFontName;
   String         mType;
   String         mName;
   String         mCaption;
   HorizontalAlignment   mHorizontalAlignment;
   VerticalAlignment   mVerticalAlignment;

   ColourValue      mColor;
   RenderOperation   mRenderOp;
   AxisAlignedBox   mAABB;
   LightList      mLList;

   uint         mCharHeight;
   uint         mSpaceWidth;

   bool         mNeedUpdate;
   bool         mUpdateColors;
   bool         mOnTop;

   Real         mTimeUntilNextToggle;
   Real         mRadius;
    Real            mAdditionalHeight;

   Camera         *mpCam;
   RenderWindow   *mpWin;
   Font         *mpFont;
   MaterialPtr      mpMaterial;
    MaterialPtr      mpBackgroundMaterial;

    /******************************** public methods ******************************/
public:
   MovableText(const String &name, const String &caption, const String &fontName = "BlueHighway", int charHeight = 32, const ColourValue &color = ColourValue::White);
   virtual ~MovableText();

    // Set settings
   void    setFontName(const String &fontName);
   void    setCaption(const String &caption);
   void    setColor(const ColourValue &color);
   void    setCharacterHeight(uint height);
   void    setSpaceWidth(uint width);
   void    setTextAlignment(const HorizontalAlignment& horizontalAlignment, const VerticalAlignment& verticalAlignment);
   void    setAdditionalHeight( Real height );
    void    showOnTop(bool show=true);

    // Get settings
   const   String          &getFontName() const {return mFontName;}
    const   String          &getCaption() const {return mCaption;}
   const   ColourValue     &getColor() const {return mColor;}
   
    uint    getCharacterHeight() const {return mCharHeight;}
   uint    getSpaceWidth() const {return mSpaceWidth;}
    Real    getAdditionalHeight() const {return mAdditionalHeight;}
    bool    getShowOnTop() const {return mOnTop;}
    AxisAlignedBox           GetAABB(void) { return mAABB; }

    /******************************** protected methods and overload **************/
protected:

    // from MovableText, create the object
   void   _setupGeometry();
   void   _updateColors();

   // from MovableObject
   void    getWorldTransforms(Matrix4 *xform) const;
    Real    getBoundingRadius(void) const {return mRadius;};
   Real    getSquaredViewDepth(const Camera *cam) const {return 0;};
    const   Quaternion        &getWorldOrientation(void) const;
    const   Vector3           &getWorldPosition(void) const;
   const   AxisAlignedBox    &getBoundingBox(void) const {return mAABB;};
   const   String            &getName(void) const {return mName;};
   const   String            &getMovableType(void) const {static Ogre::String movType = "MovableText"; return movType;};

    void    _notifyCurrentCamera(Camera *cam);
   void    _updateRenderQueue(RenderQueue* queue);

   // from renderable
   void    getRenderOperation(RenderOperation &op);
   const   MaterialPtr       &getMaterial(void) const {assert(!mpMaterial.isNull());return mpMaterial;};
   const   LightList         &getLights(void) const {return mLList;};
};

}

namespace Mogre
{

   public ref class MovableText : public SimpleRenderable
   {
    /******************************** MovableText data ****************************/
   public:
      enum class HorizontalAlignment    {H_LEFT, H_CENTER};
      enum class VerticalAlignment      {V_BELOW, V_ABOVE};

      /******************************** public methods ******************************/
   public:
      MovableText(String^ name, String^ caption, String^ fontName, int charHeight, ColourValue color)
         : SimpleRenderable( _ctor(name, caption, fontName, charHeight, color) )
      {
         _createdByCLR = true;
      }

      MovableText(String^ name, String^ caption, String^ fontName, int charHeight)
         : SimpleRenderable( _ctor(name, caption, fontName, charHeight, ColourValue::White) )
      {
         _createdByCLR = true;
      }

      MovableText(String^ name, String^ caption, String^ fontName)
         : SimpleRenderable( _ctor(name, caption, fontName, 32, ColourValue::White) )
      {
         _createdByCLR = true;
      }

      MovableText(String^ name, String^ caption)
         : SimpleRenderable( _ctor(name, caption, "BlueHighway", 32, ColourValue::White) )
      {
         _createdByCLR = true;
      }

      property String^ FontName
      {
         String^ get()
         {
            return TO_CLR_STRING( static_cast<Ogre::MovableText*>(_native)->getFontName() );
         }
         void set(String^ value)
         {
            DECLARE_NATIVE_STRING(o_value, value)
            static_cast<Ogre::MovableText*>(_native)->setFontName(o_value);
         }
      }

      property String^ Caption
      {
         String^ get()
         {
            return TO_CLR_STRING( static_cast<Ogre::MovableText*>(_native)->getCaption() );
         }
         void set(String^ value)
         {
            DECLARE_NATIVE_STRING(o_value, value)
            static_cast<Ogre::MovableText*>(_native)->setCaption(o_value);
         }
      }

      property ColourValue Color
      {
         ColourValue get()
         {
            return static_cast<Ogre::MovableText*>(_native)->getColor();
         }
         void set(ColourValue value)
         {
            static_cast<Ogre::MovableText*>(_native)->setColor( value );
         }
      }

      property Ogre::uint CharacterHeight
      {
         Ogre::uint get()
         {
            return static_cast<Ogre::MovableText*>(_native)->getCharacterHeight();
         }
         void set(Ogre::uint value)
         {
            static_cast<Ogre::MovableText*>(_native)->setCharacterHeight( value );
         }
      }

      property Ogre::uint SpaceWidth
      {
         Ogre::uint get()
         {
            return static_cast<Ogre::MovableText*>(_native)->getSpaceWidth();
         }
         void set(Ogre::uint value)
         {
            static_cast<Ogre::MovableText*>(_native)->setSpaceWidth( value );
         }
      }

      property Ogre::Real AdditionalHeight
      {
         Ogre::Real get()
         {
            return static_cast<Ogre::MovableText*>(_native)->getAdditionalHeight();
         }
         void set(Ogre::Real value)
         {
            static_cast<Ogre::MovableText*>(_native)->setAdditionalHeight( value );
         }
      }

      property bool ShowOnTop
      {
         bool get()
         {
            return static_cast<Ogre::MovableText*>(_native)->getShowOnTop();
         }
         void set(bool value)
         {
            static_cast<Ogre::MovableText*>(_native)->showOnTop( value );
         }
      }
      
      void SetTextAlignment(HorizontalAlignment horizontalAlignment, VerticalAlignment verticalAlignment)
      {
         static_cast<Ogre::MovableText*>(_native)->setTextAlignment( (Ogre::MovableText::HorizontalAlignment)horizontalAlignment, (Ogre::MovableText::VerticalAlignment)verticalAlignment);
      }

      AxisAlignedBox^ GetAABB()
      {
         return static_cast<Ogre::MovableText*>(_native)->GetAABB();
      }

   private:
      // Called by all the constructors
      Ogre::MovableText* _ctor(String^ name, String^ caption, String^ fontName, int charHeight, ColourValue color)
      {
         DECLARE_NATIVE_STRING(o_name, name)
         DECLARE_NATIVE_STRING(o_caption, caption)
         DECLARE_NATIVE_STRING(o_fontName, fontName)

         return new Ogre::MovableText(o_name, o_caption, o_fontName, charHeight, color);
      }
   };
}

MovableText.cpp

/**
 * File: MovableText.cpp
 *
 * description: This create create a billboarding object that display a text.
 *
 * @author  2003 by cTh see gavocanov@rambler.ru
 * @update  2006 by barraq see nospam@barraquand.com
 * @update  2007 by GermanDZ see Ogre Add-On Forums
 */

#include "stdafx.h"
#include "MovableText.h"

using namespace Ogre;

#define POS_TEX_BINDING    0
#define COLOUR_BINDING     1

MovableText::MovableText(const Ogre::String &name, const Ogre::String &caption, const Ogre::String &fontName, int charHeight, const ColourValue &color)
: mpCam(NULL)
, mpWin(NULL)
, mpFont(NULL)
, mName(name)
, mCaption(caption)
, mFontName(fontName)
, mCharHeight(charHeight)
, mColor(color)
, mType("MovableText")
, mTimeUntilNextToggle(0)
, mSpaceWidth(0)
, mUpdateColors(true)
, mOnTop(false)
, mHorizontalAlignment(H_LEFT)
, mVerticalAlignment(V_BELOW)
, mAdditionalHeight(0.0)
{
    if (name == "")
        Exception(Exception::ERR_INVALIDPARAMS, "Trying to create MovableText without name", "MovableText::MovableText");

    if (caption == "")
        Exception(Exception::ERR_INVALIDPARAMS, "Trying to create MovableText without caption", "MovableText::MovableText");

    mRenderOp.vertexData = NULL;
    this->setFontName(mFontName);
    this->_setupGeometry();
}

MovableText::~MovableText()
{
    if (mRenderOp.vertexData)
        delete mRenderOp.vertexData;
}

void MovableText::setFontName(const Ogre::String &fontName)
{
    if((Ogre::MaterialManager::getSingletonPtr()->resourceExists(mName + "Material")))
    {
        Ogre::MaterialManager::getSingleton().remove(mName + "Material");
    }

    if (mFontName != fontName || mpMaterial.isNull() || !mpFont)
    {
        mFontName = fontName;
        mpFont = (Font *)FontManager::getSingleton().getByName(mFontName).getPointer();
        if (!mpFont)
            Exception(Exception::ERR_ITEM_NOT_FOUND, "Could not find font " + fontName, "MovableText::setFontName");

        mpFont->load();
        if (!mpMaterial.isNull())
        {
            MaterialManager::getSingletonPtr()->remove(mpMaterial->getName());
            mpMaterial.setNull();
        }

        mpMaterial = mpFont->getMaterial()->clone(mName + "Material");
        if (!mpMaterial->isLoaded())
            mpMaterial->load();

        mpMaterial->setDepthCheckEnabled(!mOnTop);
        mpMaterial->getTechnique(0)->getPass(0)->setDepthBias(!mOnTop); //setDepthBias(!mOnTop);
        mpMaterial->setDepthWriteEnabled(mOnTop);
        mpMaterial->setLightingEnabled(false);
        mNeedUpdate = true;
    }
}

void MovableText::setCaption(const Ogre::String &caption)
{
    if (caption != mCaption)
    {
        mCaption = caption;
        mNeedUpdate = true;
    }
}

void MovableText::setColor(const ColourValue &color)
{
    if (color != mColor)
    {
        mColor = color;
        mUpdateColors = true;
    }
}

void MovableText::setCharacterHeight(uint height)
{
    if (height != mCharHeight)
    {
        mCharHeight = height;
        mNeedUpdate = true;
    }
}

void MovableText::setSpaceWidth(uint width)
{
    if (width != mSpaceWidth)
    {
        mSpaceWidth = width;
        mNeedUpdate = true;
    }
}

void MovableText::setTextAlignment(const HorizontalAlignment& horizontalAlignment, const VerticalAlignment& verticalAlignment)
{
    if(mHorizontalAlignment != horizontalAlignment)
    {
        mHorizontalAlignment = horizontalAlignment;
        mNeedUpdate = true;
    }
    if(mVerticalAlignment != verticalAlignment)
    {
        mVerticalAlignment = verticalAlignment;
        mNeedUpdate = true;
    }
}

void MovableText::setAdditionalHeight( Real height )
{
    if( mAdditionalHeight != height )
    {
        mAdditionalHeight = height;
        mNeedUpdate = true;
    }
}

void MovableText::showOnTop(bool show)
{
    if( mOnTop != show && !mpMaterial.isNull() )
    {
        mOnTop = show;
        mpMaterial->getTechnique(0)->getPass(0)->setDepthBias(!mOnTop);
        mpMaterial->setDepthCheckEnabled(!mOnTop);
        mpMaterial->setDepthWriteEnabled(mOnTop);
    }
}

void MovableText::_setupGeometry()
{
    assert(mpFont);
    assert(!mpMaterial.isNull());

    uint vertexCount = static_cast<uint>(mCaption.size() * 6);

    if (mRenderOp.vertexData)
    {
        // Removed this test as it causes problems when replacing a caption
        // of the same size: replacing "Hello" with "hello"
        // as well as when changing the text alignment
        //if (mRenderOp.vertexData->vertexCount != vertexCount)
        {
            delete mRenderOp.vertexData;
            mRenderOp.vertexData = NULL;
            mUpdateColors = true;
        }
    }

    if (!mRenderOp.vertexData)
        mRenderOp.vertexData = new VertexData();

    mRenderOp.indexData = 0;
    mRenderOp.vertexData->vertexStart = 0;
    mRenderOp.vertexData->vertexCount = vertexCount;
    mRenderOp.operationType = RenderOperation::OT_TRIANGLE_LIST;
    mRenderOp.useIndexes = false;

    VertexDeclaration  *decl = mRenderOp.vertexData->vertexDeclaration;
    VertexBufferBinding   *bind = mRenderOp.vertexData->vertexBufferBinding;
    size_t offset = 0;

    // create/bind positions/tex.ccord. buffer
    if (!decl->findElementBySemantic(VES_POSITION))
        decl->addElement(POS_TEX_BINDING, offset, VET_FLOAT3, VES_POSITION);

    offset += VertexElement::getTypeSize(VET_FLOAT3);

    if (!decl->findElementBySemantic(VES_TEXTURE_COORDINATES))
        decl->addElement(POS_TEX_BINDING, offset, Ogre::VET_FLOAT2, Ogre::VES_TEXTURE_COORDINATES, 0);

    HardwareVertexBufferSharedPtr ptbuf = HardwareBufferManager::getSingleton().createVertexBuffer(decl->getVertexSize(POS_TEX_BINDING),
        mRenderOp.vertexData->vertexCount,
        HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);
    bind->setBinding(POS_TEX_BINDING, ptbuf);

    // Colours - store these in a separate buffer because they change less often
    if (!decl->findElementBySemantic(VES_DIFFUSE))
        decl->addElement(COLOUR_BINDING, 0, VET_COLOUR, VES_DIFFUSE);

    HardwareVertexBufferSharedPtr cbuf = HardwareBufferManager::getSingleton().createVertexBuffer(decl->getVertexSize(COLOUR_BINDING),
        mRenderOp.vertexData->vertexCount,
        HardwareBuffer::HBU_DYNAMIC_WRITE_ONLY);
    bind->setBinding(COLOUR_BINDING, cbuf);

    size_t charlen = mCaption.size();
    Real *pPCBuff = static_cast<Real*>(ptbuf->lock(HardwareBuffer::HBL_DISCARD));

    float largestWidth = 0;
    float left = 0 * 2.0 - 1.0;
    float top = -((0 * 2.0) - 1.0);

    // Derive space width from a capital A
    if (mSpaceWidth == 0)
        mSpaceWidth = mpFont->getGlyphAspectRatio('A') * mCharHeight * 2.0;

    // for calculation of AABB
    Ogre::Vector3 min, max, currPos;
    Ogre::Real maxSquaredRadius;
    bool first = true;

    // Use iterator
    Ogre::String::iterator i, iend;
    iend = mCaption.end();
    bool newLine = true;
    Real len = 0.0f;

    if(mVerticalAlignment == MovableText::V_ABOVE)
    {
        // Raise the first line of the caption
        top += mCharHeight;
        for (i = mCaption.begin(); i != iend; ++i)
        {
            if (*i == '\n')
                top += mCharHeight * 2.0;
        }
    }

    for (i = mCaption.begin(); i != iend; ++i)
    {
        if (newLine)
        {
            len = 0.0f;
            for (Ogre::String::iterator j = i; j != iend && *j != '\n'; j++)
            {
                if (*j == ' ')
                    len += mSpaceWidth;
                else
                    len += mpFont->getGlyphAspectRatio(*j) * mCharHeight * 2.0;
            }
            newLine = false;
        }

        if (*i == '\n')
        {
            left = 0 * 2.0 - 1.0;
            top -= mCharHeight * 2.0;
            newLine = true;
            continue;
        }

        if (*i == ' ')
        {
            // Just leave a gap, no tris
            left += mSpaceWidth;
            // Also reduce tri count
            mRenderOp.vertexData->vertexCount -= 6;
            continue;
        }

        Real horiz_height = mpFont->getGlyphAspectRatio(*i);
        Real u1, u2, v1, v2;
        Ogre::Font::UVRect rect;
        rect = mpFont->getGlyphTexCoords(*i); // (*i, u1, v1, u2, v2);

        u1 = rect.left;
        v1 = rect.top;
        u2 = rect.right;
        v2 = rect.bottom;
        // each vert is (x, y, z, u, v)
        //-------------------------------------------------------------------------------------
        // First tri
        //
        // Upper left
        if(mHorizontalAlignment == MovableText::H_LEFT)
            *pPCBuff++ = left;
        else
            *pPCBuff++ = left - (len / 2);
        *pPCBuff++ = top;
        *pPCBuff++ = -1.0;
        *pPCBuff++ = u1;
        *pPCBuff++ = v1;

        // Deal with bounds
        if(mHorizontalAlignment == MovableText::H_LEFT)
            currPos = Ogre::Vector3(left, top, -1.0);
        else
            currPos = Ogre::Vector3(left - (len / 2), top, -1.0);
        if (first)
        {
            min = max = currPos;
            maxSquaredRadius = currPos.squaredLength();
            first = false;
        }
        else
        {
            min.makeFloor(currPos);
            max.makeCeil(currPos);
            maxSquaredRadius = std::max(maxSquaredRadius, currPos.squaredLength());
        }

        top -= mCharHeight * 2.0;

        // Bottom left
        if(mHorizontalAlignment == MovableText::H_LEFT)
            *pPCBuff++ = left;
        else
            *pPCBuff++ = left - (len / 2);
        *pPCBuff++ = top;
        *pPCBuff++ = -1.0;
        *pPCBuff++ = u1;
        *pPCBuff++ = v2;

        // Deal with bounds
        if(mHorizontalAlignment == MovableText::H_LEFT)
            currPos = Ogre::Vector3(left, top, -1.0);
        else
            currPos = Ogre::Vector3(left - (len / 2), top, -1.0);
        min.makeFloor(currPos);
        max.makeCeil(currPos);
        maxSquaredRadius = std::max(maxSquaredRadius, currPos.squaredLength());

        top += mCharHeight * 2.0;
        left += horiz_height * mCharHeight * 2.0;

        // Top right
        if(mHorizontalAlignment == MovableText::H_LEFT)
            *pPCBuff++ = left;
        else
            *pPCBuff++ = left - (len / 2);
        *pPCBuff++ = top;
        *pPCBuff++ = -1.0;
        *pPCBuff++ = u2;
        *pPCBuff++ = v1;
        //-------------------------------------------------------------------------------------

        // Deal with bounds
        if(mHorizontalAlignment == MovableText::H_LEFT)
            currPos = Ogre::Vector3(left, top, -1.0);
        else
            currPos = Ogre::Vector3(left - (len / 2), top, -1.0);
        min.makeFloor(currPos);
        max.makeCeil(currPos);
        maxSquaredRadius = std::max(maxSquaredRadius, currPos.squaredLength());

        //-------------------------------------------------------------------------------------
        // Second tri
        //
        // Top right (again)
        if(mHorizontalAlignment == MovableText::H_LEFT)
            *pPCBuff++ = left;
        else
            *pPCBuff++ = left - (len / 2);
        *pPCBuff++ = top;
        *pPCBuff++ = -1.0;
        *pPCBuff++ = u2;
        *pPCBuff++ = v1;

        currPos = Ogre::Vector3(left, top, -1.0);
        min.makeFloor(currPos);
        max.makeCeil(currPos);
        maxSquaredRadius = std::max(maxSquaredRadius, currPos.squaredLength());

        top -= mCharHeight * 2.0;
        left -= horiz_height  * mCharHeight * 2.0;

        // Bottom left (again)
        if(mHorizontalAlignment == MovableText::H_LEFT)
            *pPCBuff++ = left;
        else
            *pPCBuff++ = left - (len / 2);
        *pPCBuff++ = top;
        *pPCBuff++ = -1.0;
        *pPCBuff++ = u1;
        *pPCBuff++ = v2;

        currPos = Ogre::Vector3(left, top, -1.0);
        min.makeFloor(currPos);
        max.makeCeil(currPos);
        maxSquaredRadius = std::max(maxSquaredRadius, currPos.squaredLength());

        left += horiz_height  * mCharHeight * 2.0;

        // Bottom right
        if(mHorizontalAlignment == MovableText::H_LEFT)
            *pPCBuff++ = left;
        else
            *pPCBuff++ = left - (len / 2);
        *pPCBuff++ = top;
        *pPCBuff++ = -1.0;
        *pPCBuff++ = u2;
        *pPCBuff++ = v2;
        //-------------------------------------------------------------------------------------

        currPos = Ogre::Vector3(left, top, -1.0);
        min.makeFloor(currPos);
        max.makeCeil(currPos);
        maxSquaredRadius = std::max(maxSquaredRadius, currPos.squaredLength());

        // Go back up with top
        top += mCharHeight * 2.0;

        float currentWidth = (left + 1)/2 - 0;
        if (currentWidth > largestWidth)
            largestWidth = currentWidth;
    }

    // Unlock vertex buffer
    ptbuf->unlock();

    // update AABB/Sphere radius
    mAABB = Ogre::AxisAlignedBox(min, max);
    mRadius = Ogre::Math::Sqrt(maxSquaredRadius);

    if (mUpdateColors)
        this->_updateColors();

    mNeedUpdate = false;
}

void MovableText::_updateColors(void)
{
    assert(mpFont);
    assert(!mpMaterial.isNull());

    // Convert to system-specific
    RGBA color;
    Root::getSingleton().convertColourValue(mColor, &color);
    HardwareVertexBufferSharedPtr vbuf = mRenderOp.vertexData->vertexBufferBinding->getBuffer(COLOUR_BINDING);
    RGBA *pDest = static_cast<RGBA*>(vbuf->lock(HardwareBuffer::HBL_DISCARD));
    for (uint i = 0; i < mRenderOp.vertexData->vertexCount; ++i)
        *pDest++ = color;
    vbuf->unlock();
    mUpdateColors = false;
}

const Quaternion& MovableText::getWorldOrientation(void) const
{
    assert(mpCam);
    return const_cast<Quaternion&>(mpCam->getDerivedOrientation());
}

const Vector3& MovableText::getWorldPosition(void) const
{
    assert(mParentNode);
    return mParentNode->_getDerivedPosition();
}

void MovableText::getWorldTransforms(Matrix4 *xform) const
{
    if (this->isVisible() && mpCam)
    {
        Matrix3 rot3x3, scale3x3 = Matrix3::IDENTITY;

        // store rotation in a matrix
        mpCam->getDerivedOrientation().ToRotationMatrix(rot3x3);

        // parent node position
        Vector3 ppos = mParentNode->_getDerivedPosition() + Vector3::UNIT_Y*mAdditionalHeight;

        // apply scale
        scale3x3[0][0] = mParentNode->_getDerivedScale().x / 2;
        scale3x3[1][1] = mParentNode->_getDerivedScale().y / 2;
        scale3x3[2][2] = mParentNode->_getDerivedScale().z / 2;

        // apply all transforms to xform       
        *xform = (rot3x3 * scale3x3);
        xform->setTrans(ppos);
    }
}

void MovableText::getRenderOperation(RenderOperation &op)
{
    if (this->isVisible())
    {
        if (mNeedUpdate)
            this->_setupGeometry();
        if (mUpdateColors)
            this->_updateColors();
        op = mRenderOp;
    }
}

void MovableText::_notifyCurrentCamera(Camera *cam)
{
    mpCam = cam;
}

void MovableText::_updateRenderQueue(RenderQueue* queue)
{
    if (this->isVisible())
    {
        if (mNeedUpdate)
            this->_setupGeometry();
        if (mUpdateColors)
            this->_updateColors();

        queue->addRenderable(this, mRenderQueueID, OGRE_RENDERABLE_DEFAULT_PRIORITY);
        //      queue->addRenderable(this, mRenderQueueID, RENDER_QUEUE_SKIES_LATE);
    }
}