MOGRE MovableText
From Ogre Wiki
Contents |
About
A MovableText is a kind of billboard, created from a String using a Font (loaded as resource) and placed as any SceneNode or Bone.
- 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.
A compiled Mogre library including MovableText can be downloaded here (Mogre 1.4.8)
TODO: Reupload binaries (preferably not to hosts who delete the file after a certain period)
Open question: How to remove the MovableText from a SceneNode? (see here)
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
- MovableText - the original
- MovableTextOverlay - similar, but only for text
- SpriteManager2d | MOGRE SpriteManager2d - code snippet
- OgreSprites - similar, but without use of Billboard
- ManualObject 2D
- Overlay
- Billboard
- GUI - several gui systems
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);
}
}

