Skip to main content

History: MOGRE MovableText

Source of version: 6 (current)

Copy to clipboard
            {maketoc}

!!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|Mogre)) Namespace, you have to ((Building MOGRE from source|build Mogre from source)). Add the file ''MovableText.h'' to ''include'' directory and ''MovableText.cpp'' to ''src'' directory of your Mogre Sources path.

[http://www.ogre3d.org/phpBB2addons/viewtopic.php?p=47884#16315|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 [http://www.ogre3d.org/phpBB2addons/viewforum.php?f=8|Mogre forum].

%help% __Open question:__ How to remove the MovableText from a SceneNode? (see [http://www.ogre3d.org/phpBB2addons/viewtopic.php?t=7213|here])

%info% Consinder to use ((MOGRE MovableText by Billboards)) instead. It's similar, but doesn't need a Mogre compilation.
!!Usage example
{CODE(wrap="1", colors="c#")}// 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);{CODE}

!!See also
* ((MovableText)) - the original
* ((MovableTextOverlay)) - similar, but only for text
* ((MOGRE MovableText by Billboards)) - similar, but no Mogre compilation needed
* ((Simple text in MOGRE)) - shows text independent of a {LEX()}SceneNode{LEX} 
* ((Easy Debug Text for MOGRE)) - shows a text, which can be updated
* ((SpriteManager2d)) | ((MOGRE SpriteManager2d)) - code snippet
* ((OgreSprites)) - similar, but without use of ''Billboard''
* ((ManualObject 2D))
* {LEX()}Overlay{LEX}
* {LEX()}Billboard{LEX}
* {LEX()}GUI{LEX} - several gui systems

!!Source
!!!MovableText.h
{CODE(wrap="1", colors="c#")}#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);
      }
   };
}{CODE}

!!!MovableText.cpp
{CODE(wrap="1", colors="c#")}/**
 * 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);
    }
}{CODE}