Skip to main content

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

Copy to clipboard
// 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

Copy to clipboard
#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

Copy to clipboard
/** * 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); } }