- Cookbook »
- Snippets »
- Rendering »
- MovableText
IMPORTANT: This code is meant to be used with old releases of Ogre.
For Ogre 1.12+, rather use Font::putText.
Table of contents
What is it?
This class will allow you to write some text in the 3D world, the features are:
- The text is attached to a -SceneNode and therefore follows its movement and get smaller when further away
- The text is always facing the camera (like a -Billboard)
- The text can be positioned horizontally and vertically (center, left, etc...)
- The text can be translated on the UNIT_Y vector
How to use it
This class is very easy to use:
Ogre::MovableText* msg = new Ogre::MovableText("TXT_001", "this is the caption"); msg->setTextAlignment(Ogre::MovableText::H_CENTER, Ogre::MovableText::V_ABOVE); // Center horizontally and display above the node /* msg->setAdditionalHeight( 2.0f ); //msg->setAdditionalHeight( ei.getRadius() ) // apparently not needed from 1.7*/ mNode->attachObject(msg);
OGRE v2.1 and Above Notes
To use this with OGRE 2.1 or newer versions, you can use the adapted snippet below:
Plugin disabled
Plugin attach cannot be executed.
Instructions can be found in the zip file. Please note that this adapted snippet is not compatible with previous OGRE versions anymore.
OGRE v1.7 and Above Notes
To use this with OGRE 1.7 or newer versions, you need this resource package below:
Plugin disabled
Plugin attach cannot be executed.
(A silly, easy to make mistake: don't forget to add the directory in the above archive to your resources.)
OGRE v1.6 and Below Notes
- Because of the latest modifications to the source to work with 1.7 and above, you may need to change BlueHighway-8 to BlueHighway if you're using 1.6 or below. (Or maybe you should consider updating your OGRE version )
- To use MovableText on Ogre 1.6.x (Shoggoth), just add this:
virtual void visitRenderables(Ogre::Renderable::Visitor* visitor, bool debugRenderables = false) {};
in the public method section.
- Default font: See note on the discussion page. (TODO: integrate content to this main page)
- Text is displayed from the center of the -SceneNode, setAdditionalHeight() allows you to translate the text on the UNIT_Y vector.
- For Mogre, you can use MOGRE MovableText
Source code
[+] MovableText.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 2012 to work with newer versions of OGRE by MindCalamity <mindcalamity@gmail.com> * - See "Notes" on: http://www.ogre3d.org/tikiwiki/tiki-editpage.php?page=MovableText */ #ifndef __include_MovableText_H__ #define __include_MovableText_H__ namespace Ogre { class MovableText : public MovableObject, public Renderable { /******************************** MovableText data ****************************/ public: enum HorizontalAlignment {H_LEFT, H_CENTER}; enum VerticalAlignment {V_BELOW, V_ABOVE, V_CENTER}; protected: String mFontName; String mType; String mName; String mCaption; HorizontalAlignment mHorizontalAlignment; VerticalAlignment mVerticalAlignment; ColourValue mColor; RenderOperation mRenderOp; AxisAlignedBox mAABB; LightList mLList; Real mCharHeight; Real mSpaceWidth; bool mNeedUpdate; bool mUpdateColors; bool mOnTop; Real mTimeUntilNextToggle; Real mRadius; Vector3 mGlobalTranslation; Vector3 mLocalTranslation; Camera *mpCam; RenderWindow *mpWin; Font *mpFont; MaterialPtr mpMaterial; MaterialPtr mpBackgroundMaterial; /******************************** public methods ******************************/ public: MovableText(const String &name, const String &caption, const String &fontName = "BlueHighway-8", Real charHeight = 1.0, const ColourValue &color = ColourValue::White); virtual ~MovableText(); // Add to build on Shoggoth: virtual void visitRenderables(Ogre::Renderable::Visitor* visitor, bool debugRenderables = false); // Set settings void setFontName(const String &fontName); void setCaption(const String &caption); void setColor(const ColourValue &color); void setCharacterHeight(Real height); void setSpaceWidth(Real width); void setTextAlignment(const HorizontalAlignment& horizontalAlignment, const VerticalAlignment& verticalAlignment); void setGlobalTranslation( Vector3 trans ); void setLocalTranslation( Vector3 trans ); 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;} Real getCharacterHeight() const {return mCharHeight;} Real getSpaceWidth() const {return mSpaceWidth;} Vector3 getGlobalTranslation() const {return mGlobalTranslation;} Vector3 getLocalTranslation() const {return mLocalTranslation;} 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;}; }; } //end namespace Ogre #endif
[+] 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 2012 to work with newer versions of OGRE by MindCalamity <mindcalamity@gmail.com> * - See "Notes" on: http://www.ogre3d.org/tikiwiki/tiki-editpage.php?page=MovableText */ #include <stdafx.h> #include "OGRE/Ogre.h" #include "OGRE/OgreFontManager.h" #include "movableText.h" using namespace Ogre; #define POS_TEX_BINDING 0 #define COLOUR_BINDING 1 MovableText::MovableText(const String &name, const String &caption, const String &fontName, Real 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) , mGlobalTranslation(0.0) , mLocalTranslation(0.0) { assert(name != "" && "Trying to create MovableText without name"); assert(caption != "" && "Trying to create MovableText without caption"); mRenderOp.vertexData = NULL; this->setFontName(mFontName); this->_setupGeometry(); } MovableText::~MovableText() { if (mRenderOp.vertexData) delete mRenderOp.vertexData; // May cause crashing... check this and comment if it does if (!mpMaterial.isNull()) MaterialManager::getSingletonPtr()->remove(mpMaterial->getName()); } void MovableText::setFontName(const 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) throw 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->setDepthBias(1.0,1.0); mpMaterial->setDepthWriteEnabled(mOnTop); mpMaterial->setLightingEnabled(false); mNeedUpdate = true; } } void MovableText::setCaption(const 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(Real height) { if (height != mCharHeight) { mCharHeight = height; mNeedUpdate = true; } } void MovableText::setSpaceWidth(Real 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::setGlobalTranslation( Vector3 trans ) { mGlobalTranslation = trans; } void MovableText::setLocalTranslation( Vector3 trans ) { mLocalTranslation = trans; } void MovableText::showOnTop(bool show) { if( mOnTop != show && !mpMaterial.isNull() ) { mOnTop = show; mpMaterial->setDepthBias(1.0,1.0); mpMaterial->setDepthCheckEnabled(!mOnTop); mpMaterial->setDepthWriteEnabled(mOnTop); } } void MovableText::_setupGeometry() { assert(mpFont); assert(!mpMaterial.isNull()); unsigned int vertexCount = static_cast<unsigned int>(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(); float *pPCBuff = static_cast<float*>(ptbuf->lock(HardwareBuffer::HBL_DISCARD)); float largestWidth = 0; float left = 0 * 2.0 - 1.0; float top = -((0 * 2.0) - 1.0); Real spaceWidth = mSpaceWidth; // Derive space width from a capital A if (spaceWidth == 0) spaceWidth = mpFont->getGlyphAspectRatio('A') * mCharHeight * 2.0; // for calculation of AABB Ogre::Vector3 min, max, currPos; Ogre::Real maxSquaredRadius; bool first = true; // Use iterator String::iterator i, iend; iend = mCaption.end(); bool newLine = true; Real len = 0.0f; Real verticalOffset = 0; switch (mVerticalAlignment) { case MovableText::V_ABOVE: verticalOffset = mCharHeight; break; case MovableText::V_CENTER: verticalOffset = 0.5*mCharHeight; break; case MovableText::V_BELOW: verticalOffset = 0; break; } // Raise the first line of the caption top += verticalOffset; for (i = mCaption.begin(); i != iend; ++i) { if (*i == '\n') top += verticalOffset * 2.0; } for (i = mCaption.begin(); i != iend; ++i) { if (newLine) { len = 0.0f; for (String::iterator j = i; j != iend && *j != '\n'; j++) { if (*j == ' ') len += spaceWidth; else len += mpFont->getGlyphAspectRatio((unsigned char)*j) * mCharHeight * 2.0; } newLine = false; } if (*i == '\n') { left = 0 * 2.0 - 1.0; top -= mCharHeight * 2.0; newLine = true; // Bugfix by Wladimir Lukutin - thanks :) // Also reduce tri count mRenderOp.vertexData->vertexCount -= 6; // Bugfix end. continue; } if (*i == ' ') { // Just leave a gap, no tris left += spaceWidth; // Also reduce tri count mRenderOp.vertexData->vertexCount -= 6; continue; } Real horiz_height = mpFont->getGlyphAspectRatio((unsigned char)*i); Real u1, u2, v1, v2; Ogre::Font::UVRect utmp; utmp = mpFont->getGlyphTexCoords((unsigned char)*i); u1 = utmp.left; u2 = utmp.right; v1 = utmp.top; v2 = utmp.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 (int i = 0; i < (int)mRenderOp.vertexData->vertexCount; ++i) *pDest++ = color; vbuf->unlock(); mUpdateColors = false; } const Quaternion& MovableText::getWorldOrientation(void) const { assert(mpCam); return const_cast<Quaternion&>(mpCam->getDerivedOrientation()); } // Add to build on Shoggoth: void MovableText::visitRenderables(Ogre::Renderable::Visitor* visitor, bool debugRenderables) { } 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*mGlobalTranslation; ppos += rot3x3*mLocalTranslation; // 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); } }
Alias: MovableText_Eihort